Skip to content

Commit 733db76

Browse files
committed
wip
# Conflicts: # composer.json
1 parent 5a98a21 commit 733db76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4359
-6
lines changed

composer.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"nette/php-generator": "^4.1.6",
2626
"nikic/php-parser": "^5.3",
2727
"php": "^8.5",
28+
"psr/container": "^2.0",
2829
"psr-discovery/http-client-implementations": "^1.4",
2930
"psr-discovery/http-factory-implementations": "^1.2",
3031
"psr/cache": "^3.0",
@@ -49,7 +50,6 @@
4950
"adam-paterson/oauth2-slack": "^1.1",
5051
"aws/aws-sdk-php": "^3.338.0",
5152
"azure-oss/storage-blob-flysystem": "^1.2",
52-
"brianium/paratest": "^7.14",
5353
"carthage-software/mago": "1.0.0-beta.28",
5454
"depotwarehouse/oauth2-twitch": "^1.3",
5555
"guzzlehttp/psr7": "^2.6.1",
@@ -88,7 +88,8 @@
8888
"tempest/blade": "dev-main",
8989
"thenetworg/oauth2-azure": "^2.2",
9090
"twig/twig": "^3.16",
91-
"wohali/oauth2-discord-new": "^1.2"
91+
"wohali/oauth2-discord-new": "^1.2",
92+
"brianium/paratest": "^7.14"
9293
},
9394
"replace": {
9495
"tempest/auth": "self.version",
@@ -117,6 +118,7 @@
117118
"tempest/reflection": "self.version",
118119
"tempest/router": "self.version",
119120
"tempest/storage": "self.version",
121+
"tempest/testing": "self.version",
120122
"tempest/support": "self.version",
121123
"tempest/upgrade": "self.version",
122124
"tempest/validation": "self.version",
@@ -159,6 +161,7 @@
159161
"Tempest\\Router\\": "packages/router/src",
160162
"Tempest\\Storage\\": "packages/storage/src",
161163
"Tempest\\Support\\": "packages/support/src",
164+
"Tempest\\Testing\\": "packages/testing/src",
162165
"Tempest\\Upgrade\\": "packages/upgrade/src",
163166
"Tempest\\Validation\\": "packages/validation/src",
164167
"Tempest\\View\\": "packages/view/src",
@@ -196,6 +199,7 @@
196199
"packages/support/src/Str/functions.php",
197200
"packages/support/src/Uri/functions.php",
198201
"packages/support/src/functions.php",
202+
"packages/testing/src/functions.php",
199203
"packages/view/src/functions.php",
200204
"packages/vite/src/functions.php"
201205
]
@@ -227,6 +231,7 @@
227231
"Tempest\\Router\\Tests\\": "packages/router/tests",
228232
"Tempest\\Storage\\Tests\\": "packages/storage/tests",
229233
"Tempest\\Support\\Tests\\": "packages/support/tests",
234+
"Tempest\\Testing\\Tests\\": "packages/testing/tests",
230235
"Tempest\\Upgrade\\Tests\\": "packages/upgrade/tests",
231236
"Tempest\\Validation\\Tests\\": "packages/validation/tests",
232237
"Tempest\\View\\Tests\\": "packages/view/tests",
@@ -248,7 +253,7 @@
248253
"fmt": "vendor/bin/mago fmt",
249254
"lint:fix": "vendor/bin/mago lint --fix --format-after-fix",
250255
"style": "composer fmt && composer lint:fix",
251-
"test": "composer phpunit",
256+
"test": "@php tempest test && composer phpunit",
252257
"lint": "vendor/bin/mago lint --potentially-unsafe --minimum-fail-level=note",
253258
"phpstan": "vendor/bin/phpstan analyse src tests --memory-limit=1G",
254259
"rector": "vendor/bin/rector process --no-ansi",
@@ -266,7 +271,7 @@
266271
"composer rector",
267272
"./bin/validate-packages",
268273
"./tempest discovery:clear --no-interaction",
269-
"composer phpunit",
274+
"composer test",
270275
"composer phpstan"
271276
]
272277
}

