Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
afdeb5c
Add Pest + PHPStan test harness matching walkor/workerman conventions
detain May 28, 2026
c541753
Refactor command queueing into shared queueCommand() + dispatcher() h…
detain May 28, 2026
478e6a3
Add subprocess-based integration test harness for Workerman async client
detain May 28, 2026
4059e42
Add GitHub Actions CI workflow with Codecov + Codacy coverage upload
detain May 28, 2026
738cb0a
fixing dragonfly install in workflow
detain May 28, 2026
90e1db8
manual start of df in w
detain May 28, 2026
86a5f6d
last time i found an existing se5rvice on the redis port.. so if its …
detain May 28, 2026
0db6927
update
detain May 28, 2026
b1cdd77
update
detain May 28, 2026
bdc8b3d
fix loose char
detain May 28, 2026
50357d2
update
detain May 28, 2026
185f1bc
update switching to docker dragonfly image
detain May 28, 2026
048e1e7
update
detain May 28, 2026
005e952
update
detain May 28, 2026
93e9694
update
detain May 28, 2026
48b2eae
Implement SCAN and scanAll(), rewrite RESP decoder for nested arrays
detain May 28, 2026
d97ab38
Split Codacy coverage upload into its own job and drop dup Codecov badge
detain May 28, 2026
de75839
Move runInWorker out of RedisTestCase into a free Pest helper
detain May 28, 2026
2ac6352
Only collect Pest coverage on the PHP 8.3 leg of the matrix
detain May 28, 2026
1f933fa
Implement HSCAN and hScanAll() iterator for hash field traversal
detain May 28, 2026
8ce7a54
Implement SSCAN and sScanAll() iterator for set member traversal
detain May 28, 2026
fa4904b
Implement ZSCAN and zScanAll() for sorted-set member traversal
detain May 28, 2026
da3c29d
Add explicit rawCommand() so the @method declaration actually works
detain May 28, 2026
3acd82f
Add explicit ping/info/dbSize/time/flushDb/flushAll for no-arg-callba…
detain May 28, 2026
6c039a6
Refactor coverage graphs section in README
detain May 28, 2026
7409fc6
Document GETDEL/GETEX/SUBSTR/COPY/TOUCH/EXPIRETIME/PEXPIRETIME/ECHO/H…
detain May 28, 2026
8af9528
Implement explicit quit() with don't-reconnect semantics
detain May 28, 2026
8d8e27f
Document 23 modern list/set/hash/zset/stream commands (Tier 2)
detain May 28, 2026
8a1c9d7
Implement sharded pub/sub: sPublish + sSubscribe (Tier 3)
detain May 28, 2026
63b1958
Document bitmap/geo/scripting-RO commands with underscore-bridge methods
detain May 28, 2026
9058aba
Add server administration surface: config/acl/slowLog/memory/command/…
detain May 28, 2026
e6988b1
Implement JSON module surface: json() dispatcher + 16 typed shortcuts…
detain May 28, 2026
b80b663
Implement Bloom Filter, Count-Min Sketch, and TopK module surfaces (T…
detain May 28, 2026
beb316f
Tier 9: FT (RedisSearch), HEXPIRE family, bgSave, module(), sortRo
detain May 28, 2026
1da6bc1
Implement unsubscribe/pUnsubscribe/sUnsubscribe with lock-bypass
detain May 29, 2026
43d89cd
Implement monitor() command stream with a dedicated lock
detain May 29, 2026
c3d91c7
Add explicit xAdd() that flattens the field map (encoder fix)
detain May 29, 2026
2d16bd6
updated readme fixing badges and adding a few coverage viz's
detain May 29, 2026
5cfa3cc
updated readme and added changelog
detain May 29, 2026
7c4cf10
Harden async core: timer leak, stream guards, reconnect state, blocki…
detain May 29, 2026
94ee646
Group 0: subprocess coverage merge + dual-backend testing + floor gate
detain May 29, 2026
a7abeb4
Group 1: Protocols/Redis.php to 100% coverage (in-process unit tests)
detain May 29, 2026
0f876f7
Group 2: Client.php pure-logic unit tests (in-process, no event loop)
detain May 29, 2026
ba5707b
Group 3: connection / lifecycle / server Feature tests
detain May 29, 2026
3abe2c3
Group 4: data-type command sweep (Feature, both engines)
detain May 29, 2026
9250ffb
Group 5: module commands + FT un-gating (Feature, both engines)
detain May 29, 2026
47579aa
Group 6: pub/sub delivery Feature tests (both engines)
detain May 29, 2026
ae44199
Group 7: command-surface completeness + error-path Feature tests
detain May 29, 2026
0d3d6c2
Group 8: Revolt coroutine-mode coverage (Feature, both engines)
detain May 29, 2026
b04ae2f
Group 9: coverage close-out to ~93% + floor ratchet to 90
detain May 30, 2026
2061426
Group 10: finalize docs for the test/coverage build-out
detain May 30, 2026
0c6b828
Fix broken @method stubs (real accessors + getMultiple) and test temp…
detain May 30, 2026
0e9c1e7
Phase 0: PHPUnit scaffolding (assertThrows + coroutineSupported helpe…
detain May 30, 2026
84ffa76
Phase 1: convert 8 Unit test files Pest->PHPUnit (145 tests, 426 asse…
detain May 30, 2026
1201fd9
Phase 2: convert 35 Feature test files Pest->PHPUnit (285 tests, glob…
detain May 30, 2026
8eba563
Phase 3: repoint coverage script pest->phpunit; remove vestigial test…
detain May 30, 2026
3233ee7
Phase 4 (pre-CI): downconvert 17 arrow functions to PHP-7.2 closures …
detain May 30, 2026
798c4d9
Phase 4: add PHP 7.2/7.3/7.4 CI legs (version-aware composer update s…
detain May 30, 2026
cbd8351
Phase 5 (local): remove unused mockery/mockery dep; update docs READM…
detain May 30, 2026
fbc2e32
Fix PHPStan void-result regression from arrow-fn downconvert: drop 'r…
detain May 30, 2026
6558844
Add full PHP 7.2-7.4 CI support + PHP 8.5 leg; bump CI actions for No…
detain May 30, 2026
b7baea2
docs: update CHANGELOG for Pest->PHPUnit conversion + PHP 7.x/8.5 CI …
detain May 30, 2026
38adb3e
Backport src/Client.php array-literal spread to PHP 7.2 (array_merge)
detain May 30, 2026
8231fb6
Make test suite PHP 7.2-parse/runtime clean (heredoc de-flex + polyfi…
detain May 31, 2026
42639bd
Exclude tests/ from Codacy static analysis
detain May 31, 2026
8e3eafb
Qualify \array_merge() in the 18 backported call sites
detain May 31, 2026
25d16db
docs(README): fix MD028 blank-line-in-blockquote + refresh version note
detain May 31, 2026
5f34a14
Merge pull request #2 from detain/phpunit-conversion-php7-ci
detain May 31, 2026
90927c2
update
detain May 31, 2026
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
9 changes: 9 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# Exclude the test suite from Codacy static analysis. Test code follows
# different conventions from production code on purpose — snake_case
# `test_*` method names (PHPUnit), long inline fixtures, reflection seams,
# and `runInWorker()` heredoc snippets — so production style/complexity
# rules produce noise rather than signal here. Coverage is still reported
# (the Codacy coverage check is unaffected by exclude_paths).
exclude_paths:
- 'tests/**'
186 changes: 186 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch: {}

permissions:
contents: read

# Force any JS action still declaring `using: node20` (e.g. the artifact,
# codecov, setup-php, codacy actions) to run on Node 24, silencing the
# "Node.js 20 actions are deprecated" runner warnings. checkout/cache are
# additionally bumped to their Node-24-native majors below.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
test:
name: PHPUnit + PHPStan (PHP ${{ matrix.php }} / ${{ matrix.backend }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# 7.2/7.3/7.4 prove the advertised `php: ">=7.2"` floor: there
# composer resolves PHPUnit 8.5 (on 7.2) or 9.6 (on 7.3/7.4) + Workerman 4
# (the dev deps that gate the floor — phpstan, revolt — are stripped in
# the install step). 8.1/8.2/8.3/8.5 resolve the newest of everything
# (PHPUnit 12, Workerman 5).
php: ['7.2', '7.3', '7.4', '8.1', '8.2', '8.3', '8.5']
# Run the full suite against BOTH engines. The redis leg uses
# redis-stack-server so JSON/BF/CMS/TopK/FT module commands run there too.
backend: [dragonfly, redis]
env:
REDIS_URL: redis://127.0.0.1:6379
REDIS_BACKEND: ${{ matrix.backend }}
# NOTE on per-leg engine selection: GitHub Actions `services:` can't be
# conditionally enabled per matrix value — listing both would start both and
# the two engines would clash on 6379. So we deliberately do NOT use the
# `services:` key and instead start exactly ONE engine container per leg in a
# step gated on `matrix.backend`, both bound to 127.0.0.1:6379. Only one leg
# runs per matrix combination, so the shared host port never clashes, and
# each leg provably talks to the right engine on redis://127.0.0.1:6379.
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Start Dragonfly
if: matrix.backend == 'dragonfly'
run: |
docker run -d --name redis-engine \
-p 6379:6379 \
--ulimit memlock=-1 \
docker.dragonflydb.io/dragonflydb/dragonfly

- name: Start Redis (redis-stack-server, with modules)
if: matrix.backend == 'redis'
run: |
# redis-stack-server ships RedisJSON, RedisBloom (incl. CMS/TopK) and
# RediSearch on 6379 (RedisInsight on 8001 is irrelevant here).
docker run -d --name redis-engine \
-p 6379:6379 \
--health-cmd "redis-cli ping" \
--health-interval 5s \
--health-timeout 3s \
--health-retries 20 \
redis/redis-stack-server:latest

- name: Wait for the engine to accept connections
run: |
for i in $(seq 1 60); do
if (exec 3<>/dev/tcp/127.0.0.1/6379) 2>/dev/null; then
echo "engine is up on 127.0.0.1:6379"; exit 0
fi
echo "waiting for engine on 6379 ($i)..."; sleep 2
done
echo "engine did not come up on 6379" >&2
docker logs redis-engine || true
exit 1

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pcntl, posix, sockets, json
# Coverage instrumentation is only needed on the single coverage leg.
coverage: ${{ (matrix.php == '8.3' && matrix.backend == 'dragonfly') && 'pcov' || 'none' }}
tools: composer:v2

- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php }}-composer-

- name: Install Composer dependencies
# No composer.lock in the repo (it's a library; legs need different
# resolutions), so always `composer update`. On PHP < 8.1 first strip
# the dev deps that gate the floor — phpstan/phpstan needs >=7.4 and
# revolt/event-loop needs >=8.1 — so Composer's platform check can
# auto-select PHPUnit 8.5 (PHP 7.2) or 9.6 (PHP 7.3/7.4) + Workerman 4.
# No version is hard-pinned.
run: |
case "${{ matrix.php }}" in
7.*)
composer remove --dev --no-update phpstan/phpstan revolt/event-loop
;;
esac
composer update --prefer-dist --no-progress --no-interaction

- name: Run PHPStan
# PHPStan 2 won't install on 7.x (and only needs to run once); gate it
# to 8.x. It was stripped from the dev set on the 7.x legs above.
if: ${{ !startsWith(matrix.php, '7.') }}
run: composer analyze

# Test execution. Coverage runs on exactly ONE leg (php=8.3 &&
# backend=dragonfly) via the merge pipeline, which captures Client.php
# (subprocess coverage). The 7.x legs use the legacy config
# (phpunit9.xml.dist: testsuites + env only, no <coverage>/cacheDirectory,
# so it validates under PHPUnit 8.5 and 9.x alike); the 8.x non-coverage
# legs use the default config (phpunit.xml.dist, PHPUnit 10+). Coroutine
# tests self-skip below 8.1 via coroutineSupported(), so no CI exclusion.
- name: Run PHPUnit (PHP 7.x, legacy config)
if: ${{ startsWith(matrix.php, '7.') }}
run: vendor/bin/phpunit -c phpunit9.xml.dist --colors=never

- name: Run PHPUnit (PHP 8.x, non-coverage legs)
if: ${{ !startsWith(matrix.php, '7.') && !(matrix.php == '8.3' && matrix.backend == 'dragonfly') }}
run: vendor/bin/phpunit --colors=never

- name: Run PHPUnit with merged coverage (canonical floor gate)
if: ${{ matrix.php == '8.3' && matrix.backend == 'dragonfly' }}
# composer test:coverage runs bin/run-coverage.sh -> merge-coverage.php
# --min=<floor>, which produces coverage.xml AND fails the build if the
# merged total line coverage drops below the floor (currently 90).
run: composer test:coverage

- name: Upload coverage artifact
if: ${{ matrix.php == '8.3' && matrix.backend == 'dragonfly' }}
uses: actions/upload-artifact@v4
with:
name: coverage-clover
path: coverage.xml
retention-days: 7

- name: Upload coverage to Codecov
if: ${{ matrix.php == '8.3' && matrix.backend == 'dragonfly' }}
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
flags: phpunit
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Stop the engine
if: always()
run: docker rm -f redis-engine || true

codacy-coverage-reporter:
name: codacy-coverage-reporter
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v6

- name: Download coverage artifact
uses: actions/download-artifact@v4
with:
name: coverage-clover
path: .

- name: Run codacy-coverage-reporter
uses: codacy/codacy-coverage-reporter-action@v1.3.0
with:
api-token: ${{ secrets.CODACY_API_TOKEN }}
coverage-reports: coverage.xml
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,19 @@ logs
.settings
.idea
.DS_Store
/vendor/
/composer.lock
/.phpunit.cache/
/.pest.cache/
/.phpstan.cache/
/.phpunit.result.cache
coverage.xml
coverage.cobertura.xml
clover.xml
.phpunit.coverage/
/build/
.caliber/
async_plan.md
redis_start_prompt.md
tests/Support/*.log
tests/Support/*.pid
Loading