Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test generator, step 1 #638

Merged
merged 29 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
06b500f
Import test generator POC of tomasnorre
mk-mxp Feb 23, 2024
1f344bc
Mark PHPUnit config in root as unused
mk-mxp Feb 23, 2024
44ea6e7
Delete hardcoded POC data, disable test generation
mk-mxp Feb 23, 2024
8b18ec3
Require exercise slug as argument
mk-mxp Feb 23, 2024
d13002c
Sort `use` by alphabet
mk-mxp Feb 23, 2024
761964c
Make BuilderFactory a private class instance
mk-mxp Feb 23, 2024
3f72223
Ensure configlet is usable
mk-mxp Feb 24, 2024
25b3d18
Ensure exercise directory is usable
mk-mxp Feb 24, 2024
65887a4
Construct path to canonical data from configlet
mk-mxp Feb 24, 2024
7c273f7
Extract ensureConfigletCanBeUsed()
mk-mxp Feb 24, 2024
69564c9
Extract ensurePracticeExerciseCanBeUsed()
mk-mxp Feb 24, 2024
3815922
Extract canonical data handling
mk-mxp Feb 24, 2024
b7be21f
Sort out test creation things
mk-mxp Feb 24, 2024
1f8bc15
Inject project dir and make track root from it
mk-mxp Feb 24, 2024
eec68e4
Extract class PracticeExercise
mk-mxp Feb 25, 2024
74c2153
Extract class TestGenerator
mk-mxp Feb 25, 2024
0d54518
Generate test stubs from problem specification
mk-mxp Feb 25, 2024
2faf87d
Move method inPascalCase to command
mk-mxp Feb 25, 2024
ec01896
Rename $classBuilder to $class
mk-mxp Feb 25, 2024
2bd8ede
Use class constant instead of string
mk-mxp Feb 25, 2024
66e8eb5
Add test class doc block from exercise comment
mk-mxp Feb 25, 2024
78a44b6
Add testdox to test methods
mk-mxp Feb 25, 2024
cec0d8a
Add use for PHPUnit TestCase
mk-mxp Feb 25, 2024
bd22d0c
Mark generated tests as incomplete
mk-mxp Feb 25, 2024
4bb8fa8
Update README, more TODOs
mk-mxp Feb 25, 2024
c7f7a03
Fix code styles
mk-mxp Feb 25, 2024
18295d5
Specify array type of $comments
mk-mxp Feb 26, 2024
f12ea9f
Explain configlet command in comment
mk-mxp Feb 26, 2024
51297a4
Remove unrequired PHPUnit config file
mk-mxp Mar 3, 2024
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The following system dependencies are required:

- `composer`, as recommended in the [PHP track installation docs][exercism-track-installation-composer].
- [`bash` shell][gnu-bash]
- PHP V8.2+ CLI

Run the following commands to get started with this project:

Expand Down Expand Up @@ -51,6 +52,20 @@ composer lint:fix # Automatically fix codestyle issues
- CI is run on all pull requests, it must pass the required checks for merge.
- CI is running all tests on PHP 8.0 to PHP 8.2

## Generating new practice exercises

Use `bin/configlet create --practice-exercise <slug>` to create the exercism resources required.
This provides you with the directories and files in `exercises/practice/<slug>`.
Look into `tests.toml` for which test cases **not** to implement / generate and mark them with `include = false`.

Test generator MVP used like this:

```shell
composer -d contribution/generator install
contribution/generator/bin/console app:create-tests '<slug>'
composer lint:fix
```

[exercism-configlet]: https://exercism.org/docs/building/configlet
[exercism-docs]: https://exercism.org/docs
[exercism-track-home]: https://exercism.org/docs/tracks/php
Expand Down
20 changes: 20 additions & 0 deletions contribution/generator/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=1be0b2dbd34333efb23cfefcff0ff718
###< symfony/framework-bundle ###
6 changes: 6 additions & 0 deletions contribution/generator/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
17 changes: 17 additions & 0 deletions contribution/generator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# IDEs
.idea

