diff --git a/.gitattributes b/.gitattributes index 3dc7af676..2a808429e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ build/ export-ignore docs/ export-ignore examples/ export-ignore +scripts/ export-ignore tests/ export-ignore .codecov.yml export-ignore .editorconfig export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f1ac8782f..5b6aafacf 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -99,8 +99,8 @@ Code coverage is monitored for every PR and for the code base as a whole using [ - [PHP][] >= 5.6 - [Composer][] -- [Python 3][] -- [mitmproxy][] (`pip3 install mitmproxy`) +- [Python 3][] >= 3.12 +- [mitmproxy][] >= 11.0.2 (`pip3 install mitmproxy`) [PHP]: https://www.php.net/ [Composer]: http://getcomposer.org/ @@ -109,28 +109,65 @@ Code coverage is monitored for every PR and for the code base as a whole using [ ### Running the Tests +There are several ways to run the tests locally: + +#### 1. Using Composer Scripts (Recommended) + +The simplest way to run tests is using the Composer scripts: + +```bash +# Run tests without coverage +composer test + +# Run tests with coverage +composer coverage + +# Run tests with automatic test server management +composer test:withserver +``` + +#### 2. Manual Test Environment Management + +If you need more control over the test environment, you can manage it manually: + +```bash +# Start all test servers and set environment variables +# We need to source the script to properly set environment variables in our shell +source scripts/start-test-environment.sh + +# Now run your tests +composer test + +# When done, stop all servers +./scripts/stop-test-environment.sh +``` + +Note: The environment scripts must be sourced (using `source` or `.`) to properly set environment variables in your shell. + +#### 3. Individual Component Control + +For debugging or development, you might want to manage individual components: + ```bash -# Start the test server +# Start only the test server PORT=8080 vendor/bin/start.sh -export "REQUESTS_TEST_HOST_HTTP=localhost:8080" +export REQUESTS_TEST_HOST_HTTP=localhost:8080 -# Start the proxy server +# Start only the proxy servers PORT=9002 tests/utils/proxy/start.sh PORT=9003 AUTH="test:pass" tests/utils/proxy/start.sh -export "REQUESTS_HTTP_PROXY=localhost:9002" -export "REQUESTS_HTTP_PROXY_AUTH=localhost:9003" -export "REQUESTS_HTTP_PROXY_AUTH_USER=test" -export "REQUESTS_HTTP_PROXY_AUTH_PASS=pass" +export REQUESTS_HTTP_PROXY=localhost:9002 +export REQUESTS_HTTP_PROXY_AUTH=localhost:9003 +export REQUESTS_HTTP_PROXY_AUTH_USER=test +export REQUESTS_HTTP_PROXY_AUTH_PASS=pass +``` -# Run the tests -composer test +Remember to stop any servers you start: -# Stop the proxy server -PORT=9002 tests/utils/proxy/stop.sh +```bash +vendor/bin/stop.sh # Stop test server +PORT=9002 tests/utils/proxy/stop.sh # Stop proxy servers PORT=9003 tests/utils/proxy/stop.sh - -# Stop the test server -vendor/bin/stop.sh ``` To run the test with code coverage, use `composer coverage` instead. diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index fae653090..dbfdb4f7c 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -90,18 +90,9 @@ jobs: - name: Access localhost on port 9002 run: curl -i http://localhost:9002 - - name: Grab PHPUnit version - id: phpunit_version - run: echo "VERSION=$(vendor/bin/phpunit --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT" - - - name: Run the unit tests (PHPUnit < 10) - if: ${{ ! startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} + - name: Run the unit tests run: composer test - - name: Run the unit tests (PHPUnit 10+) - if: ${{ startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} - run: composer test10 - - name: Stop proxy server continue-on-error: true run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e04b5de2..7669ef610 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -114,26 +114,14 @@ jobs: - name: Access localhost on port 9002 run: curl -i http://localhost:9002 - - name: Grab PHPUnit version - id: phpunit_version - run: echo "VERSION=$(vendor/bin/phpunit --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT" - - - name: Run the unit tests, no code coverage (PHPUnit < 10) - if: ${{ matrix.coverage == false && ! startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} + - name: Run the unit tests, no code coverage + if: ${{ matrix.coverage == false }} run: composer test - - name: Run the unit tests, no code coverage (PHPUnit 10+) - if: ${{ matrix.coverage == false && startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} - run: composer test10 - - - name: Run the unit tests with code coverage (PHPUnit < 10) - if: ${{ matrix.coverage == true && ! startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} + - name: Run the unit tests with code coverage + if: ${{ matrix.coverage == true }} run: composer coverage -- --coverage-clover clover.xml - - name: Run the unit tests with code coverage (PHPUnit 10+) - if: ${{ matrix.coverage == true && startsWith( steps.phpunit_version.outputs.VERSION, '10.' ) }} - run: composer coverage10 -- --coverage-clover clover.xml - - name: Stop proxy server continue-on-error: true run: | diff --git a/.gitignore b/.gitignore index 8f4479dd1..bb57d438f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ certificates/etag-*.txt # Ignore temporary files generated by the testing proxy. tests/utils/proxy/__pycache__ tests/utils/proxy/*.pid +tests/utils/pids diff --git a/composer.json b/composer.json index 98477ced9..f5aca4cbb 100644 --- a/composer.json +++ b/composer.json @@ -87,25 +87,45 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], "test": [ - "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" - ], - "test10": [ - "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist --no-coverage" + "./scripts/run-phpunit.sh --no-coverage" ], "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit" + "./scripts/run-phpunit.sh" + ], + "testserver:start": [ + "./scripts/start-test-environment.sh" + ], + "testserver:stop": [ + "./scripts/stop-test-environment.sh" + ], + "test:withserver": [ + "@putenv REQUESTS_HTTP_PROXY=localhost:9002", + "@putenv REQUESTS_HTTP_PROXY_AUTH=localhost:9003", + "@putenv REQUESTS_HTTP_PROXY_AUTH_USER=test", + "@putenv REQUESTS_HTTP_PROXY_AUTH_PASS=pass", + "@testserver:start", + "@test", + "@testserver:stop" ], - "coverage10": [ - "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist" + "coverage:withserver": [ + "@putenv REQUESTS_HTTP_PROXY=localhost:9002", + "@putenv REQUESTS_HTTP_PROXY_AUTH=localhost:9003", + "@putenv REQUESTS_HTTP_PROXY_AUTH_USER=test", + "@putenv REQUESTS_HTTP_PROXY_AUTH_PASS=pass", + "@testserver:start", + "@coverage", + "@testserver:stop" ] }, "scripts-descriptions": { "lint": "Lint PHP files to find parse errors.", "checkcs": "Check the entire codebase for code-style issues.", "fixcs": "Fix all auto-fixable code-style issues in the entire codebase.", - "test": "Run the unit tests on PHPUnit 5.x - 9.x without code coverage.", - "test10": "Run the unit tests on PHPUnit 10.x without code coverage.", - "coverage": "Run the unit tests on PHPUnit 5.x - 9.x with code coverage.", - "coverage10": "Run the unit tests on PHPUnit 10.x with code coverage." + "test": "Run the unit tests without code coverage (auto-detects PHPUnit version)", + "coverage": "Run the unit tests with code coverage (auto-detects PHPUnit version)", + "testserver:start": "Start the test environment including test server and proxy servers (Note: environment variables will only be available in a sourced shell)", + "testserver:stop": "Stop all running test environment servers", + "test:withserver": "Start test servers, run PHPUnit tests with appropriate config, then stop servers", + "coverage:withserver": "Start test servers, run PHPUnit tests with code coverage, then stop servers" } } diff --git a/scripts/run-phpunit.sh b/scripts/run-phpunit.sh new file mode 100755 index 000000000..e147fd4e8 --- /dev/null +++ b/scripts/run-phpunit.sh @@ -0,0 +1,20 @@ +#!/bin/sh + + +# Set up PHPUnit command +# We're using composer exec to ensure we use the version of PHP that Composer +# is locked into, instead of the version of PHP that the system provides. +PHPUNIT="composer exec phpunit --" + +# Detect PHPUnit version +PHPUNIT_VERSION=$($PHPUNIT --version | grep -oE '[0-9]+\.[0-9]+' | head -n 1) + +# Determine config file based on version +if printf '%s\n%s\n' "10.0" "$PHPUNIT_VERSION" | sort -V -C 2>/dev/null; then + CONFIG_FILE="phpunit10.xml.dist" +else + CONFIG_FILE="phpunit.xml.dist" +fi + +# Run the tests +$PHPUNIT -c "$CONFIG_FILE" "$@" diff --git a/scripts/start-test-environment.sh b/scripts/start-test-environment.sh new file mode 100755 index 000000000..df0adad1d --- /dev/null +++ b/scripts/start-test-environment.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +# Try to determine script location +if [ -n "${BASH_SOURCE:-}" ]; then + # Bash-specific path resolution + # Shellcheck complains about POSIX arrays being unreferenced, but this bit + # conditionally run on Bash only. + # shellcheck disable=SC3054 + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +else + # POSIX fallback - assume we're in the repository root + PROJECT_ROOT="$(pwd)" + SCRIPT_DIR="$PROJECT_ROOT/scripts" +fi + +# Detect if we're being sourced (POSIX-compatible way) +sourced=0 +if [ -n "$ZSH_EVAL_CONTEXT" ]; then + case $ZSH_EVAL_CONTEXT in *:file:*) sourced=1;; esac +elif [ -n "$KSH_VERSION" ]; then + [ "$(cd $(dirname -- "$0") && pwd -P)/$(basename -- "$0")" != "$(cd $(dirname -- "${.sh.file}") && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1 +elif [ -n "$BASH_VERSION" ]; then + (return 0 2>/dev/null) && sourced=1 +else + # POSIX fallback - check if we can modify our environment + # Try to modify a test variable + TEST_VAR="test" + if [ -z "${TEST_VAR:-}" ]; then + sourced=0 + else + sourced=1 + fi +fi + +if [ $sourced -eq 0 ]; then + echo "Warning: This script should be sourced to set environment variables" + echo "Please either:" + echo "1. Source this script: source scripts/start-test-environment.sh" + echo "2. Or manually set these environment variables:" + echo " export REQUESTS_TEST_HOST_HTTP=localhost:8080" + echo " export REQUESTS_HTTP_PROXY=localhost:9002" + echo " export REQUESTS_HTTP_PROXY_AUTH=localhost:9003" + echo " export REQUESTS_HTTP_PROXY_AUTH_USER=test" + echo " export REQUESTS_HTTP_PROXY_AUTH_PASS=pass" + if [ -n "${BASH_VERSION:-}" ]; then + exit 1 + fi +fi + +PID_DIR="${PROJECT_ROOT}/tests/utils/pids" + +# Check if mitmproxy is installed +if ! command -v mitmdump >/dev/null 2>&1; then + echo "Error: mitmproxy is not installed. Please install it with: pip3 install mitmproxy" + return 1 2>/dev/null || exit 1 +fi + +# Get mitmproxy version and compare with minimum required +MITM_VERSION=$(mitmdump --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) +MINIMUM_VERSION="11.0.2" + +# POSIX-compatible version comparison +if ! printf '%s\n%s\n' "$MINIMUM_VERSION" "$MITM_VERSION" | sort -V -C 2>/dev/null; then + echo "Error: mitmproxy version $MITM_VERSION is too old" + echo "Please upgrade to version $MINIMUM_VERSION or newer with: pip3 install --upgrade mitmproxy" + return 1 2>/dev/null || exit 1 +fi + +echo "Found mitmproxy version $MITM_VERSION" + +# Create directory for PID files if it doesn't exist +mkdir -p "$PID_DIR" + +# Start the test server +echo "Starting test server..." +PORT=8080 "${PROJECT_ROOT}/vendor/bin/start.sh" +echo $! > "${PID_DIR}/test-server.pid" +REQUESTS_TEST_HOST_HTTP="localhost:8080" +export REQUESTS_TEST_HOST_HTTP + +# Start proxy servers +echo "Starting proxy servers..." +PORT=9002 "${PROJECT_ROOT}/tests/utils/proxy/start.sh" +echo $! > "${PID_DIR}/proxy-server.pid" + +PORT=9003 AUTH="test:pass" "${PROJECT_ROOT}/tests/utils/proxy/start.sh" +echo $! > "${PID_DIR}/proxy-auth-server.pid" + +# Set environment variables +REQUESTS_HTTP_PROXY="localhost:9002" +REQUESTS_HTTP_PROXY_AUTH="localhost:9003" +REQUESTS_HTTP_PROXY_AUTH_USER="test" +REQUESTS_HTTP_PROXY_AUTH_PASS="pass" + +export REQUESTS_HTTP_PROXY +export REQUESTS_HTTP_PROXY_AUTH +export REQUESTS_HTTP_PROXY_AUTH_USER +export REQUESTS_HTTP_PROXY_AUTH_PASS + +# Wait for servers to be ready +echo "Waiting for servers to be ready..." +sleep 2 + +# Test server connections +echo "Testing server connections..." +if ! curl -s -I http://localhost:8080 >/dev/null 2>&1; then + echo "Test server not responding" + return 1 2>/dev/null || exit 1 +fi + +if ! curl -s -I http://localhost:9002 >/dev/null 2>&1; then + echo "Proxy server not responding" + return 1 2>/dev/null || exit 1 +fi + +echo "Test environment is ready!" +echo "Environment variables set:" +echo "REQUESTS_TEST_HOST_HTTP=localhost:8080" +echo "REQUESTS_HTTP_PROXY=localhost:9002" +echo "REQUESTS_HTTP_PROXY_AUTH=localhost:9003" +echo "REQUESTS_HTTP_PROXY_AUTH_USER=test" +echo "REQUESTS_HTTP_PROXY_AUTH_PASS=pass" + +return 0 2>/dev/null || exit 0 diff --git a/scripts/stop-test-environment.sh b/scripts/stop-test-environment.sh new file mode 100755 index 000000000..279ec1edb --- /dev/null +++ b/scripts/stop-test-environment.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +# Try to determine script location +if [ -n "${BASH_SOURCE:-}" ]; then + # Bash-specific path resolution + # Shellcheck complains about POSIX arrays being unreferenced, but this bit + # conditionally run on Bash only. + # shellcheck disable=SC3054 + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +else + # POSIX fallback - assume we're in the repository root + PROJECT_ROOT="$(pwd)" + SCRIPT_DIR="$PROJECT_ROOT/scripts" +fi + +PID_DIR="${PROJECT_ROOT}/tests/utils/pids" + +# Function to safely kill a process +kill_process() { + pid_file="$1" + if [ -f "$pid_file" ]; then + pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "Stopping process $pid" + kill "$pid" + rm "$pid_file" + else + echo "Process $pid not running" + rm "$pid_file" + fi + fi +} + +# Stop all servers +echo "Stopping test environment..." + +# Stop test server +kill_process "${PID_DIR}/test-server.pid" +"${PROJECT_ROOT}/vendor/bin/stop.sh" + +# Stop proxy servers +PORT=9002 "${PROJECT_ROOT}/tests/utils/proxy/stop.sh" +kill_process "${PID_DIR}/proxy-server.pid" + +PORT=9003 "${PROJECT_ROOT}/tests/utils/proxy/stop.sh" +kill_process "${PID_DIR}/proxy-auth-server.pid" + +# Clean up PID directory if empty +rmdir "${PID_DIR}" 2>/dev/null || true + +echo "Test environment stopped" + +return 0 2>/dev/null || exit 0