Skip to content
Merged
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
78 changes: 78 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: CI

on:
push:

pull_request:
branches:
- '*'

permissions:
contents: read

jobs:
testsuite:
name: PHP ${{ matrix.php-version }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# composer.json declares php >=8.1 and cakephp/cakephp ^5.0.
# CakePHP 5.0-5.2 allow PHP 8.1; 5.3+ requires PHP 8.2, so on 8.1
# composer resolves 5.2.x and on 8.2+ the latest 5.x. Test the whole
# declared range: 8.1 (min) -> 8.5 (newest).
php-version: ['8.1', '8.2', '8.3', '8.4', '8.5']

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl, apcu, sqlite3, pdo_sqlite
ini-values: apc.enable_cli=1
coverage: none

- 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@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php-version }}-${{ hashFiles('composer.json') }}
restore-keys: ${{ runner.os }}-composer-${{ matrix.php-version }}-

- name: Install dependencies
# composer.lock is gitignored, so resolve dependencies on each run.
run: composer update --prefer-dist --no-interaction --no-progress

- name: Run PHPUnit
run: vendor/bin/phpunit

cs-stan:
name: Coding Standard & Static Analysis
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, intl, apcu
coverage: none

- name: Install dependencies
run: composer update --prefer-dist --no-interaction --no-progress

- name: Run PHP CodeSniffer
run: composer cs-check

# NOTE: phpstan/psalm are pinned to 1.10.14 / 5.10.0 in .phive/phars.xml;
# those versions do not run on PHP 8.2+ (fatal in better-reflection) and
# report pre-existing findings. Modernize the pins in a separate change
# before wiring them back into CI as a blocking step.
34 changes: 34 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
~
~ Licensed under The MIT License
~ Redistributions of files must retain the above copyright notice.
~
~ @copyright Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
~ @license MIT License (http://www.opensource.org/licenses/mit-license.php)
-->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
colors="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
<php>
<ini name="memory_limit" value="-1"/>
<ini name="apc.enable_cli" value="1"/>
</php>

<testsuites>
<testsuite name="DbTest Test Suite">
<directory>tests/TestCase/</directory>
</testsuite>
</testsuites>

<source>
<include>
<directory suffix=".php">src/</directory>
</include>
</source>
</phpunit>

This file was deleted.

2 changes: 1 addition & 1 deletion src/TestSuite/Fixture/Extension/PHPUnitExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function bootstrap(Configuration $configuration, Facade $facade, Paramete

$facade->registerSubscribers(
new PHPUnitStartedSubscriber($fixtureManager),
new PHPUnitBeforeTestMethodCalledSubscriber(),
new PHPUnitPreparationStartedSubscriber(),
new PHPUnitAfterTestMethodFinishedSubscriber()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);

/**
* Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\DbTest\TestSuite\Fixture\Extension;

use Cake\Database\Connection;
use Cake\Datasource\ConnectionManager;
use PHPUnit\Event\Test\PreparationStarted;
use PHPUnit\Event\Test\PreparationStartedSubscriber;

/**
* Opens the per-test transaction before setUp() runs.
*
* PHPUnit emits PreparationStarted before the before-test hooks, so opening the
* transaction here keeps everything written in setUp() (fixtures, factories, save())
* inside the per-test transaction. The previous BeforeTestMethodCalled event is
* emitted after setUp(), which committed that data and leaked it into the following
* tests of the same class.
*/
class PHPUnitPreparationStartedSubscriber implements PreparationStartedSubscriber
{
/**
* @param \PHPUnit\Event\Test\PreparationStarted $event Event
* @return void
*/
public function notify(PreparationStarted $event): void
{
$connection = ConnectionManager::get('test');
assert($connection instanceof Connection);
// a previous test may have left its transaction open (e.g. unmet requirements)
if ($connection->inTransaction()) {
$connection->rollback(true);
}
$connection->begin();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);

/**
* Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2013 - 2023, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\DbTest\Test\TestCase\TestSuite\Fixture\Extension;

use Cake\Database\Connection;
use Cake\Datasource\ConnectionManager;
use CakeDC\DbTest\TestSuite\Fixture\Extension\PHPUnitPreparationStartedSubscriber;
use PHPUnit\Event\Code\Test as EventTest;
use PHPUnit\Event\Telemetry\Duration;
use PHPUnit\Event\Telemetry\GarbageCollectorStatus;
use PHPUnit\Event\Telemetry\HRTime;
use PHPUnit\Event\Telemetry\Info;
use PHPUnit\Event\Telemetry\MemoryUsage;
use PHPUnit\Event\Telemetry\Snapshot;
use PHPUnit\Event\Test\PreparationStarted;
use PHPUnit\Event\Test\PreparationStartedSubscriber;
use PHPUnit\Framework\TestCase;

/**
* PHPUnitPreparationStartedSubscriberTest
*/
class PHPUnitPreparationStartedSubscriberTest extends TestCase
{
/**
* @var \Cake\Database\Connection
*/
protected Connection $connection;

/**
* @return void
*/
protected function setUp(): void
{
parent::setUp();

$this->connection = new class extends Connection {
/**
* @var array<string>
*/
public array $calls = [];

/**
* @var bool
*/
public bool $inTransaction = false;

/**
* Skip the parent constructor, the test never touches the driver.
*/
public function __construct()
{
}

/**
* @return bool
*/
public function inTransaction(): bool
{
return $this->inTransaction;
}

/**
* @return void
*/
public function begin(): void
{
$this->calls[] = 'begin';
$this->inTransaction = true;
}

/**
* @param bool|null $toBeginning Whether the transaction should be rolled back to the beginning.
* @return bool
*/
public function rollback(?bool $toBeginning = null): bool
{
$this->calls[] = 'rollback';
$this->inTransaction = false;

return true;
}
};

ConnectionManager::drop('test');
ConnectionManager::setConfig('test', $this->connection);
}

/**
* The subscriber opens the per-test transaction on the PreparationStarted event.
*
* @return void
*/
public function testOpensTransactionOnPreparationStarted(): void
{
$subscriber = new PHPUnitPreparationStartedSubscriber();
$this->assertInstanceOf(PreparationStartedSubscriber::class, $subscriber);

$subscriber->notify($this->preparationStartedEvent());

$this->assertSame(['begin'], $this->connection->calls);
}

/**
* A transaction left open by a previous test is rolled back before beginning.
*
* @return void
*/
public function testRollsBackAbandonedTransactionBeforeBeginning(): void
{
$this->connection->inTransaction = true;

(new PHPUnitPreparationStartedSubscriber())->notify($this->preparationStartedEvent());

$this->assertSame(['rollback', 'begin'], $this->connection->calls);
}

/**
* Builds a PreparationStarted event; the subscriber ignores the payload.
*
* @return \PHPUnit\Event\Test\PreparationStarted
*/
protected function preparationStartedEvent(): PreparationStarted
{
$snapshot = new Snapshot(
HRTime::fromSecondsAndNanoseconds(0, 0),
MemoryUsage::fromBytes(0),
MemoryUsage::fromBytes(0),
new GarbageCollectorStatus(0, 0, 0, 0, null, null, null, null, null, null, null, null),
);
$telemetry = new Info(
$snapshot,
Duration::fromSecondsAndNanoseconds(0, 0),
MemoryUsage::fromBytes(0),
Duration::fromSecondsAndNanoseconds(0, 0),
MemoryUsage::fromBytes(0),
);
$test = new class ('PreparationStarted') extends EventTest {
/**
* @return string
*/
public function id(): string
{
return 'CakeDC\DbTest\Test\TestCase\PreparationStarted::test';
}

/**
* @return string
*/
public function name(): string
{
return 'test';
}
};

return new PreparationStarted($telemetry, $test);
}
}
Loading