###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###

###> phpunit/phpunit ###
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###
20 changes: 20 additions & 0 deletions contribution/generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Auto Creating of tests for Exercism PHP Track

This is a small poc on how we could auto generate tests for the PHP track based on the https://github.com/exercism/problem-specifications/.

How to test it:

```
git clone https://github.com/tomasnorre/exercism-tests-generation.git
cd exercism-tests-generation
composer install
bin/console app:create-tests
vendor/bin/phpunit src/Command/NucleotideCountTest.php
```

If you now make a `git status` you will see that the `src/Command/NucleotideCountTest.php` and you can now inspect the auto generated tests.

It's all based on the `nikic/php-parser` and the https://github.com/exercism/problem-specifications/ repository, I have made a local copy of that on file
for now to spare the http-requests.

Let me know what you think.
17 changes: 17 additions & 0 deletions contribution/generator/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;

if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

return new Application($kernel);
};
71 changes: 71 additions & 0 deletions contribution/generator/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"nikic/php-parser": "^5.0",
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "7.0.*",
"symfony/runtime": "7.0.*",
"symfony/yaml": "7.0.*"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"symfony/maker-bundle": "^1.54"
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "7.0.*"
}
}
}
6 changes: 6 additions & 0 deletions contribution/generator/config/bundles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
];
19 changes: 19 additions & 0 deletions contribution/generator/config/packages/cache.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name

# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:

# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost

# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu

# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null
16 changes: 16 additions & 0 deletions contribution/generator/config/packages/framework.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true

# Note that the session will be started ONLY if you read or write from it.
session: true

#esi: true
#fragments: true

when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
10 changes: 10 additions & 0 deletions contribution/generator/config/packages/routing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost

when@prod:
framework:
router:
strict_requirements: null
5 changes: 5 additions & 0 deletions contribution/generator/config/preload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}
5 changes: 5 additions & 0 deletions contribution/generator/config/routes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
controllers:
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute
4 changes: 4 additions & 0 deletions contribution/generator/config/routes/framework.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error
26 changes: 26 additions & 0 deletions contribution/generator/config/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:

services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$projectDir: '%kernel.project_dir%'

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
27 changes: 27 additions & 0 deletions contribution/generator/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
</php>

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

<extensions>
</extensions>
</phpunit>
9 changes: 9 additions & 0 deletions contribution/generator/public/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
71 changes: 71 additions & 0 deletions contribution/generator/src/Command/CreateTestsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace App\Command;

use App\TestGenerator;
use App\TrackData\PracticeExercise;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'app:create-tests',
description: 'This will automatically create tests',
)]
class CreateTestsCommand extends Command
{
private string $trackRoot;

public function __construct(
private string $projectDir,
) {
$this->trackRoot = realpath($projectDir . '/../..');

parent::__construct();
}

protected function configure(): void
{
$this->addArgument('exercise', InputArgument::REQUIRED, 'Exercise slug');
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$exerciseSlug = $input->getArgument('exercise');
$exercise = new PracticeExercise(
$this->trackRoot,
$exerciseSlug,
);
$testGenerator = new TestGenerator();

$io = new SymfonyStyle($input, $output);
$io->writeln('Generating tests for ' . $exerciseSlug . ' in ' . $exercise->pathToExercise());

\file_put_contents(
// TODO: Make '$exercise->pathToTestFile()'
$exercise->pathToExercise()
. '/'
. $this->inPascalCase($exerciseSlug)
. 'Test.php',
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
$testGenerator->createTestsFor(
$exercise->canonicalData(),
$this->inPascalCase($exerciseSlug)
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
),
);
// TODO: Make '$exercise->pathToStudentsFile()'
// TODO: Make '$testGenerator->studentsFileFor()'

$io->success('Generating Tests - Finished');
return Command::SUCCESS;
}

private function inPascalCase(string $text): string
{
return \str_replace(" ", "", \ucwords(\str_replace("-", " ", $text)));
}
}
Empty file.
Loading