diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cbde6246e..bebfbf659 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,6 +25,10 @@ updates: - dependency-name: "phpstan/phpstan-strict-rules" - dependency-name: "phpunit/phpunit" versions: [ ">= 11.0" ] + # pcov for phpunit 10, version match is currently one version off. Check packagist + # requirements again raising phpunit/phpunit version and align it correspondingly. + - dependency-name: "phpunit/pcov" + versions: [ ">= 10.0" ] - dependency-name: "rector/type-perfect" - dependency-name: "saschaegerer/phpstan-typo3" - dependency-name: "ssch/typo3-*" diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml index 1bd6714a8..52f8d6d1a 100644 --- a/.github/workflows/codecoverage.yml +++ b/.github/workflows/codecoverage.yml @@ -9,83 +9,57 @@ on: permissions: contents: read jobs: + # @todo Rethink the whole implementation. It's possible to consider creating the part coverage logs directly + # for all unit, unitRandom and functional tests in the `ci.yml` workflow and using this only to merge + # and upload it. That would avoid reexecuting only for a subset of matrix run here again. + # In general there are two different approaches when doing the above mentioned considerations: + # + # * Use job artifacts to get all of the `Build/coverage/*.pcov` files and merge it using the pcov tool + # * Use parallel partly upload in ci.yaml unit, unitRandom, functional test runs and use this to finish + # the parallel execution. See: https://github.com/coverallsapp/github-action#complete-parallel-job-example code-coverage: name: Calculate code coverage permissions: actions: write runs-on: ubuntu-24.04 - env: - DB_DATABASE: typo3 - DB_USER: root - DB_PASSWORD: root - DB_HOST: localhost steps: - name: Checkout uses: actions/checkout@v6 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php-version }}" - ini-file: development - tools: composer:v2, phive - extensions: mysqli - coverage: xdebug - - name: Install development tools - run: phive --no-progress install --trust-gpg-keys D8406D0D82947747293778314AA394086372C20A - - name: Show Composer version - run: composer --version + - name: Show the Composer version + run: ./Build/Scripts/runTests.sh -s composer -- --version - name: Show the Composer configuration - run: composer config --global --list - - name: Cache dependencies installed with composer - uses: actions/cache@v5 - with: - key: "php-${{ matrix.php-version }}-typo3-${{ matrix.typo3-version }}-${{ matrix.composer-dependencies }}-composer-${{ hashFiles('**/composer.json') }}" - path: ~/.cache/composer - restore-keys: "php-${{ matrix.php-version }}-typo3-${{ matrix.typo3-version }}-${{ matrix.composer-dependencies }}-composer-\n" - - name: Install TYPO3 Core - env: - TYPO3: "${{ matrix.typo3-version }}" - run: | - composer require --no-ansi --no-interaction --no-progress --no-install typo3/cms-core:^"$TYPO3" - composer show - - name: Install lowest dependencies with composer - if: "matrix.composer-dependencies == 'Min'" - run: | - composer update --no-ansi --no-interaction --no-progress --with-dependencies --prefer-lowest - composer show - - name: Install highest dependencies with composer - if: "matrix.composer-dependencies == 'Max'" + run: ./Build/Scripts/runTests.sh -s composer config --global --list + - name: Install composer dependencies run: | - composer update --no-ansi --no-interaction --no-progress --with-dependencies - composer show - - name: Start MySQL - run: "sudo /etc/init.d/mysql start" + ./Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -t ${{matrix.typo3-version}} -s composerUpdate${{matrix.composer-dependencies}} + # @todo Consider to execute all unit tests with coverage and using job artifacts to merge them in one place + # instead of executing only one run here with coverage. Could also be combined directly uploading the + # parallel executions as partly upload and using this workflow only to make the final webhook call. - name: Run unit tests with coverage - run: composer check:coverage:unit - - name: Show generated coverage files - run: "ls -lahR build/coverage/" + run: | + ./Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s unit -m + # @todo Consider to execute all functional tests with coverage and using job artifacts to merge them in one place + # instead of executing only one run here with coverage. Could also be combined directly uploading the + # parallel executions as partly upload and using this workflow only to make the final webhook call. - name: Run functional tests with coverage run: | - export typo3DatabaseName="$DB_DATABASE"; - export typo3DatabaseHost="$DB_HOST"; - export typo3DatabaseUsername="$DB_USER"; - export typo3DatabasePassword="$DB_PASSWORD"; - composer check:coverage:functional + ./Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s functional -m - name: Show generated coverage files - run: "ls -lahR build/coverage/" + run: | + ls -lahR Build/coverage/ + # @todo - name: Merge coverage results - run: composer check:coverage:merge + run: | + ./Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s coverageMerge - name: Show combined coverage files - run: "ls -lahR build/logs/" + run: "ls -lahR Build/logs/" - name: Upload coverage results to Coveralls uses: coverallsapp/github-action@v2 with: fail-on-error: false env: github-token: ${{ secrets.GITHUB_TOKEN }} - # Note: This is the only path that the Coveralls GitHub Action supports. - # So we cannot use something like .Build/coverage/clover.xml here. - file: build/logs/clover.xml + file: Build/logs/clover.xml strategy: fail-fast: false matrix: diff --git a/.gitignore b/.gitignore index bb4fe5db7..05b7bc03f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ /.php-cs-fixer.cache /.phpunit.result.cache /Documentation-GENERATED-temp/ -/build +/Build/coverage +/Build/logs /composer.lock /generate-documentation.sh /nbproject diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 3f2a30359..2fbfca7ed 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -35,7 +35,16 @@ printSummary() { echo "DBMS: ${DBMS} driver pdo_sqlite" >&2 ;; esac + if [[ "${CREATE_COVERAGE}" -eq 1 ]]; then + echo "COVERAGE-LOG-FILE: ${COVERAGE_LOG_FILE}" + fi + fi + if [[ ${TEST_SUITE} =~ ^unit ]]; then + if [[ "${CREATE_COVERAGE}" -eq 1 ]]; then + echo "COVERAGE-LOG-FILE: ${COVERAGE_LOG_FILE}" + fi fi + if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then echo "SUCCESS" >&2 else @@ -194,6 +203,7 @@ Options: - composerUnused: Finds unused Composer packages. - composerUpdateMax: "composer update", with no platform.php config. - composerUpdateMin: "composer update --prefer-lowest", with platform.php set to PHP version x.x.0. + - coverageMerge: Merge gathered coverage files into one set. - docsGenerate: Renders the extension ReST documentation. - fix: Runs all automatic code style fixes. - fixComposerNormalize: Normalizes the composer.json. @@ -316,6 +326,11 @@ Options: Only with -s cgl|composerNormalize|npm|lintJs|lintCss Activate dry-run in checks so they do not actively change files and only print broken ones. + -m + Only for functional|functionalDeprecated|unit|unitDeprecated|unitRandom + Activate collecting coverage metrics for executed test variant. The metrics are saved generic + to "Build/coverage/" to allow merging results later with "-s coverageMerge". + -u Update existing typo3/core-testing-*:latest container images and remove dangling local volumes. New images are published once in a while and only the latest ones are supported by core testing. @@ -388,6 +403,9 @@ CI_PARAMS="${CI_PARAMS:-}" CONTAINER_HOST="host.docker.internal" # shellcheck disable=SC2034 # This variable will be needed when we try to clean up the root folder PHPSTAN_CONFIG_FILE="Build/phpstan/phpstan.neon" +CREATE_COVERAGE=0 +COVERAGE_ADDITIONAL="" +COVERAGE_LOG_FILE="" # Option parsing updates above default vars # Reset in case getopts has been used previously in the shell @@ -395,7 +413,7 @@ OPTIND=1 # Array for invalid options INVALID_OPTIONS=() # Simple option parsing based on getopts (! not getopt) -while getopts "a:b:s:d:i:p:t:xy:o:nhu" OPT; do +while getopts "a:b:s:d:i:p:t:xy:o:nmhu" OPT; do case ${OPT} in s) TEST_SUITE=${OPTARG} @@ -439,6 +457,9 @@ while getopts "a:b:s:d:i:p:t:xy:o:nhu" OPT; do n) CGLCHECK_DRY_RUN=1 ;; + m) + CREATE_COVERAGE=1 + ;; h) loadHelp echo "${HELP}" @@ -518,6 +539,10 @@ else CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" fi +if test ${PHP_XDEBUG_ON} -eq 1 && test ${CREATE_COVERAGE} -eq 1; then + echo "Option \"-m\" and \"-x\" cannot be used together. Remove one." + exit 1 +fi if [ ${PHP_XDEBUG_ON} -eq 0 ]; then XDEBUG_MODE="-e XDEBUG_MODE=off" XDEBUG_CONFIG=" " @@ -526,6 +551,15 @@ else XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal" fi +if [ ${CREATE_COVERAGE} -eq 1 ]; then + XDEBUG_MODE="-e XDEBUG_MODE=coverage" + XDEBUG_CONFIG=" " +else + # Ensure to reset even if provided in case collecting coverage is not enabled + COVERAGE_ADDITIONAL="" +fi +[[ -n "${COVERAGE_ADDITIONAL}" ]] && COVERAGE_ADDITIONAL="${COVERAGE_ADDITIONAL}-" + # Suite execution case ${TEST_SUITE} in cgl) @@ -578,6 +612,13 @@ case ${TEST_SUITE} in ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-min-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; + coverageMerge) + [[ ! -d "Build/coverage/" ]] && echo "Nothing to merge" && SUITE_EXIT_CODE=1 && printSummary && exit 1 + [[ ! -d "Build/logs/" ]] && mkdir -p "Build/logs" + COMMAND=".Build/bin/phpcov merge --clover=Build/logs/clover.xml Build/coverage/" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-unused-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; docsGenerate) mkdir -p Documentation-GENERATED-temp chown -R ${HOST_UID}:${HOST_PID} Documentation-GENERATED-temp @@ -599,9 +640,13 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; functional) - COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} "$@") + # Default COMMAND_ARRAY for non-coverage runs. Will be overridden for coverage runs for functional test type. + COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group "not-${DBMS}" "$@") + [[ "${CREATE_COVERAGE}" -eq 1 ]] && mkdir -p Build/coverage Build/logs case ${DBMS} in mariadb) + COVERAGE_LOG_FILE="functional-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//')-$(echo "mariadb${DBMS_VERSION}" | sed -e 's/\./_/')-$(echo "driver${DATABASE_DRIVER}" | sed -e 's/\.//').cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group "not-${DBMS}" --coverage-php=Build/coverage/${COVERAGE_LOG_FILE} "$@") echo "Using driver: ${DATABASE_DRIVER}" ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null waitFor mariadb-func-${SUFFIX} 3306 @@ -610,6 +655,8 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; mysql) + COVERAGE_LOG_FILE="functional-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//')-$(echo "mysql${DBMS_VERSION}" | sed -e 's/\./_/')-$(echo "driver${DATABASE_DRIVER}" | sed -e 's/\.//').cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group "not-${DBMS}" --coverage-php=Build/coverage/${COVERAGE_LOG_FILE} "$@") echo "Using driver: ${DATABASE_DRIVER}" ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null waitFor mysql-func-${SUFFIX} 3306 @@ -618,6 +665,8 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; postgres) + COVERAGE_LOG_FILE="functional-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//')-$(echo "postgres${DBMS_VERSION}" | sed -e 's/\./_/')-$(echo "driver${DATABASE_DRIVER}" | sed -e 's/\.//').cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group "not-${DBMS}" --coverage-php=Build/coverage/${COVERAGE_LOG_FILE} "$@") ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-func-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null waitFor postgres-func-${SUFFIX} 5432 CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-${SUFFIX} -e typo3DatabasePassword=funcp" @@ -625,6 +674,8 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; sqlite) + COVERAGE_LOG_FILE="functional-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//')-sqlite.cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group "not-${DBMS}" --coverage-php=Build/coverage/${COVERAGE_LOG_FILE} "$@") CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite" ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? @@ -712,11 +763,17 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; unit) - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} .Build/bin/phpunit -c Build/phpunit/UnitTests.xml "$@" + COVERAGE_LOG_FILE="unit-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//').cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && mkdir -p Build/coverage Build/logs + [[ "${CREATE_COVERAGE}" -eq 1 ]] && PHPUNIT_COVERAGE_OPTION="--coverage-php=Build/coverage/${COVERAGE_LOG_FILE}" || PHPUNIT_COVERAGE_OPTION="" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} .Build/bin/phpunit -c Build/phpunit/UnitTests.xml ${PHPUNIT_COVERAGE_OPTION} "$@" SUITE_EXIT_CODE=$? ;; unitRandom) - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-random-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} .Build/bin/phpunit -c Build/phpunit/UnitTests.xml --order-by=random ${PHPUNIT_RANDOM} "$@" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && mkdir -p Build/coverage Build/logs + COVERAGE_LOG_FILE="unitrandom-$(echo "core${CORE_VERSION}" | sed -e 's/\./-/')-$(echo "php${PHP_VERSION}" | sed -e 's/\.//').cov" + [[ "${CREATE_COVERAGE}" -eq 1 ]] && PHPUNIT_COVERAGE_OPTION="--coverage-php=Build/coverage/${COVERAGE_LOG_FILE}" || PHPUNIT_COVERAGE_OPTION="" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-random-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} .Build/bin/phpunit -c Build/phpunit/UnitTests.xml ${PHPUNIT_COVERAGE_OPTION} --order-by=random ${PHPUNIT_RANDOM} "$@" SUITE_EXIT_CODE=$? ;; update) diff --git a/composer.json b/composer.json index 08b84ef8c..8fb8adf74 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ "phpstan/phpstan": "1.12.33 || 2.1.40", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.12", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.8", + "phpunit/phpcov": "9.0.2", "phpunit/phpunit": "10.5.63", "rector/type-perfect": "1.0.0 || 2.1.2", "saschaegerer/phpstan-typo3": "1.10.2 || 2.1.1", @@ -123,15 +124,15 @@ "check:coverage:functional": [ "@check:tests:create-directories", "@coverage:create-directories", - "phpunit -c Build/phpunit/FunctionalTests.xml --coverage-php=build/coverage/functional.cov" + "phpunit -c Build/phpunit/FunctionalTests.xml --coverage-php=Build/coverage/functional.cov" ], "check:coverage:merge": [ "@coverage:create-directories", - "@php tools/phpcov merge --clover=build/logs/clover.xml build/coverage/" + "@php tools/phpcov merge --clover=Build/logs/clover.xml Build/coverage/" ], "check:coverage:unit": [ "@coverage:create-directories", - "phpunit -c Build/phpunit/UnitTests.xml --coverage-php=build/coverage/unit.cov" + "phpunit -c Build/phpunit/UnitTests.xml --coverage-php=Build/coverage/unit.cov" ], "check:json:lint": "find . ! -path '*/.cache/*' ! -path '*/.Build/*' ! -path '*/node_modules/*' -name '*.json' | xargs -r php .Build/bin/jsonlint -q", "check:php": [ @@ -165,7 +166,7 @@ "check:typoscript:lint": "typoscript-lint -c Build/typoscript-lint/config.yml --ansi -n --fail-on-warnings -vvv Configuration/TypoScript Tests/Functional/Controller/Fixtures/TypoScript", "check:xliff:lint": "php Build/Scripts/xliffLint.sh lint:xliff Resources/Private/Language", "check:yaml:lint": "find . ! -path '*.Build/*' ! -path '*node_modules/*' \\( -name '*.yaml' -o -name '*.yml' \\) | xargs -r php ./.Build/bin/yaml-lint", - "coverage:create-directories": "mkdir -p build/coverage build/logs", + "coverage:create-directories": "mkdir -p Build/coverage Build/logs", "fix": [ "@fix:composer:normalize", "@fix:php" diff --git a/phive.xml b/phive.xml index 93d62fd47..fb817624c 100644 --- a/phive.xml +++ b/phive.xml @@ -1,4 +1,12 @@ +