packages/testing/.gitattributes

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Exclude build/test files from the release
2+
.github/ export-ignore
3+
tests/ export-ignore
4+
.gitattributes export-ignore
5+
.gitignore export-ignore
6+
phpunit.xml export-ignore
7+
README.md export-ignore
8+
9+
# Configure diff output
10+
*.view.php diff=html
11+
*.php diff=php
12+
*.css diff=css
13+
*.html diff=html
14+
*.md diff=markdown

packages/testing/LICENSE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2024 Brent Roose [email protected]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

packages/testing/README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
## A parallel test runner for modern PHP
2+
3+
This package is an experiment in rethinking testing for PHP. It's not intended for use in real-life projects. Some of the core ideas behind this package:
4+
5+
### ✅ A fluent testing API
6+
7+
```php
8+
use Tempest\Testing\Test;
9+
10+
final class ArrayTest
11+
{
12+
#[Test]
13+
public function forget_keys_mutates_array(): void
14+
{
15+
$original = [
16+
'foo' => 'bar',
17+
'baz' => 'qux',
18+
];
19+
20+
Arr\forget_keys($original, ['foo']);
21+
22+
test($original)
23+
->hasCount(1)
24+
->hasKey('baz')
25+
->hasNoKey('foo');
26+
}
27+
}
28+
```
29+
30+
### ✅ Dependency injection support
31+
32+
Use Tempest's container or any PSR-11 compatible one to inject dependencies into your tests.
33+
34+
```php
35+
use Tempest\Testing\Test;
36+
37+
final class BookTest
38+
{
39+
public function __construct(
40+
private Database $database,
41+
) {}
42+
43+
#[Test]
44+
public function book_can_be_created(BookRepository $repository): void
45+
{
46+
// …
47+
}
48+
}
49+
```
50+
51+
```php
52+
use Tempest\Container\Initializer;
53+
use Tempest\Container\Singleton;
54+
use Tempest\Testing\Actions\RunTest;
55+
56+
final class RunTestInitializer implements Initializer
57+
{
58+
#[Singleton]
59+
public function initialize(Container $container): RunTest
60+
{
61+
return new RunTest(container: new YourOwnContainer());
62+
}
63+
}
64+
```
65+
66+
### ✅ Parallel execution
67+
68+
Parallel execution by default instead of an afterthought.
69+
70+
### ✅ Immediate output
71+
72+
Get immediate feedback on test failures while running them.
73+
74+
```console
75+
× // Tempest\Testing\Tests\TestFoo::a
76+
// /Dev/tempest-testing/tests/TestFoo.php:13
77+
// failed asserting that true is false
78+
79+
× // Tempest\Testing\Tests\TestFoo::b
80+
// /Dev/tempest-testing/tests/TestFoo.php:20
81+
// failed asserting that true is false
82+
83+
× // Tempest\Testing\Tests\TestFoo::e
84+
// /Dev/tempest-testing/tests/TestFoo.php:39
85+
// failed asserting that true is false
86+
87+
2 succeeded 3 failed 0 skipped 0.12s
88+
```
89+
90+
### ✅ Compose tests however you like
91+
92+
```php
93+
final class ApplicationTest
94+
{
95+
use TestsEvents, TestsDatbase, TestsHttp;
96+
97+
#[Test]
98+
public function test_before(): void
99+
{
100+
$this->events
101+
->preventPropagation();
102+
103+
$this->http
104+
->post('/books', ['title' => 'Timeline Taxi'])
105+
->assertRedirectTo('/books/timeline-taxi');
106+
107+
$this->database
108+
->assertContains('books', ['title' => 'Timeline Taxi']);
109+
}
110+
}
111+
```
112+
113+
### ✅ Tempest's no-config approach
114+
115+
Structure your tests however you like: in a separate dev namespaces or alongside your production code. Tempest's discovery will find them for you without any configuration on your part.
116+
117+
### ✅ Extensible event-driven architecture
118+
119+
Anything that happens during tests is easy to hook into with your own event listeners.
120+
121+
```php
122+
use Tempest\Container\Singleton;
123+
use Tempest\Console\HasConsole;
124+
use Tempest\EventBus\EventHandler;
125+
use Tempest\Testing\Events\TestFailed;
126+
127+
#[Singleton]
128+
final class TestEventListeners
129+
{
130+
use HasConsole;
131+
132+
#[EventHandler]
133+
public function onTestFailed(TestFailed $event): void
134+
{
135+
$this->error(sprintf('<style="fg-red">%s</style>', $event->name));
136+
$this->writeln(sprintf(' <style="fg-red dim">//</style> <style="fg-red underline">%s</style>', $event->location));
137+
$this->writeln(sprintf(' <style="fg-red dim">//</style> <style="fg-red">%s</style>', $event->reason));
138+
$this->writeln();
139+
}
140+
}
141+
```

packages/testing/composer.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "tempest/testing",
3+
"description": "A parallel test runner for modern PHP",
4+
"license": "MIT",
5+
"require": {
6+
"php": "^8.5",
7+
"tempest/core": "^dev-main",
8+
"tempest/event-bus": "^dev-main",
9+
"tempest/console": "^dev-main",
10+
"symfony/process": "^7.3",
11+
"psr/container": "^2.0"
12+
},
13+
"autoload": {
14+
"psr-4": {
15+
"Tempest\\Testing\\": "src"
16+
},
17+
"files": [
18+
"src/functions.php"
19+
]
20+
},
21+
"autoload-dev": {
22+
"psr-4": {
23+
"Tempest\\Testing\\Tests\\": "tests"
24+
}
25+
}
26+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Tempest\Testing\Actions;
4+
5+
use Tempest\Support\Arr\ImmutableArray;
6+
use Tempest\Testing\Events\TestRunEnded;
7+
use Tempest\Testing\Events\TestRunStarted;
8+
use Tempest\Testing\Events\TestsChunked;
9+
use Tempest\Testing\Runner\TestRunner;
10+
11+
use function Tempest\event;
12+
13+
final class ChunkAndRunTests
14+
{
15+
public function __invoke(ImmutableArray $tests, int $processes): void
16+
{
17+
$chunks = ceil($tests->count() / $processes);
18+
19+
$tests = $tests
20+
->chunk($chunks)
21+
->map(fn (ImmutableArray $tests, int $i) => new TestRunner($i)->run($tests));
22+
23+
event(new TestsChunked($tests->count()));
24+
25+
event(new TestRunStarted());
26+
27+
$tests->map(fn (TestRunner $runner) => $runner->wait());
28+
29+
event(new TestRunEnded());
30+
}
31+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
namespace Tempest\Testing\Actions;
4+
5+
use function Tempest\Support\str;
6+
7+
final class ConvertPHPUnit
8+
{
9+
public function __invoke(string $input): string
10+
{
11+
$string = str($input)
12+
// Add uses
13+
->replaceRegex('/namespace [\w\\\\]+;\s/', function (array $match) {
14+
return $match[0] . PHP_EOL . 'use function Tempest\Testing\test;' . PHP_EOL . 'use Tempest\Testing\Test;';
15+
})
16+
// Remove extends from PHPUnit test classes
17+
->replaceRegex('/extends [\w]+/', '');
18+
19+
$expectedActualPatterns = [
20+
'assertSame' => 'is',
21+
'assertInstanceOf' => 'instanceOf',
22+
];
23+
24+
foreach ($expectedActualPatterns as $original => $method) {
25+
$string = $string->replaceRegex(
26+
'/\$this->' . $original . '\(\s*(?<expected>.*?),\s*(?<actual>.*?)\s*\)\s*;/',
27+
function (array $match) use ($method) {
28+
return sprintf(
29+
'test(%s)->%s(%s);',
30+
$match['actual'],
31+
$method,
32+
$match['expected'],
33+
);
34+
},
35+
);
36+
}
37+
38+
return $string->toString();
39+
}
40+
}
41+
42+
/*
43+
*
44+
* final public static function assertArrayHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void
45+
* final public static function assertArrayNotHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void
46+
* final public static function assertIsList(mixed $array, string $message = ''): void
47+
* final public static function assertContains(mixed $needle, iterable $haystack, string $message = ''): void
48+
* final public static function assertNotContains(mixed $needle, iterable $haystack, string $message = ''): void
49+
* final public static function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void
50+
* final public static function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void
51+
* final public static function assertEquals(mixed $expected, mixed $actual, string $message = ''): void
52+
* final public static function assertNotEquals(mixed $expected, mixed $actual, string $message = ''): void
53+
* final public static function assertEmpty(mixed $actual, string $message = ''): void
54+
* final public static function assertNotEmpty(mixed $actual, string $message = ''): void
55+
* final public static function assertGreaterThan(mixed $minimum, mixed $actual, string $message = ''): void
56+
* final public static function assertGreaterThanOrEqual(mixed $minimum, mixed $actual, string $message = ''): void
57+
* final public static function assertLessThan(mixed $maximum, mixed $actual, string $message = ''): void
58+
* final public static function assertLessThanOrEqual(mixed $maximum, mixed $actual, string $message = ''): void
59+
* final public static function assertTrue(mixed $condition, string $message = ''): void
60+
* final public static function assertFalse(mixed $condition, string $message = ''): void
61+
* final public static function assertNull(mixed $actual, string $message = ''): void
62+
* final public static function assertNotNull(mixed $actual, string $message = ''): void
63+
* final public static function assertSame(mixed $expected, mixed $actual, string $message = ''): void
64+
* final public static function assertNotSame(mixed $expected, mixed $actual, string $message = ''): void
65+
* final public static function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void
66+
* final public static function assertNotInstanceOf(string $expected, mixed $actual, string $message = ''): void
67+
* final public static function assertIsArray(mixed $actual, string $message = ''): void
68+
* final public static function assertIsBool(mixed $actual, string $message = ''): void
69+
* final public static function assertIsFloat(mixed $actual, string $message = ''): void
70+
* final public static function assertIsInt(mixed $actual, string $message = ''): void
71+
* final public static function assertIsNumeric(mixed $actual, string $message = ''): void
72+
* final public static function assertIsObject(mixed $actual, string $message = ''): void
73+
* final public static function assertIsResource(mixed $actual, string $message = ''): void
74+
* final public static function assertIsString(mixed $actual, string $message = ''): void
75+
* final public static function assertIsScalar(mixed $actual, string $message = ''): void
76+
* final public static function assertIsCallable(mixed $actual, string $message = ''): void
77+
* final public static function assertIsIterable(mixed $actual, string $message = ''): void
78+
* final public static function assertIsNotArray(mixed $actual, string $message = ''): void
79+
* final public static function assertIsNotBool(mixed $actual, string $message = ''): void
80+
* final public static function assertIsNotFloat(mixed $actual, string $message = ''): void
81+
* final public static function assertIsNotInt(mixed $actual, string $message = ''): void
82+
* final public static function assertIsNotNumeric(mixed $actual, string $message = ''): void
83+
* final public static function assertIsNotObject(mixed $actual, string $message = ''): void
84+
* final public static function assertIsNotResource(mixed $actual, string $message = ''): void
85+
* final public static function assertIsNotString(mixed $actual, string $message = ''): void
86+
* final public static function assertIsNotScalar(mixed $actual, string $message = ''): void
87+
* final public static function assertIsNotCallable(mixed $actual, string $message = ''): void
88+
* final public static function assertIsNotIterable(mixed $actual, string $message = ''): void
89+
* final public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void
90+
* final public static function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void
91+
* final public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void
92+
* final public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void
93+
* final public static function assertJson(string $actual, string $message = ''): void
94+
*
95+
*/

0 commit comments

Comments
 (0)