diff --git a/.github/workflows/qit-custom-test-woocommerce.yml b/.github/workflows/qit-custom-test-woocommerce.yml new file mode 100644 index 00000000..043d35b9 --- /dev/null +++ b/.github/workflows/qit-custom-test-woocommerce.yml @@ -0,0 +1,89 @@ +name: QIT Custom Test - WooCommerce + +on: + # Manually + workflow_dispatch: + +jobs: + playwright-tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Setup PHP (Cross-platform) + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Enable dev mode + run: ./qit dev + + # Delay start of tests based on shardIndex to space out requests and avoid 429. + # Example: This step causes shard 1 to start immediately, shard 2 to start after a 10-second delay, shard 3 after a 20-second delay, etc. + - name: Wait before running tests + run: sleep $(( (${{ matrix.shardIndex }} - 1) * 10 )) + + - name: Connect to Staging QIT + run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com" + + - name: Composer install + working-directory: src + run: composer install + + - name: Start environment + working-directory: src + run: php qit-cli.php run:e2e https://github.com/woocommerce/woocommerce/releases/download/8.8.1/woocommerce.zip:test:./../_tests/example-tests/woocommerce -v --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + + - name: Set the path in an env var + if: ${{ !cancelled() }} + working-directory: src + run: | + echo "BLOB_REPORT_PATH=$(php qit-cli.php e2e-report --dir_only)/blob" >> $GITHUB_ENV + + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: ${{ env.BLOB_REPORT_PATH }} + retention-days: 1 + + merge-reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [playwright-tests] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install Playwright + run: npm install playwright + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 \ No newline at end of file diff --git a/.github/workflows/qit-environment-dangling-test.yml b/.github/workflows/qit-environment-dangling-test.yml index 1745e890..494f115f 100644 --- a/.github/workflows/qit-environment-dangling-test.yml +++ b/.github/workflows/qit-environment-dangling-test.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - environment_dangling_tests: + test_stop_random_container: runs-on: ubuntu-latest env: NO_COLOR: 1 @@ -22,44 +22,62 @@ jobs: with: php-version: '8.3' - - name: Composer install (Unix) + - name: Run setup script + run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com + + - name: Environment Setup and Checks + id: env_setup + run: bash .github/workflows/tests/environments/start-and-assert.sh + + - name: Store the environment path in a ENV var working-directory: src - run: composer install + run: echo "TEMPORARY_ENV=$(php qit-cli.php env:list --field=temporary_env)" >> $GITHUB_ENV - - name: Enable dev mode + - name: Stop a random container from that environment. working-directory: src - run: php qit-cli.php dev + run: | + CONTAINERS=$(php qit-cli.php env:list --field=docker_images) + RANDOM_CONTAINER=$(echo $CONTAINERS | awk '{print $1}') + echo "Stopping container: $RANDOM_CONTAINER" + docker stop $RANDOM_CONTAINER - - name: Connect to Staging QIT + - name: Run a env:list to trigger the Dangling Environment Cleanup working-directory: src - run: php qit-cli.php backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com" + run: php qit-cli.php env:list - - name: Add "qit.test" to hosts file (Linux) - run: sudo echo "127.0.0.1 qit.test" | sudo tee -a /etc/hosts + - name: Environment Cleanup Checks + id: env_cleanup + run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh + + test_delete_environment_entry: + runs-on: ubuntu-latest + env: + NO_COLOR: 1 + QIT_DISABLE_ONBOARDING: yes + steps: + - name: Checkout code (Cross-platform) + uses: actions/checkout@v4 - - name: Start environment + - name: Setup PHP (Cross-platform) + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Run setup script + run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com + + - name: Environment Setup and Checks + id: env_setup + run: bash .github/workflows/tests/environments/start-and-assert.sh + + - name: Find and delete all hidden JSON files in the directory working-directory: src - run: php qit-cli.php env:up + run: php qit-cli.php cache delete environment_monitor - - name: Test Site Up - run: | - # Fetch the site URL - SITE_URL=$(php qit-cli.php env:list --field=site_url) - echo "Site URL: $SITE_URL" - - # Assert home page is 200 - HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}\n" $SITE_URL) - if [ "$HTTP_STATUS" -ne 200 ]; then - echo "Home page is not up. HTTP status: $HTTP_STATUS" - exit 1 - fi - echo "Home page is up. HTTP status: $HTTP_STATUS" - - # Assert the name property in the JSON response - JSON_RESPONSE=$(curl -s $SITE_URL/wp-json) - NAME_PROPERTY=$(echo $JSON_RESPONSE | jq -r '.name') - if [ "$NAME_PROPERTY" != "WooCommerce Core E2E Test Suite" ]; then - echo "Name property does not match. Found: $NAME_PROPERTY" - exit 1 - fi - echo "Name property matches: $NAME_PROPERTY" \ No newline at end of file + - name: Run a env:list to trigger the Dangling Environment Cleanup + working-directory: src + run: php qit-cli.php env:list + + - name: Environment Cleanup Checks + id: env_cleanup + run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh \ No newline at end of file diff --git a/.github/workflows/qit-environment-test-linux.yml b/.github/workflows/qit-environment-test-linux.yml index 27dd2c02..619d2d14 100644 --- a/.github/workflows/qit-environment-test-linux.yml +++ b/.github/workflows/qit-environment-test-linux.yml @@ -1,4 +1,4 @@ -name: QIT Environment Test - Linux and Mac +name: QIT Environment Test - Linux on: # Every day at 11pm UTC (6pm ET) @@ -8,19 +8,17 @@ on: workflow_dispatch: jobs: - environment_tests: + linux_environment_test: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-20.04, macos-13 ] + os: [ ubuntu-20.04 ] php: [ '7.4' ] wordpress: [ 'rc' ] - woocommerce: [ 'rc' ] include: - os: ubuntu-20.04 php: '8.3' wordpress: 'rc' - woocommerce: 'rc' fail-fast: false env: NO_COLOR: 1 @@ -34,26 +32,13 @@ jobs: with: php-version: '8.3' - - name: Enable dev mode (Cross-platform) + - name: Enable dev mode run: ./qit dev - - name: Connect to Staging QIT (Cross-platform) + - name: Connect to Staging QIT run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com" - - name: Add "qit.test" to hosts file (Linux) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' - run: sudo echo "127.0.0.1 qit.test" | sudo tee -a /etc/hosts - - - name: Setup Docker on macOS (Mac) - if: matrix.os == 'macos-13' - uses: douglascamata/setup-docker-macos-action@v1-alpha - - - name: Check Colima Status (Mac) - if: matrix.os == 'macos-13' - run: colima status - - - name: Debug Docker and Docker Compose (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Debug Docker and Docker Compose run: | echo "Checking Docker version..." docker --version || echo "Docker is not installed" @@ -64,24 +49,19 @@ jobs: echo "Checking Docker Compose (standalone) version..." docker-compose --version || echo "Docker Compose (standalone) is not installed" - - name: Benchmark Docker (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Benchmark Docker working-directory: scripts run: docker run -v $GITHUB_WORKSPACE/scripts:/scripts -w /scripts alpine sh -c "apk add --no-cache bash wget tar; chmod +x benchmark.sh; ./benchmark.sh" - - name: Composer install (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Composer install working-directory: src run: composer install - - name: Start environment (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Start environment working-directory: src - env: - QIT_DOCKER_RUN_TIMEOUT: ${{ (matrix.os == 'ubuntu-20.04') && '300' || '1800' }} - run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --woocommerce_version ${{ matrix.woocommerce }} --php_version ${{ matrix.php }} + run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --php_version ${{ matrix.php }} - - name: Get URL (Cross-platform) + - name: Get URL working-directory: src run: | siteUrl=$(php -r "echo exec(PHP_BINARY . ' qit-cli.php env:list --field=site_url');"); @@ -91,7 +71,7 @@ jobs: run: | php -r "echo 'Site URL: ' . getenv('SITE_URL');" - - name: Test Site Up (Cross-platform) + - name: Test Site Up run: | php -r " \$url = '${{ env.SITE_URL }}'; @@ -105,17 +85,15 @@ jobs: exit(1); }" - - name: Query WP JSON (Cross-platform) + - name: Query WP JSON run: php -r "echo file_get_contents('${{ env.SITE_URL }}/wp-json');" - - name: Verify PHP Version (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Verify PHP Version working-directory: src run: | actual_major_minor=$(docker exec $(php qit-cli.php env:list --field=docker_images | grep php) php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;') [[ "$actual_major_minor" == "${{ matrix.php }}" ]] || { echo "PHP Major.Minor version mismatch. Expected: ${{ matrix.php }}, Actual: $actual_major_minor"; exit 1; } - - name: Print WordPress Version (Unix) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13' + - name: Print WordPress Version working-directory: src run: php qit-cli.php env:exec "wp core version" \ No newline at end of file diff --git a/.github/workflows/qit-environment-test-mac.yml b/.github/workflows/qit-environment-test-mac.yml new file mode 100644 index 00000000..7a7c3cd7 --- /dev/null +++ b/.github/workflows/qit-environment-test-mac.yml @@ -0,0 +1,109 @@ +## +# ARM64 Macs such as M1/M2/M3 can't run Docker in CI, so we test only with older Macs. +# More info: https://github.com/douglascamata/setup-docker-macos-action +## +name: QIT Environment Test - Mac (non M1) + +on: + # Every day at 11pm UTC (6pm ET) + schedule: + - cron: '0 23 * * *' + # Manually + workflow_dispatch: + +jobs: + mac_environment_test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-13 ] + php: [ '7.4' ] + wordpress: [ 'rc' ] + fail-fast: false + env: + NO_COLOR: 1 + QIT_DISABLE_ONBOARDING: yes + steps: + - name: Checkout code (Cross-platform) + uses: actions/checkout@v4 + + - name: Setup PHP (Cross-platform) + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Enable dev mode + run: ./qit dev + + - name: Connect to Staging QIT + run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com" + + - name: Setup Docker on macOS + if: matrix.os == 'macos-13' + uses: douglascamata/setup-docker-macos-action@v1-alpha + + - name: Check Colima Status (Mac) + if: matrix.os == 'macos-13' + run: colima status + + - name: Debug Docker and Docker Compose + run: | + echo "Checking Docker version..." + docker --version || echo "Docker is not installed" + + echo "Checking Docker Compose (plugin) version..." + docker compose version || echo "Docker Compose (plugin) is not installed" + + echo "Checking Docker Compose (standalone) version..." + docker-compose --version || echo "Docker Compose (standalone) is not installed" + + - name: Benchmark Docker + working-directory: scripts + run: docker run -v $GITHUB_WORKSPACE/scripts:/scripts -w /scripts alpine sh -c "apk add --no-cache bash wget tar; chmod +x benchmark.sh; ./benchmark.sh" + + - name: Composer install + working-directory: src + run: composer install + + - name: Start environment + working-directory: src + env: + QIT_DOCKER_RUN_TIMEOUT: 1800 + run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --php_version ${{ matrix.php }} + + - name: Get URL + working-directory: src + run: | + siteUrl=$(php -r "echo exec(PHP_BINARY . ' qit-cli.php env:list --field=site_url');"); + echo "SITE_URL=$siteUrl" >> $GITHUB_ENV + + - name: Print site URL + run: | + php -r "echo 'Site URL: ' . getenv('SITE_URL');" + + - name: Test Site Up + run: | + php -r " + \$url = '${{ env.SITE_URL }}'; + \$context = stream_context_create(['http' => ['method' => 'GET']]); + \$headers = @get_headers(\$url, 1, \$context); + \$statusCode = \$headers ? substr(\$headers[0], 9, 3) : 500; + if (\$statusCode == 200) { + echo 'Site is up'; + } else { + echo 'Site is not up, status code: ' . \$statusCode; + exit(1); + }" + + - name: Query WP JSON + run: php -r "echo file_get_contents('${{ env.SITE_URL }}/wp-json');" + + - name: Verify PHP Version + working-directory: src + run: | + actual_major_minor=$(docker exec $(php qit-cli.php env:list --field=docker_images | grep php) php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;') + [[ "$actual_major_minor" == "${{ matrix.php }}" ]] || { echo "PHP Major.Minor version mismatch. Expected: ${{ matrix.php }}, Actual: $actual_major_minor"; exit 1; } + + - name: Print WordPress Version + working-directory: src + run: php qit-cli.php env:exec "wp core version" \ No newline at end of file diff --git a/.github/workflows/qit-self-test-activation.yml b/.github/workflows/qit-self-test-activation.yml index e773d203..3882c31d 100644 --- a/.github/workflows/qit-self-test-activation.yml +++ b/.github/workflows/qit-self-test-activation.yml @@ -19,4 +19,5 @@ jobs: test_type: activation secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-custom-test.yml b/.github/workflows/qit-self-test-custom-test.yml new file mode 100644 index 00000000..c3e9e20a --- /dev/null +++ b/.github/workflows/qit-self-test-custom-test.yml @@ -0,0 +1,46 @@ +name: QIT Self-Tests - Custom Tests + +on: + # Every day at 11pm UTC (6pm ET) + schedule: + - cron: '0 23 * * *' + # Manually + workflow_dispatch: + +jobs: + self_test_custom_test: + runs-on: ubuntu-20.04 + env: + NO_COLOR: 1 + QIT_DISABLE_ONBOARDING: yes + QIT_DISABLE_CLEANUP: 1 + QIT_SELF_TESTS: 1 + steps: + - name: Checkout code (Cross-platform) + uses: actions/checkout@v4 + + - name: Create tmp directory on the workspace. + run: mkdir -p ${{ github.workspace }}/tmp + + - name: Setup PHP (Cross-platform) + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + ini-values: sys_temp_dir=${{ github.workspace }}/tmp + + - name: Composer install + working-directory: _tests/custom_tests + run: composer install + + - name: Fill in .env + working-directory: _tests/custom_tests + run: | + echo 'QIT_CUSTOM_TESTS_USER="noop"' >> .env + echo 'QIT_CUSTOM_TESTS_USER_QIT_TOKEN="noop"' >> .env + echo 'QIT_CUSTOM_TESTS_SECRET="${{ secrets.QIT_STAGING_SECRET }}"' >> .env + echo 'QIT_CUSTOM_TESTS_URL="https://stagingcompatibilitydashboard.wpcomstaging.com/"' >> .env + echo 'QIT_CUSTOM_TESTS_ENV="staging"' >> .env + + - name: Run tests in parallel + working-directory: _tests/custom_tests + run: ./vendor/bin/phpunit diff --git a/.github/workflows/qit-self-test-malware.yml b/.github/workflows/qit-self-test-malware.yml index f4f1cf55..aaeaddf6 100644 --- a/.github/workflows/qit-self-test-malware.yml +++ b/.github/workflows/qit-self-test-malware.yml @@ -19,4 +19,5 @@ jobs: test_type: malware secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-phpcompatibility.yml b/.github/workflows/qit-self-test-phpcompatibility.yml index 8a915cec..a3f626bc 100644 --- a/.github/workflows/qit-self-test-phpcompatibility.yml +++ b/.github/workflows/qit-self-test-phpcompatibility.yml @@ -19,4 +19,5 @@ jobs: test_type: phpcompatibility secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-phpstan.yml b/.github/workflows/qit-self-test-phpstan.yml index 443f0f7d..5ee83a8d 100644 --- a/.github/workflows/qit-self-test-phpstan.yml +++ b/.github/workflows/qit-self-test-phpstan.yml @@ -19,4 +19,5 @@ jobs: test_type: phpstan secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-security.yml b/.github/workflows/qit-self-test-security.yml index 8845f993..2ea90002 100644 --- a/.github/workflows/qit-self-test-security.yml +++ b/.github/workflows/qit-self-test-security.yml @@ -19,4 +19,5 @@ jobs: test_type: security secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-woo-api.yml b/.github/workflows/qit-self-test-woo-api.yml index bf10001e..bfbb96f2 100644 --- a/.github/workflows/qit-self-test-woo-api.yml +++ b/.github/workflows/qit-self-test-woo-api.yml @@ -19,4 +19,5 @@ jobs: test_type: woo-api secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-self-test-woo-e2e.yml b/.github/workflows/qit-self-test-woo-e2e.yml index 348f0513..abe8c370 100644 --- a/.github/workflows/qit-self-test-woo-e2e.yml +++ b/.github/workflows/qit-self-test-woo-e2e.yml @@ -20,4 +20,5 @@ jobs: test_type: woo-e2e secrets: QIT_USER: ${{ secrets.QIT_USER }} - QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} \ No newline at end of file + QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }} + QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }} \ No newline at end of file diff --git a/.github/workflows/qit-windows.yml b/.github/workflows/qit-windows.yml deleted file mode 100644 index 494f115f..00000000 --- a/.github/workflows/qit-windows.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: QIT Environment Dangling Test - -on: - # Every day at 11pm UTC (6pm ET) - schedule: - - cron: '0 23 * * *' - # Manually - workflow_dispatch: - -jobs: - test_stop_random_container: - runs-on: ubuntu-latest - env: - NO_COLOR: 1 - QIT_DISABLE_ONBOARDING: yes - steps: - - name: Checkout code (Cross-platform) - uses: actions/checkout@v4 - - - name: Setup PHP (Cross-platform) - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Run setup script - run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com - - - name: Environment Setup and Checks - id: env_setup - run: bash .github/workflows/tests/environments/start-and-assert.sh - - - name: Store the environment path in a ENV var - working-directory: src - run: echo "TEMPORARY_ENV=$(php qit-cli.php env:list --field=temporary_env)" >> $GITHUB_ENV - - - name: Stop a random container from that environment. - working-directory: src - run: | - CONTAINERS=$(php qit-cli.php env:list --field=docker_images) - RANDOM_CONTAINER=$(echo $CONTAINERS | awk '{print $1}') - echo "Stopping container: $RANDOM_CONTAINER" - docker stop $RANDOM_CONTAINER - - - name: Run a env:list to trigger the Dangling Environment Cleanup - working-directory: src - run: php qit-cli.php env:list - - - name: Environment Cleanup Checks - id: env_cleanup - run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh - - test_delete_environment_entry: - runs-on: ubuntu-latest - env: - NO_COLOR: 1 - QIT_DISABLE_ONBOARDING: yes - steps: - - name: Checkout code (Cross-platform) - uses: actions/checkout@v4 - - - name: Setup PHP (Cross-platform) - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Run setup script - run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com - - - name: Environment Setup and Checks - id: env_setup - run: bash .github/workflows/tests/environments/start-and-assert.sh - - - name: Find and delete all hidden JSON files in the directory - working-directory: src - run: php qit-cli.php cache delete environment_monitor - - - name: Run a env:list to trigger the Dangling Environment Cleanup - working-directory: src - run: php qit-cli.php env:list - - - name: Environment Cleanup Checks - id: env_cleanup - run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh \ No newline at end of file diff --git a/.github/workflows/self-test-template.yml b/.github/workflows/self-test-template.yml index bb92fe06..f75a80c8 100644 --- a/.github/workflows/self-test-template.yml +++ b/.github/workflows/self-test-template.yml @@ -12,6 +12,8 @@ on: required: true QIT_ACCESS_TOKEN: required: true + QIT_STAGING_SECRET: + required: true jobs: self_test: @@ -57,11 +59,17 @@ jobs: php-version: '7.2' - name: Composer install - working-directory: _tests + working-directory: _tests/managed_tests run: composer install - - name: Connect to QIT - run: ./qit partner:add --user="${{ secrets.QIT_USER }}" --application_password="${{ secrets.QIT_ACCESS_TOKEN }}" + #- name: Connect to QIT + # run: ./qit partner:add --user="${{ secrets.QIT_USER }}" --application_password="${{ secrets.QIT_ACCESS_TOKEN }}" + + - name: Enable dev (Staging) + run: ./qit dev + + - name: Connect to QIT (Staging) + run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com" ### Cache ZIP Image - name: Set up date @@ -88,7 +96,7 @@ jobs: - name: Run Self-Tests id: run-self-tests - working-directory: _tests + working-directory: _tests/managed_tests env: QIT_WAIT_BEFORE_REQUEST: yes run: php ./QITSelfTests.php run ${{ inputs.test_type }} diff --git a/.gitignore b/.gitignore index 3ad22315..a7786d22 100644 --- a/.gitignore +++ b/.gitignore @@ -14,15 +14,19 @@ project.properties .sublimelinterrc vendor/ -!_tests/phpstan/with_vendor/woocommerce-product-feeds/vendor +!_tests/managed_tests/phpstan/with_vendor/woocommerce-product-feeds/vendor .idea/ src-tmp/ .cache .phpunit.result.cache ./qit -_tests/**/*.zip -_tests/**/*.txt +_tests/managed_tests/**/*.zip +_tests/managed_tests/**/*.txt dev/ _build/docker/php83/xdebug/* !_build/docker/php83/xdebug/.gitkeep -!_build/docker/php83/xdebug/README.md \ No newline at end of file +!_build/docker/php83/xdebug/README.md +_tests/custom_tests/.env +_tests/custom_tests/tmp_qit_config* +_tests/custom_tests/cache +_tests/custom_tests/tmp/ diff --git a/Makefile b/Makefile index 7559873f..da06fcc8 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ phpcbf: $(call execPhpAlpine,/app/src/vendor/bin/phpcbf /app/src/qit-cli.php /app/src/src -s --standard=/app/src/.phpcs.xml.dist) phpcs: - $(MAKE) phpcbf + $(MAKE) phpcbf || true $(call execPhpAlpine,/app/src/vendor/bin/phpcs /app/src/qit-cli.php /app/src/src -s --standard=/app/src/.phpcs.xml.dist) phpstan: diff --git a/_tests/custom_tests/.env.sample b/_tests/custom_tests/.env.sample new file mode 100644 index 00000000..2147dcbf --- /dev/null +++ b/_tests/custom_tests/.env.sample @@ -0,0 +1,14 @@ +# The username of the partner being used. +QIT_CUSTOM_TESTS_USER="" + +# The token of the partner being used. +QIT_CUSTOM_TESTS_USER_QIT_TOKEN="" + +# The Secret of the environment. +QIT_CUSTOM_TESTS_SECRET="" + +# The URL to the QIT Manager being used (URL of the Environment). +QIT_CUSTOM_TESTS_URL="" + +# The environment, eg: "staging", "local", "production". +QIT_CUSTOM_TESTS_ENV="" \ No newline at end of file diff --git a/_tests/custom_tests/QITTestExtension.php b/_tests/custom_tests/QITTestExtension.php new file mode 100644 index 00000000..ab24a89d --- /dev/null +++ b/_tests/custom_tests/QITTestExtension.php @@ -0,0 +1,289 @@ +registerSubscriber( new \QITTestStart() ); + $facade->registerSubscriber( new \QITTestFinish() ); + } +} + +class QITTestStart implements ExecutionStartedSubscriber { + public function notify( ExecutionStarted $event ): void { + if ( file_exists( __DIR__ . '/qit-env.json' ) ) { + unlink( __DIR__ . '/qit-env.json' ); + echo 'Deleted qit-env.json'; + } + + $dotenv = Dotenv::createImmutable( __DIR__ ); + $dotenv->load(); + $dotenv->required( [ + 'QIT_CUSTOM_TESTS_USER', + 'QIT_CUSTOM_TESTS_USER_QIT_TOKEN', + 'QIT_CUSTOM_TESTS_SECRET', + 'QIT_CUSTOM_TESTS_URL', + 'QIT_CUSTOM_TESTS_ENV', + ] ); + + if ( ! file_exists( __DIR__ . '/../../qit' ) ) { + throw new \RuntimeException( sprintf( 'The qit binary was not found at %s.', realpath( __DIR__ . '/../../qit' ) ) ); + } + + $GLOBALS['qit'] = __DIR__ . '/../../qit'; + + // Generate an ID for this run. + $run_id = uniqid( 'qit_custom_tests_' ); + $qit_tmp_dir = __DIR__ . "/tmp/tmp_qit_config-$run_id"; + + $GLOBALS['QIT_HOME'] = $qit_tmp_dir; + $GLOBALS['RUN_ID'] = $run_id; + + $fs = new Filesystem(); + + // We utilize the filesystem as shared state to coordinate parallel processes. + if ( ! touch( sys_get_temp_dir() . '/test-initialization-lock-file' ) ) { + throw new \RuntimeException( 'Failed to create lock file at ' . sys_get_temp_dir() . '/test-initialization-lock-file' ); + } + + $lock_file = fopen( sys_get_temp_dir() . '/test-initialization-lock-file', 'w+' ); + + if ( ! $lock_file ) { + throw new \RuntimeException( 'Failed to open lock file at ' . sys_get_temp_dir() . '/test-initialization-lock-file' ); + } + + // Attempt to get an exclusive lock - first process wins. + if ( flock( $lock_file, LOCK_EX | LOCK_NB ) ) { + echo sprintf( "Process %s has exclusive lock\n", getenv( 'TEST_TOKEN' ) ); + // Since we are the single process that has an exclusive lock, we run the initialization. + + // Cleanup initial state. + array_map( 'unlink', glob( sys_get_temp_dir() . '/qit-running-*' ) ); + array_map( 'unlink', glob( sys_get_temp_dir() . '/qit-test-tag-lock-*' ) ); + if ( file_exists( sys_get_temp_dir() . '/qit-semaphore' ) ) { + unlink( sys_get_temp_dir() . '/qit-semaphore' ); + } + // Delete all directories in the current dir that matches the pattern "tmp_qit_config-*" + if ( file_exists( __DIR__ . '/tmp/' ) ) { + try { + $fs->remove( __DIR__ . '/tmp/' ); + } catch ( \Exception $e ) { + /** + * Try to delete twice, waiting a bit before the second retry, as during + * code review an issue popped up on Mac when deleting resource fork files. + * @link https://github.com/woocommerce/qit-cli/pull/148#issuecomment-2096920576 + */ + sleep( 5 ); + $fs->remove( __DIR__ . '/tmp/' ); + } + } + + if ( ! mkdir( __DIR__ . '/tmp', 0755, true ) ) { + throw new \RuntimeException( 'Failed to create the tmp directory.' ); + } + + // Enable dev mode. + $dev = new Process( [ $GLOBALS['qit'], 'dev' ] ); + $dev->setEnv( [ + 'QIT_HOME' => $GLOBALS['QIT_HOME'], + ] ); + $dev->setTimeout( 30 ); + $dev->setIdleTimeout( 30 ); + $dev->mustRun( function ( $type, $buffer ) { + echo $buffer; + } ); + + // Add the environment. + $add_environment = new Process( [ + $GLOBALS['qit'], + 'backend:add', + '--manager_url', + $_ENV['QIT_CUSTOM_TESTS_URL'], + '--qit_secret', + $_ENV['QIT_CUSTOM_TESTS_SECRET'], + '--environment', + $_ENV['QIT_CUSTOM_TESTS_ENV'], + ] ); + $add_environment->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] ); + $add_environment->mustRun( function ( $type, $buffer ) { + echo $buffer; + } ); + + if ( $_ENV['QIT_CUSTOM_TESTS_ENV'] !== 'staging' ) { + // Add the partner account that will be used. + $add_partner = new Process( [ + $GLOBALS['qit'], + 'partner:add', + '--user', + $_ENV['QIT_CUSTOM_TESTS_USER'], + '--qit_token', + $_ENV['QIT_CUSTOM_TESTS_USER_QIT_TOKEN'], + ] ); + $add_partner->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] ); + $add_partner->mustRun( function ( $type, $buffer ) { + echo $buffer; + } ); + } else { + echo "Skipping partner add for staging environment\n"; + } + + // Validate connection. Run "qit extensions" and assert "18734003134382" is present, which is the ID of the "woocommerce" extension. + $extensions = new Process( [ $GLOBALS['qit'], 'extensions' ] ); + $extensions->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] ); + $extensions->mustRun(); + + $woocommerce_ids_per_environment = [ + 'staging' => 18734003134382, + 'local' => 18734002449992, + 'production' => 18734002449992, + ]; + + $woocommerce_id = $woocommerce_ids_per_environment[ $_ENV['QIT_CUSTOM_TESTS_ENV'] ]; + + if ( strpos( $extensions->getOutput(), $woocommerce_id ) === false ) { + if ( ! fwrite( $lock_file, 'failed' ) ) { + throw new \RuntimeException( 'Failed to write to lock file.' ); + } + } else { + if ( ! fwrite( $lock_file, $GLOBALS['QIT_HOME'] ) ) { + throw new \RuntimeException( 'Failed to write to lock file.' ); + } + } + + if ( ! file_exists( __DIR__ . '/cache' ) ) { + mkdir( __DIR__ . '/cache' ); + } + + if ( ! file_exists( __DIR__ . '/tmp' ) ) { + mkdir( __DIR__ . '/tmp' ); + } + + $fs->mirror( __DIR__ . '/cache', $GLOBALS['QIT_HOME'] . '/cache' ); + + $GLOBALS['IS_SOURCE'] = true; + + echo "Main process has authenticated and is releasing the lock...\n"; + + flock( $lock_file, LOCK_UN ); + fclose( $lock_file ); + + // Wait after unlocking so that other processes can copy this source directory. + sleep( 10 ); + } else { + if ( ! touch( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ) ) { + throw new \RuntimeException( 'Failed to create running file at ' . sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ); + } + + // If no exclusive lock is available, block until the first process is done with initialization + flock( $lock_file, LOCK_SH ); + + echo sprintf( "Process %s proceeding after the lock is released\n", getenv( 'TEST_TOKEN' ) ); + + // Read the contents of the lock file to get the QIT_HOME directory. + $source_qit_home = fread( $lock_file, 1024 ); + fclose( $lock_file ); + + if ( $source_qit_home === 'failed' ) { + throw new \RuntimeException( 'Bailing because it failed to initialize.' ); + } + + if ( ! file_exists( $source_qit_home ) ) { + throw new \RuntimeException( sprintf( 'The QIT_HOME directory "%s" does not exist.', $source_qit_home ) ); + } + + $GLOBALS['QIT_SOURCE_DIR'] = $source_qit_home; + + // Mirror the directory. + $fs->mirror( $source_qit_home, $GLOBALS['QIT_HOME'] ); + } + + // Make sure each parallel process is spaced out a little bit. + $semaphore = sys_get_temp_dir() . '/qit-semaphore.log'; + $fp = fopen( $semaphore, 'c+' ); + if ( ! $fp ) { + throw new \RuntimeException( "Failed to open semaphore file. $semaphore" ); + } + $start = microtime( true ); + if ( flock( $fp, LOCK_EX ) ) { + if ( empty( fread( $fp, 1 ) ) ) { + // First process. + file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s] First process\n", getenv( 'TEST_TOKEN' ), microtime() ) ); + } else { + // Lock acquired, now wait 5 seconds between each request. + $sleep = 5000000; + file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s], Sleeping for $sleep microseconds\n", getenv( 'TEST_TOKEN' ), microtime() ), FILE_APPEND ); + usleep( $sleep ); + file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s] Finished sleeping (Waited %s total)\n", getenv( 'TEST_TOKEN' ), microtime(), microtime( true ) - $start ), FILE_APPEND ); + } + flock( $fp, LOCK_UN ); + } + + fclose( $fp ); + } +} + +class QITTestFinish implements ExecutionFinishedSubscriber { + public function notify( ExecutionFinished $event ): void { + if ( getenv( 'CI' ) ) { + echo "Skipping cleanup because this is a CI environment.\n"; + } + + if ( ! isset( $GLOBALS['RUN_ID'] ) ) { + throw new \RuntimeException( 'The "RUN_ID" GLOBAL must be set.' ); + } + + if ( file_exists( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ) ) { + unlink( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ); + } + + self::delete_temp_environment(); + } + + public static function delete_temp_environment(): void { + if ( empty( $GLOBALS['QIT_HOME'] ) || empty( $GLOBALS['qit'] ) ) { + throw new \LogicException( 'The "QIT_HOME" and "qit" GLOBALS must be set.' ); + } + + $fs = new Filesystem(); + + // Check that $GLOBALS['QIT_HOME'] is inside this directory. + if ( strpos( $GLOBALS['QIT_HOME'], __DIR__ ) !== 0 ) { + throw new \RuntimeException( sprintf( 'The QIT_HOME directory is not inside the test directory. This is a security risk. QIT_HOME: %s, Test Directory: %s', $GLOBALS['QIT_HOME'], __DIR__ ) ); + } + + if ( file_exists( __DIR__ . '/qit-env.json' ) ) { + unlink( __DIR__ . '/qit-env.json' ); + } + + if ( $fs->exists( $GLOBALS['QIT_HOME'] ) ) { + // Copy files from $GLOBALS['QIT_HOME'] . '/cache' to __DIR__. '/cache' + if ( file_exists( $GLOBALS['QIT_HOME'] . '/cache' ) ) { + $fs->mirror( $GLOBALS['QIT_HOME'] . '/cache', __DIR__ . '/cache' ); + } + + if ( isset( $GLOBALS['IS_SOURCE'] ) && $GLOBALS['IS_SOURCE'] ) { + $timeout = 300; + // Wait for all other tests to finish. + while ( count( glob( sys_get_temp_dir() . '/qit-running-*' ) ) > 1 ) { + echo sprintf( "Main process is waiting for %d other tests to finish.\n", count( glob( sys_get_temp_dir() . '/qit-running-*' ) ) - 1 ); + sleep( 5 ); + $timeout -= 5; + if ( $timeout <= 0 ) { + throw new \RuntimeException( 'Timeout waiting for other tests to finish.' ); + } + } + } + $fs->remove( $GLOBALS['QIT_HOME'] ); + } + } +} \ No newline at end of file diff --git a/_tests/custom_tests/README.md b/_tests/custom_tests/README.md new file mode 100644 index 00000000..c1178a92 --- /dev/null +++ b/_tests/custom_tests/README.md @@ -0,0 +1,24 @@ +### Running tests + +1. Copy `.env.example` to `.env` and update the values +2. Run `./vendor/bin/paratest` + +### Updating snapshots + +1. Append "UPDATE_SNAPSHOTS" env var, eg: `UPDATE_SNAPSHOTS=true ./vendor/bin/paratest` + +### How it works + +1. The tests are regular PHPUnit tests +2. The tests run in parallel using [ParaTest](https://github.com/paratestphp/paratest) +3. This is because each test might spin up a QIT environment, which takes some time +4. If the tests were run sequentially, it would take a long time to run all the tests +5. For each test, we: + - Assign a uniquely generated QIT_HOME + - The first test that runs will acquire a exclusive file lock on a file + - All other tests when seeing that there is a file lock on a file will wait for that lock to be released + - The first test will authenticate to QIT and release the lock + - All other tests will then reuse the same QIT baseline authentication JSON + - Then all tests starts in parallel, with a shared, persistent cache. Ideally, all operations would be offline, without touching the internet + - This allows us to write more and more tests without increasing the time linearly + - After the test ends, each QIT_HOME that was generated is deleted \ No newline at end of file diff --git a/_tests/custom_tests/bootstrap.php b/_tests/custom_tests/bootstrap.php new file mode 100644 index 00000000..e895e5eb --- /dev/null +++ b/_tests/custom_tests/bootstrap.php @@ -0,0 +1,80 @@ +scaffold_test(), + * ] ); + * Lock "automatewoo:self-test-scaffolded" + * When we receive qit( [ 'tag:delete', 'automatewoo:self-test-scaffolded' ] ) we unlock it. + * + * If we receive another command like the previous one while it's locked, wait. + */ + if ( 'tag:upload' === $command[0] ) { + $lock_name = $command[1]; + $lock_file = sprintf( '%s/qit-test-tag-lock-%s', sys_get_temp_dir(), md5( $lock_name ) ); + $max_wait = 60; + $waited = 0; + while ( file_exists( $lock_file ) ) { + sleep( 1 ); + $waited ++; + if ( $waited > $max_wait ) { + throw new \RuntimeException( sprintf( 'Timeout while waiting for the lock for %s', $lock_name ) ); + } + } + touch( $lock_file ); + } elseif ( 'tag:delete' === $command[0] ) { + $lock_name = $command[1]; + $lock_file = sprintf( '%s/qit-test-tag-lock-%s', sys_get_temp_dir(), md5( $lock_name ) ); + if ( file_exists( $lock_file ) ) { + unlink( $lock_file ); + } + } + + $args = [ $GLOBALS['qit'] ]; + $args = array_merge( $args, $command ); + if ( ! empty( $qit_env_json ) ) { + $args[] = '--config'; + $args[] = $qit_config_filename; + } + $qit = new Process( $args ); + $qit->setTimeout( 300 ); + $qit->setIdleTimeout( 300 ); + $qit->setTty( false ); + $qit->setPty( false ); + $qit->setEnv( [ + 'QIT_HOME' => $GLOBALS['QIT_HOME'], + 'QIT_DISABLE_CLEANUP' => '1', // We need to disable it because of parallelization with individualized QIT_HOMEs. + 'QIT_SELF_TESTS' => '1', + 'CI' => '1', + 'COLUMNS' => '300', // Set a fixed width so that we can snapshot the output. + ] ); + $qit->run(); + + if ( $qit->getExitCode() !== $expected_exit_code ) { + throw new \RuntimeException( sprintf( "Command \"%s\" failed with exit code %d. \n\nError Output:\n %s \n\nOutput:\n %s", implode( ' ', $command ), $qit->getExitCode(), $qit->getErrorOutput(), $qit->getOutput() ) ); + } + + return $qit->getOutput(); +} \ No newline at end of file diff --git a/_tests/custom_tests/composer.json b/_tests/custom_tests/composer.json new file mode 100644 index 00000000..646b8834 --- /dev/null +++ b/_tests/custom_tests/composer.json @@ -0,0 +1,26 @@ +{ + "config": { + "platform": { + "php": "8.2" + } + }, + "require": { + "php": "^8" + }, + "autoload": { + "psr-4": { + "QIT\\SelfTests\\CustomTests\\": "tests" + }, + "files": [ + "QITTestExtension.php" + ] + }, + "require-dev": { + "phpunit/phpunit": "^11", + "spatie/phpunit-snapshot-assertions": "^5.0", + "symfony/process": "^7", + "symfony/filesystem": "^7", + "vlucas/phpdotenv": "^5", + "brianium/paratest": "^7" + } +} diff --git a/_tests/custom_tests/composer.lock b/_tests/custom_tests/composer.lock new file mode 100644 index 00000000..48c48ea2 --- /dev/null +++ b/_tests/custom_tests/composer.lock @@ -0,0 +1,3328 @@ +{ + "_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": "d650fa6759bc5a32508668df20938212", + "packages": [], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.4.3", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.1.0", + "jean85/pretty-package-versions": "^2.0.5", + "php": "~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", + "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", + "phpunit/php-timer": "^6.0.0 || ^7.0.0", + "phpunit/phpunit": "^10.5.9 || ^11.0.3", + "sebastian/environment": "^6.0.1 || ^7.0.0", + "symfony/console": "^6.4.3 || ^7.0.3", + "symfony/process": "^6.4.3 || ^7.0.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^1.10.58", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "squizlabs/php_codesniffer": "^3.9.0", + "symfony/filesystem": "^6.4.3 || ^7.0.3" + }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2024-02-20T07:24:02+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:16:48+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + }, + "time": "2024-03-08T09:58:59+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "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", + "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.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-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.0.2" + }, + "time": "2024-03-05T20:51:40+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": "phpoption/phpoption", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-11-12T21:59:55+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e35a2cbcabac0e6865fd373742ea432a3c34f92", + "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "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": "11.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 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/11.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-12T15:35:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/99e95c94ad9500daca992354fa09d7b99abe2210", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:05:04+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "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": "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:05:50+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/d38f6cbff1cdb6f40b03c9811421561668cc133e", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:06:56+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8a59d9e25720482ee7fcdf296595e08795b84dc5", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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", + "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/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:08:01+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "d475be032238173ca3b0a516f5cc291d174708ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d475be032238173ca3b0a516f5cc291d174708ae", + "reference": "d475be032238173ca3b0a516f5cc291d174708ae", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-invoker": "^5.0", + "phpunit/php-text-template": "^4.0", + "phpunit/php-timer": "^7.0", + "sebastian/cli-parser": "^3.0", + "sebastian/code-unit": "^3.0", + "sebastian/comparator": "^6.0", + "sebastian/diff": "^6.0", + "sebastian/environment": "^7.0", + "sebastian/exporter": "^6.0", + "sebastian/global-state": "^7.0", + "sebastian/object-enumerator": "^6.0", + "sebastian/type": "^5.0", + "sebastian/version": "^5.0" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.1-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.1.3" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-04-24T06:34:25+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/00a74d5568694711f0222e54fb281e1d15fdf04a", + "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.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 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/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:26:58+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6634549cb8d702282a04a774e36a7477d2bd9015", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.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 PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:50:41+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:52:17+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:53:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "88a434ad86150e11a606ac4866b09130712671f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/88a434ad86150e11a606ac4866b09130712671f0", + "reference": "88a434ad86150e11a606ac4866b09130712671f0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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 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/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:55:19+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ab83243ecc233de5655b76f577711de9f842e712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712", + "reference": "ab83243ecc233de5655b76f577711de9f842e712", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "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" + }, + { + "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/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:30:33+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4eb3a442574d0e9d141aab209cd4aaf25701b09a", + "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "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" + } + ], + "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/7.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:56:34+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f291e5a317c321c0381fa9ecc796fa2d21b186da", + "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:28:20+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c3a307e832f2e69c7ef869e31fc644fde0e7cb3e", + "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.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": "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/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:32:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/376c5b3f6b43c78fdc049740bca76a7c846706c0", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.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/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:00:36+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:01:29+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bb2a6255d30853425fd38f032eb64ced9f7f132d", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "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/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:02:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:08:48+00:00" + }, + { + "name": "sebastian/type", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8502785eb3523ca0dd4afe9ca62235590020f3f", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:09:34+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/13999475d2cb1ab33cb73403ba356a814fdbb001", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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 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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:10:47+00:00" + }, + { + "name": "spatie/phpunit-snapshot-assertions", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/spatie/phpunit-snapshot-assertions.git", + "reference": "fe84650c4cfb8f51ecf5146f4089369fafea2c94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/phpunit-snapshot-assertions/zipball/fe84650c4cfb8f51ecf5146f4089369fafea2c94", + "reference": "fe84650c4cfb8f51ecf5146f4089369fafea2c94", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "php": "^8.1", + "phpunit/phpunit": "^10.0|^11.0", + "symfony/property-access": "^5.2|^6.2|^7.0", + "symfony/serializer": "^5.2|^6.2|^7.0", + "symfony/yaml": "^5.2|^6.2|^7.0" + }, + "require-dev": { + "spatie/pixelmatch-php": "dev-main", + "spatie/ray": "^1.37" + }, + "suggest": { + "spatie/pixelmatch-php": "Required to use the image snapshot assertions" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-v5": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Snapshots\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Snapshot testing with PHPUnit", + "homepage": "https://github.com/spatie/phpunit-snapshot-assertions", + "keywords": [ + "assert", + "phpunit", + "phpunit-snapshot-assertions", + "snapshot", + "spatie", + "testing" + ], + "support": { + "issues": "https://github.com/spatie/phpunit-snapshot-assertions/issues", + "source": "https://github.com/spatie/phpunit-snapshot-assertions/tree/5.1.5" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2024-02-20T10:04:25+00:00" + }, + { + "name": "symfony/console", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5", + "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-01T11:04:53+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d", + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-21T19:37:36+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/property-access", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "1c268ba954ccc5e78cf035b391abb67759e24423" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/1c268ba954ccc5e78cf035b391abb67759e24423", + "reference": "1c268ba954ccc5e78cf035b391abb67759e24423", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-19T11:57:22+00:00" + }, + { + "name": "symfony/property-info", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "b8844ddce7d53f78b57ec9be59da80fceddf3167" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/b8844ddce7d53f78b57ec9be59da80fceddf3167", + "reference": "b8844ddce7d53f78b57ec9be59da80fceddf3167", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-28T09:20:36+00:00" + }, + { + "name": "symfony/serializer", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "dbdc0c04c28ac53de1fa35f92fca26e9b1345d98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/dbdc0c04c28ac53de1fa35f92fca26e9b1345d98", + "reference": "dbdc0c04c28ac53de1fa35f92fca26e9b1345d98", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-28T09:20:36+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e", + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-19T21:51:00+00:00" + }, + { + "name": "symfony/string", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-01T13:17:36+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T15:02:46+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" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:43:29+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8" + }, + "platform-dev": [], + "platform-overrides": { + "php": "8.2" + }, + "plugin-api-version": "2.6.0" +} diff --git a/_tests/custom_tests/phpunit.xml.dist b/_tests/custom_tests/phpunit.xml.dist new file mode 100644 index 00000000..e9a0115d --- /dev/null +++ b/_tests/custom_tests/phpunit.xml.dist @@ -0,0 +1,16 @@ + + + + + + + + tests + + + diff --git a/_tests/custom_tests/tests/BasicTest.php b/_tests/custom_tests/tests/BasicTest.php new file mode 100644 index 00000000..e05704f5 --- /dev/null +++ b/_tests/custom_tests/tests/BasicTest.php @@ -0,0 +1,15 @@ +assertTrue( true ); + } + + public function test_run_e2e_exists() { + qit( [ 'run:e2e', '--help' ] ); + // If we got here, it means the command ran successfully. + $this->assertTrue( true ); + } +} \ No newline at end of file diff --git a/_tests/custom_tests/tests/CompatibilityTest.php b/_tests/custom_tests/tests/CompatibilityTest.php new file mode 100644 index 00000000..4a896fca --- /dev/null +++ b/_tests/custom_tests/tests/CompatibilityTest.php @@ -0,0 +1,120 @@ +scaffold_test(), + '--plugin', + 'woocommerce:activate', + ] + ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_sut_and_bootstrap_additional() { + // Upload Woo Test Tag. + qit( [ + 'tag:upload', + 'woocommerce:self-test-bootstrap-additional', + $this->scaffold_test(), + ] + ); + + // Run AutomateWoo, bootstrapping Woo. + $output = qit( [ + 'run:e2e', + 'automatewoo', + $this->scaffold_test(), + '--plugin', + 'woocommerce:bootstrap:self-test-bootstrap-additional', + ] + ); + + // Cleanup. + qit( [ 'tag:delete', 'woocommerce:self-test-bootstrap-additional' ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_sut_and_test_additional() { + // Upload Woo Test Tag. + qit( [ + 'tag:upload', + 'woocommerce:self-test-sut-and-test-additional', + $this->scaffold_test(), + ] + ); + + // Run AutomateWoo, bootstrapping Woo. + $output = qit( [ + 'run:e2e', + 'automatewoo', + $this->scaffold_test(), + '--plugin', + 'woocommerce:test:self-test-sut-and-test-additional', + ] + ); + + // Cleanup. + qit( [ 'tag:delete', 'woocommerce:self-test-sut-and-test-additional' ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_multiple_tags_and_multiple_plugins_with_multiple_tags() { + qit( [ + 'tag:upload', + 'automatewoo:self-test-multiple-test-tags', + $this->scaffold_test(), + ] ); + + qit( [ + 'tag:upload', + 'automatewoo:self-test-multiple-test-tags-another', + $this->scaffold_test( 'another-tag' ), + ] ); + + qit( [ + 'tag:upload', + 'woocommerce:self-test-multiple-test-tags', + $this->scaffold_test(), + ] ); + + qit( [ + 'tag:upload', + 'woocommerce:self-test-multiple-test-tags-another', + $this->scaffold_test( 'another-tag' ), + ] ); + + $output = qit( [ + 'run:e2e', + 'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another', + '--plugin', + 'woocommerce:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another', + ] ); + + qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags' ] ); + qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags-another' ] ); + qit( [ 'tag:delete', 'woocommerce:self-test-multiple-test-tags' ] ); + qit( [ 'tag:delete', 'woocommerce:self-test-multiple-test-tags-another' ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } +} \ No newline at end of file diff --git a/_tests/custom_tests/tests/EnvTest.php b/_tests/custom_tests/tests/EnvTest.php new file mode 100644 index 00000000..a75d8f74 --- /dev/null +++ b/_tests/custom_tests/tests/EnvTest.php @@ -0,0 +1,315 @@ + "WordPress Version: 6.5.2-normalized" + $normalizedOutput = preg_replace( '/WordPress Version: .+/', 'WordPress Version: NORMALIZED', $normalizedOutput ); + + $this->assertMatchesNormalizedSnapshot( $normalizedOutput ); + } + + public function test_env_up_with_parameters() { + $output = qit( [ + 'env:up', + '--wp', + '6.5', + '--php_version', + '8.3', + ] + ); + + // Check that WordPress Version is as expected: + $this->assertStringContainsString( 'WordPress Version: 6.5', $output ); + + // Check that PHP Version is as expected: + $this->assertStringContainsString( 'PHP Version: 8.3', $output ); + } + + public function test_env_up_with_object_cache() { + $output = qit( [ + 'env:up', + '--object_cache', + ] + ); + + $this->assertStringContainsString( 'Redis Object Cache? Yes', $output ); + } + + public function test_env_up_with_file() { + $output = qit( [ 'env:up' ], [ + 'wp' => '6.4', + 'php_version' => '8.2', + ] ); + + // Check that WordPress Version is as expected: + $this->assertStringContainsString( 'WordPress Version: 6.4', $output ); + + // Check that PHP Version is as expected: + $this->assertStringContainsString( 'PHP Version: 8.2', $output ); + } + + public function test_env_up_with_file_and_parameters() { + $output = qit( [ 'env:up' ], [ + 'wp' => '6.4', + 'php_version' => '8.3', + ] ); + + // Check that WordPress Version is as expected: + $this->assertStringContainsString( 'WordPress Version: 6.4', $output ); + + // Check that PHP Version is as expected: + $this->assertStringContainsString( 'PHP Version: 8.3', $output ); + } + + public function test_env_up_with_plugins() { + $json = json_decode( qit( [ 'env:up', '--json' ], [ + 'plugins' => [ + 'automatewoo' => [ + 'action' => 'activate', + ], + 'woocommerce' => [ + 'action' => 'activate', + ], + ], + ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp plugin list', + ] ); + + /** + * name status update version update_version auto_update + * automatewoo active none 6.0.20 off + * woocommerce active available 8.7.0.20 8.8.2 off + * qit-wp-cli must-use none off + * wp-cli-github-cache must-use none off + */ + + // Iterate over each line, for the "automatewoo" and "woocommerce", normalize the version. + $lines = explode( "\n", $output ); + $headers = preg_split( '/\s+/', trim( $lines[0] ) ); // Split the header to find the index of 'version' + $version_index = array_search( 'version', $headers ); // Locate the index of the 'version' column + $update_version_index = array_search( 'update_version', $headers ); // Locate the index of the 'update_version' column + $update_index = array_search( 'update', $headers ); // Locate the index of the 'update' column + + foreach ( $lines as $key => $line ) { + if ( strpos( $line, 'automatewoo' ) !== false || strpos( $line, 'woocommerce' ) !== false ) { + $parts = preg_split( '/\s+/', trim( $line ) ); + $parts[ $version_index ] = 'NORMALIZED_VERSION'; + $parts[ $update_version_index ] = 'NORMALIZED_VERSION'; + $parts[ $update_index ] = 'NORMALIZED'; + $lines[ $key ] = implode( ' ', $parts ); + } + } + $output = implode( "\n", $lines ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_with_additional_volumes() { + if ( file_exists( sys_get_temp_dir() . '/qit-tmp-plugin.php' ) && ! unlink( sys_get_temp_dir() . '/qit-tmp-plugin.php' ) ) { + throw new \RuntimeException( 'Could not delete the temporary file.' ); + } + + file_put_contents( sys_get_temp_dir() . '/qit-tmp-plugin.php', + <<<'PHP' +assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_wordpress_stable_version() { + $json = json_decode( qit( [ 'env:up', '--json', '--wp', 'stable' ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp core check-update --force-check', + ] ); + + $this->assertStringContainsString( 'WordPress is at the latest version', $output ); + } + + public function test_env_up_wordpress_nightly_version() { + $json = json_decode( qit( [ 'env:up', '--json', '--wp', 'nightly' ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp core version', + ] ); + + // Preg match "6.6-alpha-58052" + $version_parts = explode( '-', $output ); + $this->assertEquals( 3, count( $version_parts ) ); + $this->assertEquals( 'alpha', $version_parts[1] ); + $this->assertIsNumeric( $version_parts[2] ); + } + + public function test_env_up_woocommerce_stable_version() { + $json = json_decode( qit( [ 'env:up', '--json', '--woo', 'stable', ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp plugin update woocommerce', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_woocommerce_stable_version_alternative_syntax() { + $json = json_decode( qit( [ 'env:up', '--json', '--plugin', 'woocommerce', ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp plugin update woocommerce', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_woocommerce_nightly_version() { + $json = json_decode( qit( [ + 'env:up', + '--json', + '--woo', + 'nightly', + ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp plugin get woocommerce', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_woocommerce_rc_version() { + $this->markTestSkipped(); + $json = json_decode( qit( [ + 'env:up', + '--json', + '--woo', + 'rc', + '--plugin', + 'https://github.com/woocommerce/woocommerce/releases/download/wc-beta-tester-2.3.0/woocommerce-beta-tester.zip:activate', + ] ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp plugin update woocommerce', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_with_additional_php_extensions() { + $json = json_decode( qit( [ + 'env:up', + '--json', + '--php_extension', + 'gd', + ] + ), true ); + + $output = qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'php -m | grep gd', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_env_up_with_additional_themes() { + $json = json_decode( qit( [ + 'env:up', + '--json', + '--theme', + 'storefront', + '--theme', + 'twentyseventeen', + ] + ), true ); + + $output = ''; + + $output .= qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp theme get storefront --fields=name,status', + ] ); + + $output .= "\n"; + + $output .= qit( [ + 'env:exec', + '--env_id', + $json['env_id'], + 'wp theme get twentyseventeen --fields=name,status', + ] ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } +} \ No newline at end of file diff --git a/_tests/custom_tests/tests/RunE2ETest.php b/_tests/custom_tests/tests/RunE2ETest.php new file mode 100644 index 00000000..13f0c7fc --- /dev/null +++ b/_tests/custom_tests/tests/RunE2ETest.php @@ -0,0 +1,207 @@ +scaffold_test(), + ], + [], + 1 + ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_runs_scaffolded_e2e() { + $output = qit( [ + 'run:e2e', + 'automatewoo', + $this->scaffold_test(), + '--plugin', + 'woocommerce:activate', + ] + ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_tag_and_run_test() { + qit( [ + 'tag:upload', + 'automatewoo:self-test-tag-and-run', + $this->scaffold_test(), + ] ); + + $output = qit( [ + 'run:e2e', + 'automatewoo:test:self-test-tag-and-run', + '--plugin', + 'woocommerce:activate', + ] ); + + qit( [ 'tag:delete', 'automatewoo:self-test-tag-and-run' ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_multiple_tags_and_run_tests() { + qit( [ + 'tag:upload', + 'automatewoo:self-test-multiple-test-tags', + $this->scaffold_test(), + ] ); + + qit( [ + 'tag:upload', + 'automatewoo:self-test-multiple-test-tags-another', + $this->scaffold_test( 'another-tag' ), + ] ); + + $output = qit( [ + 'run:e2e', + 'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another', + '--plugin', + 'woocommerce:activate', + ] ); + + qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags' ] ); + qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags-another' ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_theme_as_sut() { + // Scaffold. + $scaffolded_dir = $this->scaffold_test(); + + $activate_theme_test = <<<'JS' +import { test, expect } from '@playwright/test'; +import qit from '/qitHelpers'; + +test('I can activate Deli', async ({ page }) => { + await qit.loginAsAdmin(page); + await page.getByRole('link', { name: 'Appearance' }).click(); + await expect(page.getByRole('cell', { name: 'Deli' })).toBeVisible(); + await page.getByRole('link', { name: 'Install Parent Theme' }).click(); + await page.getByRole('link', { name: 'Activate “Storefront”' }).click(); + await page.getByLabel('Activate Deli').click(); + await page.goto('/'); +}); +JS; + + // Create a new test that will activate the theme. + if ( ! file_put_contents( $scaffolded_dir . '/activate-theme.spec.js', $activate_theme_test ) ) { + throw new \RuntimeException( 'Failed to create the scaffolded test file.' ); + + } + + // Run. + $output = qit( [ + 'run:e2e', + 'deli', + $scaffolded_dir, + '--testing_theme', + ] ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + $this->assertMatchesNormalizedSnapshot( $output ); + } + + public function test_run_with_snapshot() { + // Scaffold. + $scaffolded_dir = $this->scaffold_test(); + + $activate_theme_test = <<<'JS' +import { test, expect } from '@playwright/test'; +import qit from '/qitHelpers'; + +test('I can activate Deli', async ({ page }) => { + await qit.loginAsAdmin(page); + await page.getByRole('link', { name: 'Appearance' }).click(); + await expect(page.getByRole('cell', { name: 'Deli' })).toBeVisible(); + await page.getByRole('link', { name: 'Install Parent Theme' }).click(); + await page.getByRole('link', { name: 'Activate “Storefront”' }).click(); + await page.getByLabel('Activate Deli').click(); + await page.goto('/'); + await expect(page).toHaveScreenshot('home.png', { maxDiffPixels: 100 }); +}); +JS; + + // Create a new test that will activate the theme. + if ( ! file_put_contents( $scaffolded_dir . '/activate-theme.spec.js', $activate_theme_test ) ) { + throw new \RuntimeException( 'Failed to create the scaffolded test file.' ); + } + + $this->assertFileDoesNotExist( $scaffolded_dir . '/__snapshots__' ); + + // Run the first time to generate snapshots. + $output = qit( [ + 'run:e2e', + 'deli', + $scaffolded_dir, + '--testing_theme', + '--update_snapshots', + ] ); + + $this->assertFileExists( $scaffolded_dir . '/__snapshots__' ); + $this->assertMatchesNormalizedSnapshot( $this->normalize_scaffolded_test_run_output( $output ) ); + + // Run the second time to validate snapshot. + $output = qit( [ + 'run:e2e', + 'deli', + $scaffolded_dir, + '--testing_theme', + ] ); + + $this->assertMatchesNormalizedSnapshot( $this->normalize_scaffolded_test_run_output( $output ) ); + } + + public function test_playwright_config_override() { + $output = qit( [ + 'run:e2e', + 'automatewoo', + $this->scaffold_test(), + '--plugin', + 'woocommerce:activate', + ], [ + 'playwright_config' => [ + 'reportSlowTests' => [ + 'max' => 10, + 'threshold' => 1, + ], + ], + ] + ); + + $output = $this->normalize_scaffolded_test_run_output( $output ); + + // "Loading environment config from override parameter /tmp/qit-env-97d237784cddc7ec1341113ca364110d.json..." Normalize "97d237784cddc7ec1341113ca364110d". + $output = preg_replace( '/qit-env-[a-f0-9]{32}/', 'qit-env-', $output ); + + // "Slow test file: [automatewoo-local] › automatewoo/local/example.spec.js (7.1s)" Normalize "7.1s". + $output = preg_replace( '/\d+\.\d+s/', '