Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamelso committed May 20, 2024
1 parent f43cbb0 commit 8251fdf
Show file tree
Hide file tree
Showing 14 changed files with 930 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
51 changes: 51 additions & 0 deletions .github/workflows/symfony.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Transunit

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
symfony-tests:
runs-on: ubuntu-latest
steps:
# To automatically get bug fixes and new Php versions for shivammathur/setup-php,
# change this to (see https://github.com/shivammathur/setup-php#bookmark-versioning):
# uses: shivammathur/setup-php@v2
- uses: shivammathur/setup-php@2cb9b829437ee246e9b3cac53555a39208ca6d28
with:
php-version: '8.3'

- uses: actions/checkout@v3

- name: Copy .env.test.local
run: php -r "file_exists('.env.test.local') || copy('.env.test', '.env.test.local');"

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install Dependencies
run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

- name: Execute canon unit tests via PhpSpec
run: bin/phpspec run --format=pretty --verbose --no-interaction

# - name: Dogfooding via converting unit tests from PhpSpec to PHPUnit
# run: |
# php transunit.php spec
#
# - name: Execute converted unit tests via PHPUnit
# run: bin/phpunit --testdox

- name: 'Candidate conversion: Convert sylius/addressing unit tests from PhpSpec to PHPUnit, then run them'
run: php transunit.php vendor/sylius/addressing/spec && phpunit var/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor/
/var/
composer.lock
18 changes: 18 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"require-dev": {
"phpspec/phpspec": "*",
"phpunit/phpunit": "*",
"sylius/addressing": "*",
"phpspec/prophecy-phpunit": "^2.2"
},
"require": {
"nikic/php-parser": "*",
"symfony/finder": "*",
"symfony/filesystem": "^7.0"
},
"autoload": {
"psr-4": {
"Transunit\\": "lib/Transunit"
}
}
}
13 changes: 13 additions & 0 deletions lib/Transunit/Pass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Transunit;

use PhpParser\Node;
use PhpParser\NodeFinder;

interface Pass
{
/** @param Node[] $ast */
public function find(NodeFinder $nodeFinder, $ast): array;
public function rewrite(Node $node): void;
}
72 changes: 72 additions & 0 deletions lib/Transunit/Pass/AssertionPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Transunit\Pass;

use PhpParser\NodeFinder;
use PhpParser\Node;
use Transunit\Pass;

class AssertionPass implements Pass
{
public function find(NodeFinder $nodeFinder, $ast): array
{
return $nodeFinder->find($ast, function (Node $node) {
if ($node instanceof Node\Stmt\Expression) {
return $node;
}

return null;
});
}

public function rewrite(Node $node): void
{
if (!$node->expr instanceof Node\Expr\MethodCall) {
return;
}

$assertion = $node->expr->name->toString();
$mappedAssertions = [
'shouldBe' => 'assertSame',
'shouldReturn' => 'assertSame',
'shouldBeLike' => 'assertEquals',
'shouldHaveCount' => 'assertCount',
'shouldHaveType' => 'assertInstanceOf',
'shouldImplement' => 'assertInstanceOf',
];

if (!isset($mappedAssertions[$assertion])) {
return;
}

$expectation = $node->expr->args[0]->value;
$call = $node->expr->var;

if (
$expectation instanceof Node\Expr\ConstFetch
&& $expectation->name->toString() === 'null'
) {
// static::assertNull($call);
$rewrittenAssertion = new Node\Expr\StaticCall(
new Node\Name('static'),
'assertNull',
[
new Node\Arg($call)
]
);

} else {
// static::assertSame($expectation, $call);
$rewrittenAssertion = new Node\Expr\StaticCall(
new Node\Name('static'),
$mappedAssertions[$assertion],
[
new Node\Arg($expectation),
new Node\Arg($call)
]
);
}

$node->expr = $rewrittenAssertion;
}
}
71 changes: 71 additions & 0 deletions lib/Transunit/Pass/CallTestSubjectPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Transunit\Pass;

use PhpParser\Node;
use PhpParser\NodeFinder;
use Transunit\Pass;

class CallTestSubjectPass implements Pass
{
public function find(NodeFinder $nodeFinder, $ast): array
{
return $nodeFinder->findInstanceOf($ast, Node\Stmt\ClassMethod::class);
}

public function rewrite(Node $node): void
{
if (!$node instanceof Node\Stmt\ClassMethod) {
return;
}

if (in_array($node->name->toString(), ['setUp', 'let'])) {
return;
}

foreach ($node->stmts as $stmt) {
if (!$stmt instanceof Node\Stmt\Expression) {
continue;
}

$resolvedCall = $stmt->expr;

if ($resolvedCall instanceof Node\Expr\Assign) {
$resolvedCall = $resolvedCall->expr;
}

if (!$resolvedCall instanceof Node\Expr\MethodCall) {
continue;
}

if ($resolvedCall->var instanceof Node\Expr\MethodCall) {
$resolvedCall = $resolvedCall->var;
}

$this->callMethodOnTestSubject($resolvedCall);
}
}

/**
* $this->_testSubject->doSomething();
*/
private function callMethodOnTestSubject(Node\Expr\MethodCall $stmt): void
{
if (!$stmt->var instanceof Node\Expr\Variable) {
return;
}

if ('this' !== $stmt->var->name) {
return;
}

if ($stmt->name->toString() === 'prophesize') {
return;
}

$stmt->var = new Node\Expr\PropertyFetch(
new Node\Expr\Variable('this'),
'_testSubject'
);
}
}
55 changes: 55 additions & 0 deletions lib/Transunit/Pass/ClassnamePass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Transunit\Pass;

use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\Node\Stmt\Class_;
use Transunit\Pass;

class ClassnamePass implements Pass
{
public function find(NodeFinder $nodeFinder, $ast): array
{
return [$nodeFinder->findFirstInstanceOf($ast, Class_::class)];
}

public function rewrite(Node $node): void
{
if (!$node instanceof Class_) {
return;
}

$this->renameClass($node);
$this->changeExtendedClass($node);
$this->useProphecyTrait($node);
}

private function renameClass(Class_ $node): void
{
$sourceClassname = $node->name->toString();

if (substr($sourceClassname, -4) !== 'Spec') {
return;
}

$targetClassname = substr_replace($sourceClassname, '', -4).'Test';

$node->name = new Node\Identifier($targetClassname);
}

private function changeExtendedClass(Class_ $node): void
{
if ($node->extends->toString() === 'ObjectBehavior') {
$node->extends = new Node\Name('TestCase');
}
}

private function useProphecyTrait(Class_ $node): void
{
$node->stmts = array_merge(
[new Node\Stmt\TraitUse([new Node\Name('ProphecyTrait')])],
$node->stmts
);
}
}
Loading

0 comments on commit 8251fdf

Please sign in to comment.