diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..94f807f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,131 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: PHP ${{ matrix.php }} / ${{ matrix.driver }} / ${{ matrix.search_build }} + runs-on: ubuntu-22.04 + timeout-minutes: 30 + continue-on-error: ${{ matrix.search_build == 'SPHINX3' }} + permissions: + contents: read + packages: read + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1'] + driver: [mysqli, pdo] + search_build: [SPHINX2, SPHINX3, MANTICORE] + env: + DRIVER: ${{ matrix.driver }} + SEARCH_BUILD: ${{ matrix.search_build }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + extensions: mysqli, pdo_mysql, mbstring + tools: composer:v2 + + - name: Resolve search image and group exclusions + id: vars + run: | + EXCLUDE_GROUP="" + SEARCH_IMAGE="" + SEARCH_DOCKERFILE="" + case "$SEARCH_BUILD" in + SPHINX2) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-sphinx2:sphinx2-latest" + SEARCH_DOCKERFILE="docker/search/sphinx2/Dockerfile" + EXCLUDE_GROUP="--exclude-group=Manticore" + ;; + SPHINX3) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-sphinx3:sphinx3-latest" + SEARCH_DOCKERFILE="docker/search/sphinx3/Dockerfile" + EXCLUDE_GROUP="--exclude-group=Manticore" + ;; + MANTICORE) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-manticore:manticore-latest" + SEARCH_DOCKERFILE="docker/search/manticore/Dockerfile" + ;; + *) + echo "Unknown SEARCH_BUILD: $SEARCH_BUILD" + exit 1 + ;; + esac + { + echo "search_image=$SEARCH_IMAGE" + echo "search_dockerfile=$SEARCH_DOCKERFILE" + echo "exclude_group=$EXCLUDE_GROUP" + } >> "$GITHUB_OUTPUT" + + - name: Fetch search image + run: | + if ! docker pull "${{ steps.vars.outputs.search_image }}"; then + echo "Unable to pull image, building from repository Dockerfile fallback." + docker build -f "${{ steps.vars.outputs.search_dockerfile }}" -t "${{ steps.vars.outputs.search_image }}" . + fi + + - name: Start search daemon container + run: | + docker run -d --name searchd \ + -p 9307:9307 \ + -p 9312:9312 \ + "${{ steps.vars.outputs.search_image }}" + + - name: Wait for searchd + run: | + n=0 + while [ "$n" -lt 60 ]; do + if (echo > /dev/tcp/127.0.0.1/9307) >/dev/null 2>&1; then + exit 0 + fi + n=$((n + 1)) + sleep 1 + done + echo "searchd did not become ready on 127.0.0.1:9307" + docker logs searchd || true + exit 1 + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction + + - name: Prepare autoload + run: composer dump-autoload + + - name: Run tests + run: | + ./vendor/bin/phpunit --configuration "tests/travis/${DRIVER}.phpunit.xml" --coverage-text ${{ steps.vars.outputs.exclude_group }} + + - name: Upload debug artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ci-debug-php${{ matrix.php }}-${{ matrix.driver }}-${{ matrix.search_build }} + if-no-files-found: ignore + path: | + tests/searchd.log + tests/searchd.pid + tests/data/* + + - name: Show searchd logs + if: always() + run: docker logs searchd || true + + - name: Stop searchd container + if: always() + run: docker rm -f searchd || true diff --git a/.github/workflows/publish-search-images.yml b/.github/workflows/publish-search-images.yml new file mode 100644 index 0000000..451b542 --- /dev/null +++ b/.github/workflows/publish-search-images.yml @@ -0,0 +1,62 @@ +name: Publish Search Images + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - .github/workflows/publish-search-images.yml + - docker/search/** + - tests/sphinx.conf + - tests/manticore.conf + - tests/test_udf.c + - tests/ms_test_udf.c + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish ${{ matrix.target }} image + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - target: sphinx2 + version: 2.3.2-beta + dockerfile: docker/search/sphinx2/Dockerfile + - target: sphinx3 + version: 3.9.1 + dockerfile: docker/search/sphinx3/Dockerfile + - target: manticore + version: 2.6.3 + dockerfile: docker/search/manticore/Dockerfile + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + tags: | + ghcr.io/foolcode/sphinxql-query-builder-search-${{ matrix.target }}:${{ matrix.target }}-${{ matrix.version }} + ghcr.io/foolcode/sphinxql-query-builder-search-${{ matrix.target }}:${{ matrix.target }}-latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2141c80..0000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -dist: bionic -language: php - -php: - - 7.4 - - 8.0 - - 8.1.0 - -env: - - DRIVER=mysqli SEARCH_BUILD=SPHINX2 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=mysqli SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=mysqli SEARCH_BUILD=MANTICORE - - DRIVER=pdo SEARCH_BUILD=SPHINX2 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=pdo SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=pdo SEARCH_BUILD=MANTICORE - -matrix: - fast_finish: true - allow_failures: - - env: DRIVER=mysqli SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - env: DRIVER=pdo SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - -addons: - apt: - packages: - - libodbc1 # needed for SPHINX2 - -before_install: - - mkdir $HOME/search - - pushd $HOME/search - - $TRAVIS_BUILD_DIR/tests/install.sh - - popd - -install: composer update --prefer-dist --no-interaction - -before_script: - - composer dump-autoload - - cd tests - - $TRAVIS_BUILD_DIR/tests/run.sh - - cd .. - -script: ./vendor/bin/phpunit --configuration tests/travis/$DRIVER.phpunit.xml --coverage-text $EXCLUDE_GROUP diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..cea4f9c --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,23 @@ +FROM php:8.3-cli + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + gcc \ + git \ + libonig-dev \ + unzip \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-install mysqli pdo_mysql mbstring + +RUN php -r "copy('https://composer.github.io/installer.sig', 'composer.sig');" \ + && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php -r "if (trim(file_get_contents('composer.sig')) !== hash_file('sha384', 'composer-setup.php')) { fwrite(STDERR, 'Invalid Composer installer checksum'.PHP_EOL); exit(1); }" \ + && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \ + && php -r "unlink('composer-setup.php'); unlink('composer.sig');" + +WORKDIR /work + +ENV COMPOSER_ALLOW_SUPERUSER=1 diff --git a/README.md b/README.md index 043c2bc..c0868d1 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Query Builder for SphinxQL ========================== -[![Build Status](https://travis-ci.org/FoolCode/SphinxQL-Query-Builder.png)](https://travis-ci.org/FoolCode/SphinxQL-Query-Builder) +[![CI](https://github.com/FoolCode/SphinxQL-Query-Builder/actions/workflows/ci.yml/badge.svg)](https://github.com/FoolCode/SphinxQL-Query-Builder/actions/workflows/ci.yml) [![Latest Stable Version](https://poser.pugx.org/foolz/sphinxql-query-builder/v/stable)](https://packagist.org/packages/foolz/sphinxql-query-builder) [![Latest Unstable Version](https://poser.pugx.org/foolz/sphinxql-query-builder/v/unstable)](https://packagist.org/packages/foolz/sphinxql-query-builder) [![Total Downloads](https://poser.pugx.org/foolz/sphinxql-query-builder/downloads)](https://packagist.org/packages/foolz/sphinxql-query-builder) diff --git a/docker/search/manticore/Dockerfile b/docker/search/manticore/Dockerfile new file mode 100644 index 0000000..eb27b74 --- /dev/null +++ b/docker/search/manticore/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV MANTICORE_VERSION=2.6.3 +ENV LD_LIBRARY_PATH=/opt/manticore/usr/lib/x86_64-linux-gnu:/opt/manticore/usr/lib + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet -O /opt/sphinx-tests/search.deb \ + "https://github.com/manticoresoftware/manticoresearch/releases/download/${MANTICORE_VERSION}/manticore_${MANTICORE_VERSION}-180328-cccb538-release-stemmer.trusty_amd64-bin.deb" \ + && dpkg -x /opt/sphinx-tests/search.deb /opt/manticore \ + && rm -f /opt/sphinx-tests/search.deb + +COPY tests/manticore.conf /opt/sphinx-tests/manticore.conf +COPY tests/ms_test_udf.c /opt/sphinx-tests/ms_test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -fPIC -I/opt/manticore/usr/include -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/ms_test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/manticore/usr/bin/searchd", "-c", "/opt/sphinx-tests/manticore.conf", "--nodetach"] diff --git a/docker/search/sphinx2/Dockerfile b/docker/search/sphinx2/Dockerfile new file mode 100644 index 0000000..b964bc0 --- /dev/null +++ b/docker/search/sphinx2/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV SPHINX2_VERSION=2.3.2-beta + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + g++ \ + make \ + autoconf \ + automake \ + libtool \ + pkg-config \ + bison \ + flex \ + libexpat1-dev \ + zlib1g-dev \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet "https://sphinxsearch.com/files/sphinx-${SPHINX2_VERSION}.tar.gz" \ + && tar zxf "sphinx-${SPHINX2_VERSION}.tar.gz" \ + && cd "sphinx-${SPHINX2_VERSION}" \ + && ./configure --prefix=/opt/sphinx --without-mysql \ + && make -j"$(nproc)" \ + && make install \ + && cd /opt/sphinx-tests \ + && rm -rf "sphinx-${SPHINX2_VERSION}" "sphinx-${SPHINX2_VERSION}.tar.gz" + +COPY tests/sphinx.conf /opt/sphinx-tests/sphinx.conf +COPY tests/test_udf.c /opt/sphinx-tests/test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/sphinx/bin/searchd", "-c", "/opt/sphinx-tests/sphinx.conf", "--nodetach"] diff --git a/docker/search/sphinx3/Dockerfile b/docker/search/sphinx3/Dockerfile new file mode 100644 index 0000000..d498973 --- /dev/null +++ b/docker/search/sphinx3/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV SPHINX3_VERSION=3.9.1 +ENV SPHINX3_BUILD=141d2ea + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet "https://sphinxsearch.com/files/sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" \ + && tar zxf "sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" \ + && mv "sphinx-${SPHINX3_VERSION}" /opt/sphinx3 \ + && rm -f "sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" + +COPY tests/sphinx.conf /opt/sphinx-tests/sphinx.conf +COPY tests/test_udf.c /opt/sphinx-tests/test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/sphinx3/bin/searchd", "-c", "/opt/sphinx-tests/sphinx.conf", "--nodetach"] diff --git a/scripts/run-tests-docker.sh b/scripts/run-tests-docker.sh new file mode 100755 index 0000000..aab4d58 --- /dev/null +++ b/scripts/run-tests-docker.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IMAGE_NAME="${IMAGE_NAME:-sphinxql-test-runner:local}" +BUILD_LOG="$(mktemp)" +USE_ISOLATED_DOCKER_CONFIG=0 +TEMP_DOCKER_CONFIG="" + +cleanup() { + rm -f "$BUILD_LOG" + if [ -n "$TEMP_DOCKER_CONFIG" ] && [ -d "$TEMP_DOCKER_CONFIG" ]; then + rm -rf "$TEMP_DOCKER_CONFIG" + fi +} +trap cleanup EXIT + +docker_cmd() { + if [ "$USE_ISOLATED_DOCKER_CONFIG" -eq 1 ]; then + DOCKER_CONFIG="$TEMP_DOCKER_CONFIG" docker "$@" + else + docker "$@" + fi +} + +if ! docker_cmd build -f "$ROOT_DIR/Dockerfile.test" -t "$IMAGE_NAME" "$ROOT_DIR" 2>&1 | tee "$BUILD_LOG"; then + if grep -Eq "error getting credentials|docker-credential-.*not found" "$BUILD_LOG"; then + echo "Docker credential helper failure detected, retrying build with isolated anonymous Docker config." >&2 + TEMP_DOCKER_CONFIG="$(mktemp -d)" + cat >"$TEMP_DOCKER_CONFIG/config.json" <<'JSON' +{ + "auths": {} +} +JSON + USE_ISOLATED_DOCKER_CONFIG=1 + docker_cmd build -f "$ROOT_DIR/Dockerfile.test" -t "$IMAGE_NAME" "$ROOT_DIR" + else + exit 1 + fi +fi + +DOCKER_RUN_ARGS=( + run + --rm + -u "$(id -u):$(id -g)" + -e HOME=/tmp/home + -e SOURCE_DIR=/src + -v "$ROOT_DIR:/src:ro" +) + +if [ -t 1 ]; then + DOCKER_RUN_ARGS+=(-t) +fi + +docker_cmd "${DOCKER_RUN_ARGS[@]}" "$IMAGE_NAME" bash /src/scripts/run-tests-in-container.sh diff --git a/scripts/run-tests-in-container.sh b/scripts/run-tests-in-container.sh new file mode 100755 index 0000000..fb27ca5 --- /dev/null +++ b/scripts/run-tests-in-container.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +SOURCE_DIR="${SOURCE_DIR:-/src}" +WORK_DIR="$(mktemp -d /tmp/sphinxql-work-XXXXXX)" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +cp -a "$SOURCE_DIR/." "$WORK_DIR" + +export SEARCH_BUILD=MANTICORE +export COMPOSER_ALLOW_SUPERUSER=1 +export COMPOSER_HOME="${COMPOSER_HOME:-/tmp/composer}" +export HOME="${HOME:-/tmp/home}" + +mkdir -p "$HOME/search" +pushd "$HOME/search" >/dev/null +"$WORK_DIR/tests/install.sh" +popd >/dev/null + +cd "$WORK_DIR" +composer install --prefer-dist --no-interaction --no-progress +composer dump-autoload + +cd "$WORK_DIR/tests" +./run.sh + +cd "$WORK_DIR" +./vendor/bin/phpunit --configuration tests/travis/mysqli.phpunit.xml +./vendor/bin/phpunit --configuration tests/travis/pdo.phpunit.xml diff --git a/tests/README.md b/tests/README.md index 32728d8..bfb1954 100755 --- a/tests/README.md +++ b/tests/README.md @@ -10,3 +10,27 @@ The udf must be compiled: `gcc -shared -o data/test_udf.so test_udf.c` The test should then just work: `phpunit -c phpunit.xml` Make sure there's a `data` directory under the `tests` directory. + +##### Docker-only local run (no host PHP/Composer install) + +This repository includes a Docker-based runner that executes the Manticore matrix used in CI (`mysqli` and `pdo` test configs). + +From the repository root: + +```bash +./scripts/run-tests-docker.sh +``` + +What this command does: + +1. Builds `Dockerfile.test` (PHP CLI + required extensions + Composer + gcc/wget). +2. Runs `tests/install.sh` with `SEARCH_BUILD=MANTICORE`. +3. Starts searchd with `tests/run.sh` (and compiles test UDF). +4. Executes: + - `vendor/bin/phpunit --configuration tests/travis/mysqli.phpunit.xml` + - `vendor/bin/phpunit --configuration tests/travis/pdo.phpunit.xml` + +Notes: + +- The script copies the repository into a temporary in-container workspace before running tests, so your local working tree is not modified by test artifacts. +- If Docker image build fails due to a broken local credential helper, the script retries build with an isolated anonymous Docker config for public image pulls. diff --git a/tests/SphinxQL/TestUtil.php b/tests/SphinxQL/TestUtil.php index 62e32fd..1267304 100644 --- a/tests/SphinxQL/TestUtil.php +++ b/tests/SphinxQL/TestUtil.php @@ -4,6 +4,7 @@ use Foolz\SphinxQL\Drivers\Mysqli\Connection as MysqliConnection; use Foolz\SphinxQL\Drivers\Pdo\Connection as PdoConnection; +use PDO; class TestUtil { @@ -12,8 +13,18 @@ class TestUtil */ public static function getConnectionDriver() { - $connection = '\\Foolz\\SphinxQL\\Drivers\\'.$GLOBALS['driver'].'\\Connection'; + if ($GLOBALS['driver'] === 'Pdo') { + return new class extends PdoConnection { + public function connect() + { + $connected = parent::connect(); + $this->getConnection()->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); - return new $connection(); + return $connected; + } + }; + } + + return new MysqliConnection(); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6bd522e..a45f371 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,10 @@