diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index d3f3bd0db87..3afac9d2d54 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -23,10 +23,17 @@ permissions: jobs: baseline: - name: "Baseline" + name: "Baseline (PHP ${{ matrix.php-version }})" runs-on: ubuntu-latest timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + php-version: + - "7.4" + - "8.5" + steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 @@ -40,31 +47,65 @@ jobs: uses: "shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc" # v2.37.1 with: coverage: "none" - php-version: "8.5" + php-version: "${{ matrix.php-version }}" tools: pecl extensions: ds,mbstring ini-file: development ini-values: memory_limit=-1 + - name: "Downgrade PHPUnit" + if: matrix.php-version == '7.4' + run: "composer require --dev phpunit/phpunit:^9.6 sebastian/diff:^4.0 doctrine/instantiator:^1.0 --update-with-dependencies --ignore-platform-reqs" + - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 + + # tests/composer.json pins the Composer platform to PHP 8.2, which would make + # Composer resolve PHPBench's Symfony dependencies to versions that cannot + # even be parsed on PHP 7.4. Unsetting it resolves against the real PHP version. + - name: "Downgrade PHPBench" + if: matrix.php-version == '7.4' + run: | + composer config --unset platform.php --working-dir=tests + composer require --dev phpbench/phpbench:1.2.14 phpunit/phpunit:^9.6 brianium/paratest:^6.5 --update-with-all-dependencies --working-dir=tests + - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 + if: matrix.php-version != '7.4' with: working-directory: "tests/" + - uses: ./.github/actions/downgrade-code + with: + php-version: "${{ matrix.php-version }}" + - name: "Run phpbench baseline" - run: "tests/vendor/bin/phpbench run --dump-file=tests/bench/storage/baseline.xml --ansi" + run: "tests/vendor/bin/phpbench run --dump-file=tests/bench/storage/baseline-${{ matrix.php-version }}.xml --ansi" + # Before committing an artifact as the new baseline, compare the + # values (md5, file_rw) in it with the ones in the currently committed baseline. + # GitHub runners differ in speed by up to 2x, and a baseline recorded on a fast + # runner makes every later run look like a regression. - name: "Upload baseline artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: phpbench-baseline - path: tests/bench/storage/baseline.xml + name: phpbench-baseline-${{ matrix.php-version }} + path: tests/bench/storage/baseline-${{ matrix.php-version }}.xml test: - name: "Test" + name: "Test (PHP ${{ matrix.php-version }})" runs-on: ubuntu-latest timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + # phpbench 1.2.14 is the last version supporting PHP 7.4 + # and it does not have the aggregate-preview report 'my-report' extends + - php-version: "7.4" + report: "aggregate" + - php-version: "8.5" + report: "my-report" + steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 @@ -78,16 +119,42 @@ jobs: uses: "shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc" # v2.37.1 with: coverage: "none" - php-version: "8.5" + php-version: "${{ matrix.php-version }}" tools: pecl extensions: ds,mbstring ini-file: development ini-values: memory_limit=-1 + - name: "Downgrade PHPUnit" + if: matrix.php-version == '7.4' + run: "composer require --dev phpunit/phpunit:^9.6 sebastian/diff:^4.0 doctrine/instantiator:^1.0 --update-with-dependencies --ignore-platform-reqs" + - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 + + # tests/composer.json pins the Composer platform to PHP 8.2, which would make + # Composer resolve PHPBench's Symfony dependencies to versions that cannot + # even be parsed on PHP 7.4. Unsetting it resolves against the real PHP version. + - name: "Downgrade PHPBench" + if: matrix.php-version == '7.4' + run: | + composer config --unset platform.php --working-dir=tests + composer require --dev phpbench/phpbench:1.2.14 phpunit/phpunit:^9.6 brianium/paratest:^6.5 --update-with-all-dependencies --working-dir=tests + - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 + if: matrix.php-version != '7.4' with: working-directory: "tests/" + - uses: ./.github/actions/downgrade-code + with: + php-version: "${{ matrix.php-version }}" + + # PHPBench does not fail when the baseline file is missing, it just runs + # with an empty baseline and every assertion passes + - name: "Check that the baseline exists" + run: | + test -f tests/bench/storage/baseline-${{ matrix.php-version }}.xml \ + || (echo "Missing tests/bench/storage/baseline-${{ matrix.php-version }}.xml, download it from the phpbench-baseline-${{ matrix.php-version }} artifact of the Baseline job and commit it." && exit 1) + - name: "Run phpbench test" - run: "tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline.xml --report=my-report --ansi" + run: "tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline-${{ matrix.php-version }}.xml --report=${{ matrix.report }} --ansi" diff --git a/Makefile b/Makefile index e506d79bcf6..bfaaa6b1923 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,6 @@ lint: XDEBUG_MODE=off php vendor/bin/parallel-lint --colors \ --exclude tests/PHPStan/Analyser/data \ --exclude tests/bench/data \ - --exclude tests/bench/RegressionBench.php \ --exclude tests/PHPStan/Analyser/nsrt \ --exclude tests/PHPStan/Rules/Methods/data \ --exclude tests/PHPStan/Rules/Functions/data \ diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index d31fa7e97af..49cc9f546df 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -11,7 +11,6 @@ use PHPStan\File\CouldNotWriteFileException; use PHPStan\File\FileReader; use PHPStan\File\FileWriter; -use PHPStan\Turbo\TurboExtensionEnabler; use function array_keys; use function count; use function error_reporting; @@ -30,7 +29,6 @@ use function time; use function trim; use function unlink; -use function var_export; use const E_USER_DEPRECATED; use const PHP_RELEASE_VERSION; use const PHP_VERSION_ID; @@ -105,12 +103,6 @@ public function loadContainer(): string is_file($attributesPhp) ? hash_file('sha256', $attributesPhp) : 'attributes-missing', NeonAdapter::CACHE_KEY, $this->getAllConfigFilesHashes(), - // the stubs, not the extension: a shadowed service class is - // reflected while the container compiles, so a container built - // against the stub shells must not be reused by a run that loaded - // the extension but left it inactive (version mismatch, - // PHPSTAN_TURBO=0) and reflects the PHP implementations instead - var_export(TurboExtensionEnabler::isActive(), true), ]; $className = $loader->load( diff --git a/tests/bench/BenchCase.php b/tests/bench/BenchCase.php index 779718d72a8..755b3f3532d 100644 --- a/tests/bench/BenchCase.php +++ b/tests/bench/BenchCase.php @@ -2,10 +2,20 @@ namespace PHPStan\Benchmark; +use LogicException; use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; use PHPStan\Testing\PHPStanTestCaseTrait; +use function fclose; +use function fgets; +use function fopen; +use function preg_match; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function version_compare; +use const PHP_VERSION; abstract class BenchCase { @@ -19,6 +29,44 @@ public static function getAdditionalConfigFiles(): array ]; } + /** + * Data files that cannot be parsed on the current PHP version are not worth + * benchmarking - the analysis stops at the syntax error. + * + * Copy of TypeInferenceTestCase::isFileLintSkipped(), originally from + * https://github.com/php-parallel-lint/PHP-Parallel-Lint/blob/0c2706086ac36dce31967cb36062ff8915fe03f7/bin/skip-linting.php + * + * Copyright (c) 2012, Jakub Onderka + */ + protected static function isFileLintSkipped(string $file): bool + { + $f = @fopen($file, 'r'); + if ($f !== false) { + $firstLine = fgets($f); + if ($firstLine === false) { + return false; + } + + // ignore shebang line + if (str_starts_with($firstLine, '#!')) { + $firstLine = fgets($f); + if ($firstLine === false) { + return false; + } + } + + @fclose($f); + + if (preg_match('~ diff --git a/tests/bench/RegressionBench.php b/tests/bench/RegressionBench.php index bef98a0789f..a8929c0bcbe 100644 --- a/tests/bench/RegressionBench.php +++ b/tests/bench/RegressionBench.php @@ -2,26 +2,31 @@ namespace PHPStan\Benchmark; -use PhpBench\Attributes as Bench; use Symfony\Component\Finder\Finder; -#[Bench\Revs(revs: 1)] -#[Bench\Iterations(iterations: 5)] -#[Bench\Warmup(revs: 1)] -#[Bench\RetryThreshold(retryThreshold: 10.0)] -#[Bench\Assert(expression: ' - (mode(baseline.time.avg) < 100 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 50%) - or (mode(baseline.time.avg) >= 100 milliseconds and mode(baseline.time.avg) < 500 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 25%) - or (mode(baseline.time.avg) >= 500 milliseconds and mode(baseline.time.avg) < 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 20%) - or (mode(baseline.time.avg) >= 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%) -')] +/** + * PHPBench annotations are used instead of attributes so that the benchmark + * also runs on PHP 7.4 with the downgraded source code, where attributes + * are just comments. + * + * The assertion expression has to stay on a single line - an annotation value + * cannot span multiple lines. Annotation names must not be mentioned anywhere + * else in this docblock either, the annotation reader tries to parse them. + * + * @Revs(1) + * @Iterations(5) + * @Warmup(1) + * @RetryThreshold(10.0) + * @Assert("(mode(baseline.time.avg) < 100 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 50%) or (mode(baseline.time.avg) >= 100 milliseconds and mode(baseline.time.avg) < 500 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 25%) or (mode(baseline.time.avg) >= 500 milliseconds and mode(baseline.time.avg) < 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 20%) or (mode(baseline.time.avg) >= 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%)") + */ class RegressionBench extends BenchCase { /** + * @ParamProviders({"provideFiles"}) + * * @param array{string} $params */ - #[Bench\ParamProviders(['provideFiles'])] public function benchRunAnalyse(array $params): void { $this->runAnalyse($params[0]); @@ -42,6 +47,10 @@ private static function findTestDataFilesFromDirectory(string $directory): array $finder->sortByName(true); $files = []; foreach ($finder->files()->name('*.php')->in($directory) as $fileInfo) { + if (self::isFileLintSkipped($fileInfo->getPathname())) { + continue; + } + $files[$fileInfo->getBasename()] = [$fileInfo->getPathname()]; } diff --git a/tests/bench/data/bug-10772.php b/tests/bench/data/bug-10772.php index 76ea0790462..e7acb5a7688 100644 --- a/tests/bench/data/bug-10772.php +++ b/tests/bench/data/bug-10772.php @@ -1,4 +1,4 @@ -= 8.1 namespace Bug10772; diff --git a/tests/bench/data/bug-10979.php b/tests/bench/data/bug-10979.php index 562d7b4eeb8..e244f5a7f26 100644 --- a/tests/bench/data/bug-10979.php +++ b/tests/bench/data/bug-10979.php @@ -1,4 +1,4 @@ -= 8.1 declare(strict_types=1); diff --git a/tests/bench/data/bug-14869-enum.php b/tests/bench/data/bug-14869-enum.php index 21a7768bdad..4db97e8cd79 100644 --- a/tests/bench/data/bug-14869-enum.php +++ b/tests/bench/data/bug-14869-enum.php @@ -1,4 +1,4 @@ -= 8.1 namespace Bug14869Enum; diff --git a/tests/bench/data/conditional-expression-infinite-loop.php b/tests/bench/data/conditional-expression-infinite-loop.php index 9dda0d65b11..e4e15a3c399 100644 --- a/tests/bench/data/conditional-expression-infinite-loop.php +++ b/tests/bench/data/conditional-expression-infinite-loop.php @@ -1,4 +1,5 @@ -= 8.0 +declare(strict_types=1); namespace ConditionalExpressionInfiniteLoop; class test diff --git a/tests/bench/data/hash-key-lookup.php b/tests/bench/data/hash-key-lookup.php index d97897f6ab5..40410b9c2a0 100644 --- a/tests/bench/data/hash-key-lookup.php +++ b/tests/bench/data/hash-key-lookup.php @@ -1,4 +1,5 @@ -= 8.0 +declare(strict_types = 1); namespace BenchHashKeyLookup; diff --git a/tests/bench/data/in-array-intersect-blowup.php b/tests/bench/data/in-array-intersect-blowup.php index 2683e039145..573673f59bd 100644 --- a/tests/bench/data/in-array-intersect-blowup.php +++ b/tests/bench/data/in-array-intersect-blowup.php @@ -1,4 +1,5 @@ -= 8.0 +declare(strict_types = 1); namespace BenchInArrayIntersectBlowup; diff --git a/tests/bench/storage/baseline-7.4.xml b/tests/bench/storage/baseline-7.4.xml new file mode 100644 index 00000000000..c310e5acceb --- /dev/null +++ b/tests/bench/storage/baseline-7.4.xml @@ -0,0 +1,734 @@ + + + + + + Linux + runnervmvrwv9 + 6.17.0-1020-azure + #20~24.04.1-Ubuntu SMP Fri Jun 19 20:09:14 UTC 2026 + x86_64 + + + + 7.4.33 + /etc/php/7.4/cli/php.ini + Core, date, libxml, openssl, pcre, zlib, filter, hash, pcntl, Reflection, SPL, session, standard, sodium, mysqlnd, PDO, xml, amqp, apcu, ast, bcmath, bz2, calendar, ctype, curl, dba, dom, enchant, mbstring, FFI, fileinfo, ftp, gd, gettext, gmp, iconv, igbinary, imagick, imap, intl, json, ldap, exif, memcache, mongodb, msgpack, mysqli, odbc, pdo_dblib, PDO_Firebird, pdo_mysql, PDO_ODBC, pdo_pgsql, pdo_sqlite, pdo_sqlsrv, pgsql, Phar, posix, pspell, readline, shmop, SimpleXML, snmp, soap, sockets, sqlite3, sqlsrv, sysvmsg, sysvsem, sysvshm, tidy, tokenizer, xmlreader, xmlrpc, xmlwriter, xsl, yaml, zip, zmq, memcached, redis, ds, Zend OPcache + + + 1 + + + + 0.90576171875 + 0.31591796875 + 0.11474609375 + + + git + (unnamed branch) + + + + 0.0081062316894531 + 0.18310546875 + 2.1159648895264 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/bench/storage/baseline-8.5.xml b/tests/bench/storage/baseline-8.5.xml new file mode 100644 index 00000000000..a294a5872b6 --- /dev/null +++ b/tests/bench/storage/baseline-8.5.xml @@ -0,0 +1,844 @@ + + + + + + Linux + runnervmvrwv9 + 6.17.0-1020-azure + #20~24.04.1-Ubuntu SMP Fri Jun 19 20:09:14 UTC 2026 + x86_64 + + + + 8.5.8 + /etc/php/8.5/cli/php.ini + Core, date, lexbor, openssl, pcre, zlib, filter, hash, json, uri, Zend OPcache, pcntl, random, Reflection, SPL, session, standard, sodium, libxml, mysqlnd, PDO, xml, amqp, apcu, ast, bcmath, bz2, calendar, ctype, curl, dba, dom, enchant, mbstring, FFI, fileinfo, ftp, gd, gettext, gmp, iconv, igbinary, imagick, imap, intl, ldap, exif, memcache, mongodb, msgpack, mysqli, odbc, pdo_dblib, PDO_Firebird, pdo_mysql, PDO_ODBC, pdo_pgsql, pdo_sqlite, pdo_sqlsrv, pgsql, Phar, posix, readline, shmop, SimpleXML, snmp, soap, sockets, sqlite3, sqlsrv, sysvmsg, sysvsem, sysvshm, tidy, tokenizer, xmlreader, xmlwriter, xsl, yaml, zip, zmq, memcached, redis, ds + + + 1 + + + + 1.63720703125 + 0.41650390625 + 0.1376953125 + + + git + (unnamed branch) + + + + 0.0071525573730469 + 0.16903877258301 + 1.9760131835938 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/bench/storage/baseline.xml b/tests/bench/storage/baseline.xml deleted file mode 100644 index b1730670d84..00000000000 --- a/tests/bench/storage/baseline.xml +++ /dev/null @@ -1,844 +0,0 @@ - - - - - - Linux - runnervmvrwv9 - 6.17.0-1020-azure - #20~24.04.1-Ubuntu SMP Fri Jun 19 20:09:14 UTC 2026 - x86_64 - - - - 8.5.8 - /etc/php/8.5/cli/php.ini - Core, date, lexbor, openssl, pcre, zlib, filter, hash, json, uri, Zend OPcache, pcntl, random, Reflection, SPL, session, standard, sodium, libxml, mysqlnd, PDO, xml, amqp, apcu, ast, bcmath, bz2, calendar, ctype, curl, dba, dom, enchant, mbstring, FFI, fileinfo, ftp, gd, gettext, gmp, iconv, igbinary, imagick, imap, intl, ldap, exif, memcache, mongodb, msgpack, mysqli, odbc, pdo_dblib, PDO_Firebird, pdo_mysql, PDO_ODBC, pdo_pgsql, pdo_sqlite, pdo_sqlsrv, pgsql, Phar, posix, readline, shmop, SimpleXML, snmp, soap, sockets, sqlite3, sqlsrv, sysvmsg, sysvsem, sysvshm, tidy, tokenizer, xmlreader, xmlwriter, xsl, yaml, zip, zmq, memcached, redis, ds - - - 1 - - - - 0.8076171875 - 0.21240234375 - 0.06982421875 - - - git - 2.2.x - 8536a413cf14ebe2ef7babf84d7fca40c6ab3c2f - - - 0.0059604644775391 - 0.14996528625488 - 1.816987991333 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -