Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions .github/workflows/mysql-server-result-suite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
name: MySQL Server Result Suite

on:
push:
branches:
- trunk
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Disable permissions for all available scopes by default.
# Any needed permissions should be configured at the job level.
permissions: {}

jobs:
test:
name: MySQL server result suite
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
extensions: sockets, pdo_sqlite
tools: phpunit-polyfills

- name: Install Composer dependencies (mysql-proxy)
uses: ramsey/composer-install@v3
with:
working-directory: packages/mysql-proxy
ignore-cache: "yes"
composer-options: "--optimize-autoloader"

- name: Install MySQL command-line client
run: |
set -euo pipefail

sudo timeout 300s apt-get update
sudo env DEBIAN_FRONTEND=noninteractive timeout 300s \
apt-get install -y --no-install-recommends mariadb-client

- name: Download MySQL server test suite
run: |
set -euo pipefail

mysql_version='8.0.46'
archive="mysql-test-${mysql_version}-linux-glibc2.28-x86_64.tar.xz"
archive_url="https://cdn.mysql.com/Downloads/MySQL-8.0/${archive}"
download_path="$RUNNER_TEMP/$archive"
install_dir="$RUNNER_TEMP/mysql-server-test-suite"

curl --fail --location --retry 3 --connect-timeout 30 --max-time 1200 \
--output "$download_path" \
"$archive_url"

mkdir -p "$install_dir"
tar -xJf "$download_path" -C "$install_dir" --strip-components=1

mkdir -p "$install_dir/share"
for mysql_share_dir in /usr/share/mysql /usr/share/mariadb; do
if [ -d "$mysql_share_dir/charsets" ]; then
ln -sfn "$mysql_share_dir" "$install_dir/share/mysql"
break
fi
done
if [ ! -e "$install_dir/share/mysql" ]; then
mkdir -p "$install_dir/share/mysql/charsets"
fi

mysql_test_dir="$(find "$install_dir" -type f -name mysql-test-run.pl -printf '%h\n' | head -n 1)"
mysqltest_bin="$(find "$install_dir" -type f -name mysqltest -perm /111 -print | head -n 1)"

if [ -z "$mysql_test_dir" ] || [ ! -f "$mysql_test_dir/mysql-test-run.pl" ]; then
echo "mysql-test-run.pl was not found in $archive." >&2
find "$install_dir" -maxdepth 3 -type f | sort | sed -n '1,120p' >&2
exit 1
fi

if [ -z "$mysqltest_bin" ]; then
echo "mysqltest was not found in $archive." >&2
find "$install_dir" -maxdepth 4 -type f -name 'mysql*' | sort >&2
exit 1
fi

echo "MYSQL_TEST_DIR=$mysql_test_dir" >> "$GITHUB_ENV"
echo "MYSQLTEST_BIN=$mysqltest_bin" >> "$GITHUB_ENV"

client_bindir="$RUNNER_TEMP/mysql-client-bindir"
mkdir -p "$client_bindir"
ln -sf "$mysqltest_bin" "$client_bindir/mysqltest"
for client in mysql mysqladmin mysqldump mysqlshow mysqlslap perror; do
client_path="$(command -v "$client" || true)"
if [ -n "$client_path" ]; then
ln -sf "$client_path" "$client_bindir/$client"
fi
done
create_noop_helper() {
cat > "$1" <<'SH'
#!/usr/bin/env bash
exit 0
SH
chmod +x "$1"
}
for helper in \
ibd2sdi \
innochecksum \
my_print_defaults \
myisamchk \
myisampack \
mysql_client_test \
mysql_config_editor \
mysql_keyring_encryption_test \
mysql_migrate_keyring \
mysql_plugin \
mysql_secure_installation \
mysql_ssl_rsa_setup \
mysql_tzinfo_to_sql \
mysql_upgrade \
mysqlbinlog \
mysqlcheck \
mysqlimport \
mysqlpump; do
if [ ! -e "$client_bindir/$helper" ]; then
create_noop_helper "$client_bindir/$helper"
fi
done
create_noop_helper "$client_bindir/mysqld"
create_noop_helper "$install_dir/bin/mysqld"

echo "MYSQL_CLIENT_BINDIR=$client_bindir" >> "$GITHUB_ENV"

"$mysqltest_bin" --version

- name: Run MySQL server result suite against SQLite proxy
run: |
set -euo pipefail

port=13306
db_path="$RUNNER_TEMP/mysql-server-result-suite.sqlite"
proxy_log="$RUNNER_TEMP/mysql-proxy.log"
vardir="$RUNNER_TEMP/mysql-test-var"

php "$GITHUB_WORKSPACE/packages/mysql-proxy/bin/wp-mysql-proxy.php" \
--port "$port" \
--database "$db_path" \
--database-name test \
--database-alias mysql \
--log-level warning \
> "$proxy_log" 2>&1 &
proxy_pid="$!"

cleanup() {
kill "$proxy_pid" 2>/dev/null || true
wait "$proxy_pid" 2>/dev/null || true
}
trap cleanup EXIT

for attempt in $(seq 1 100); do
if php -r "\$socket = @fsockopen( '127.0.0.1', $port ); if ( \$socket ) { fclose( \$socket ); exit( 0 ); } exit( 1 );"; then
break
fi
if ! kill -0 "$proxy_pid" 2>/dev/null; then
echo "MySQL proxy exited before accepting connections." >&2
cat "$proxy_log" >&2 || true
exit 1
fi
if [ "$attempt" -eq 100 ]; then
echo "Timed out waiting for MySQL proxy on port $port." >&2
cat "$proxy_log" >&2 || true
exit 1
fi
sleep 0.1
done

mkdir -p "$vardir"

mysql_preflight_stdout="$RUNNER_TEMP/mysql-preflight.out"
mysql_preflight_stderr="$RUNNER_TEMP/mysql-preflight.err"
set +e
"$MYSQL_CLIENT_BINDIR/mysql" \
--no-defaults \
--host=127.0.0.1 \
--port="$port" \
--user=root \
--password=root \
--database=mysql \
--connect-timeout=10 \
--silent \
--execute="SHOW GLOBAL VARIABLES" \
> "$mysql_preflight_stdout" 2> "$mysql_preflight_stderr"
mysql_preflight_status="$?"
set -e

if [ "$mysql_preflight_status" -ne 0 ]; then
echo "MySQL client preflight failed with exit code $mysql_preflight_status." >&2
echo "### mysql stdout" >&2
cat "$mysql_preflight_stdout" >&2 || true
echo "### mysql stderr" >&2
cat "$mysql_preflight_stderr" >&2 || true
echo "### proxy log" >&2
cat "$proxy_log" >&2 || true
exit "$mysql_preflight_status"
fi

set +e
(
cd "$MYSQL_TEST_DIR"
MYSQL_TEST="$MYSQLTEST_BIN" perl ./mysql-test-run.pl \
--extern host=127.0.0.1 \
--extern port="$port" \
--extern user=root \
--extern password=root \
--extern database=test \
--force \
--max-test-fail=1000 \
--timer \
--parallel=1 \
--client-bindir="$MYSQL_CLIENT_BINDIR" \
--vardir="$vardir"
)
status="$?"
set -e

{
echo "## MySQL Server Result Suite"
echo
echo "- Test source: MySQL server test-suite archive."
echo "- Runner: \`$MYSQLTEST_BIN\`."
echo "- Target: PHP MySQL proxy backed by the SQLite driver, exposing database \`test\`."
echo "- Exit status: \`$status\`."
echo
echo "### Proxy log tail"
echo
echo '```text'
tail -100 "$proxy_log" || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

exit "$status"
28 changes: 25 additions & 3 deletions packages/mysql-proxy/bin/wp-mysql-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@

// Process CLI arguments:
$shortopts = 'h:d:p:l:';
$longopts = array( 'help', 'database:', 'port:', 'log-level:' );
$longopts = array( 'help', 'database:', 'database-name:', 'database-alias:', 'port:', 'log-level:' );
$opts = getopt( $shortopts, $longopts );

$help = <<<USAGE
Usage: php bin/wp-mysql-proxy.php [--port <port>] [--database <path/to/db.sqlite>] [--log-level <log_level>]
Usage: php bin/wp-mysql-proxy.php [--port <port>] [--database <path/to/db.sqlite>] [--database-name <name>] [--database-alias <name>] [--log-level <log_level>]

Options:
-h, --help Show this help message and exit.
-p, --port=<port> The port to listen on. Default: 3306
-d, --database=<path> The path to the SQLite database file. Default: :memory:
--database-name=<name>
The MySQL database name exposed by the proxy. Default: sqlite_database
--database-alias=<name>
Additional database name accepted as an alias for --database-name.
May be passed more than once.
-l, --log-level=<level> The log level to use. One of 'error', 'warning', 'info', 'debug'. Default: info

USAGE;
Expand All @@ -31,6 +36,23 @@
// Database path.
$db_path = $opts['d'] ?? $opts['database'] ?? ':memory:';

// Database name.
$db_name = $opts['database-name'] ?? 'sqlite_database';
if ( '' === $db_name ) {
fwrite( STDERR, "Error: --database-name cannot be empty. Use --help for more information.\n" );
exit( 1 );
}

// Database aliases.
$db_aliases = $opts['database-alias'] ?? array();
if ( ! is_array( $db_aliases ) ) {
$db_aliases = array( $db_aliases );
}
if ( in_array( '', $db_aliases, true ) ) {
fwrite( STDERR, "Error: --database-alias cannot be empty. Use --help for more information.\n" );
exit( 1 );
}

// Port.
$port = (int) ( $opts['p'] ?? $opts['port'] ?? 3306 );
if ( $port < 1 || $port > 65535 ) {
Expand All @@ -47,7 +69,7 @@

// Start the MySQL proxy.
$proxy = new MySQL_Proxy(
new SQLite_Adapter( $db_path ),
new SQLite_Adapter( $db_path, $db_name, $db_aliases ),
array(
'port' => $port,
'log_level' => $log_level,
Expand Down
Loading
Loading