update v 1.0.7.5

This commit is contained in:
Sujit Prasad
2016-06-13 20:41:55 +05:30
parent aa9786d829
commit 283d97e3ea
5078 changed files with 339851 additions and 175995 deletions

View File

@@ -18,7 +18,7 @@ tools:
excluded-paths: [ spec/*, integration/* ]
build_failure_conditions:
- 'issues.label("coding-style").exists'
- 'issues.label("coding-style").new.exists'
filter:
excluded_paths:

View File

@@ -24,6 +24,7 @@ matrix:
before_install:
- composer selfupdate
- if [ "$DEPENDENCIES" == "low" ]; then composer config disable-tls true; fi;
install:
- export COMPOSER_ROOT_VERSION=dev-master

View File

@@ -1,4 +1,27 @@
2.4.1 / 2016/01/01
2.5.0 / 2016-03-20
==================
* No changes from RC1
2.5.0-rc1 / 2016-03-12
======================
* Fixed bug with typehints in classes defined in spec file
2.5.0-beta / 2016-02-16
=======================
* Supports grouped Use statements
* Now shows path in error message when spec file doesn't contain a class
* Supports catching PHP 7 Errors in shouldThrow
* No longer attempts to generate methods with reserved names
* Fixed bug where bootstrapped classes could not be loaded after class generation
* Fixed bug where line numbers were incorrectly reported on PHP 7
* Fixed new methods being inserted incorrectly when strings included closing brace
* Dot formatter now shows spec count on last line
2.4.1 / 2016-01-01
==================
* Correctly handle nested class definitions
@@ -9,12 +32,12 @@
* Handle underscores correctly when using PSR-4
* Fixed HTML formatter
2.4.0 / 2015/11/28
2.4.0 / 2015-11-28
==================
* Improved docblock for beConstructedThrough()
2.4.0-rc1 / 2015/11/20
2.4.0-rc1 / 2015-11-20
======================
* No changes from RC1

View File

@@ -11,6 +11,12 @@ The main website with documentation is at `http://www.phpspec.net <http://www.ph
:target: https://scrutinizer-ci.com/g/phpspec/phpspec/build-status/master
:alt: Master Scrutinizer Quality Score
.. image:: https://ci.appveyor.com/api/projects/status/wce4nun9re76ocp6/branch/master?svg=true
:target: https://ci.appveyor.com/project/ciaranmcnulty/phpspec/branch/master
:alt: AppVeyor build status
Installing Dependencies
-----------------------

View File

@@ -7,17 +7,12 @@ environment:
matrix:
- PHP_DOWNLOAD_FILE: php-5.6.14-nts-Win32-VC11-x86.zip
matrix:
allow_failures:
- PHP_DOWNLOAD_FILE: php-5.6.14-nts-Win32-VC11-x86.zip
skip_commits:
message: /\[ci skip\]/
cache:
- c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer'
- vendor
init:
- SET PATH=c:\php;%PATH%
@@ -48,4 +43,4 @@ test_script:
- cd c:\projects\phpspec
- php bin\phpspec run --format=pretty
- php vendor\phpunit\phpunit\phpunit --testdox
- php vendor\behat\behat\bin\behat --format=pretty --tags="~@php-version,@php5.4"
- php vendor\behat\behat\bin\behat --format=pretty --tags="~@php-version,@php5.4&&~@hhvm"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env php
<?php
define('PHPSPEC_VERSION', '2.4.1');
define('PHPSPEC_VERSION', '2.5.0');
if (is_file($autoload = getcwd() . '/vendor/autoload.php')) {
require $autoload;

View File

@@ -108,7 +108,10 @@ class ApplicationContext implements Context, MatchersProviderInterface
$this->addOptionToArguments($option, $arguments);
$this->lastExitCode = $this->tester->run($arguments, array('interactive' => (bool)$interactive));
$this->lastExitCode = $this->tester->run($arguments, array(
'interactive' => (bool)$interactive,
'decorated' => false,
));
}
/**

View File

@@ -7,6 +7,7 @@ use Behat\Gherkin\Node\TableNode;
use Matcher\FileExistsMatcher;
use Matcher\FileHasContentsMatcher;
use PhpSpec\Matcher\MatchersProviderInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
@@ -51,7 +52,11 @@ class FilesystemContext implements Context, MatchersProviderInterface
*/
public function removeWorkingDirectory()
{
$this->filesystem->remove($this->workingDirectory);
try {
$this->filesystem->remove($this->workingDirectory);
} catch (IOException $e) {
//ignoring exception
}
}
/**

View File

@@ -40,8 +40,8 @@ class IsolatedProcessContext implements Context, SnippetAcceptingContext
$command = sprintf('%s %s', $this->buildPhpSpecCmd(), 'run');
$env = array(
'SHELL_INTERACTIVE' => true,
'HOME' => $_SERVER['HOME'],
'PATH' => $_SERVER['PATH']
'HOME' => getenv('HOME'),
'PATH' => getenv('PATH'),
);
$this->process = $process = new Process($command);
@@ -56,7 +56,13 @@ class IsolatedProcessContext implements Context, SnippetAcceptingContext
*/
protected function buildPhpSpecCmd()
{
return escapeshellcmd(__DIR__ . '/../../bin/phpspec');
$isWindows = DIRECTORY_SEPARATOR === '\\';
$cmd = escapeshellcmd('' . __DIR__ . '/../../bin/phpspec');
if ($isWindows) {
$cmd = 'php ' . $cmd;
}
return $cmd;
}
/**

View File

@@ -33,7 +33,7 @@ class ApplicationOutputMatcher implements MatcherInterface
public function positiveMatch($name, $subject, array $arguments)
{
$expected = $this->normalize($arguments[0]);
$actual = $this->normalize($subject->getDisplay());
$actual = $this->normalize($subject->getDisplay(true));
if (strpos($actual, $expected) === false) {
throw new FailureException(sprintf(
"Application output did not contain expected '%s'. Actual output:\n'%s'" ,

View File

@@ -0,0 +1,133 @@
@php-version @php7
Feature: Developer generates a collaborator
As a Developer
I want to automate creating collaborators
In order to avoid disrupting my workflow
Scenario: Being prompted but not generating a collaborator
Given the spec file "spec/CodeGeneration/CollaboratorExample1/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample1;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample1\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample1/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample1;
class Markdown1
{
}
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should be prompted with:
"""
Would you like me to generate an interface
`CodeGeneration\CollaboratorExample1\Parser` for you?
[Y/n]
"""
Scenario: Asking for interface to be generated for second collaborator in group
Given the spec file "spec/CodeGeneration/CollaboratorExample2/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample2;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample2\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample2/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample2;
class Markdown1
{
}
"""
When I run phpspec and answer "y" when asked if I want to generate the code
Then the class in "src/CodeGeneration/CollaboratorExample2/Parser.php" should contain:
"""
<?php
namespace CodeGeneration\CollaboratorExample2;
interface Parser
{
}
"""
Scenario: Asking for interface to be generated for the first collaborator in group
Given the spec file "spec/CodeGeneration/CollaboratorExample3/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample3;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample3\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Tokenizer $tokenizer)
{
$tokenizer->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample3/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample3;
class Markdown1
{
}
"""
When I run phpspec and answer "y" when asked if I want to generate the code
Then the class in "src/CodeGeneration/CollaboratorExample3/Tokenizer.php" should contain:
"""
<?php
namespace CodeGeneration\CollaboratorExample3;
interface Tokenizer
{
}
"""

View File

@@ -264,3 +264,48 @@ Feature: Developer generates a collaborator's method
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should not be prompted for code generation
Scenario: Being warned when a collaborator method is a restricted word
Given the spec file "spec/CodeGeneration/CollaboratorMethodExample6/MarkdownSpec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorMethodExample6;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorMethodExample1\Parser;
class MarkdownSpec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->throw()->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorMethodExample6/Markdown.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorMethodExample6;
class Markdown
{
}
"""
And the class file "src/CodeGeneration/CollaboratorMethodExample6/Parser.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorMethodExample6;
interface Parser
{
}
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should see "I cannot generate the method 'throw' for you"

View File

@@ -371,3 +371,47 @@ Feature: Developer generates a method
}
"""
Scenario: Generating a method named with a restricted keyword
Given the spec file "spec/MyNamespace/RestrictedSpec.php" contains:
"""
<?php
namespace spec\MyNamespace;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RestrictedSpec extends ObjectBehavior
{
function it_tries_to_call_wrong_method()
{
$this->throw()->shouldReturn();
}
}
"""
And the class file "src/MyNamespace/Restricted.php" contains:
"""
<?php
namespace MyNamespace;
class Restricted
{
}
"""
When I run phpspec interactively
Then I should see "I cannot generate the method 'throw' for you"
And the class in "src/MyNamespace/Restricted.php" should contain:
"""
<?php
namespace MyNamespace;
class Restricted
{
}
"""

View File

@@ -396,7 +396,7 @@ Feature: Developer is shown diffs
"""
method call:
- methodTwo("value")
on Double\Diffs\DiffExample7\ClassBeingMocked\P13 was not expected, expected calls were:
on Double\Diffs\DiffExample7\ClassBeingMocked\P14 was not expected, expected calls were:
- methodOne(exact("value"))
"""
@@ -460,7 +460,7 @@ Feature: Developer is shown diffs
"""
method call:
- methodTwo("another value")
on Double\Diffs\DiffExample8\ClassBeingMocked\P14 was not expected, expected calls were:
on Double\Diffs\DiffExample8\ClassBeingMocked\P15 was not expected, expected calls were:
- methodTwo(exact("value"))
- methodOne(exact("another value"))
"""

View File

@@ -102,4 +102,27 @@ class TokenizedNamespaceResolverSpec extends ObjectBehavior
$this->resolve('Foo')->shouldReturn('Baz\Foo');
$this->resolve('Bar\Baz')->shouldReturn('Boz\Bar\Baz');
}
function it_resolves_types_from_grouped_use_statements()
{
$this->analyse('
<?php
namespace Baz;
use Boz\{Fiz, Buz};
class Foo
{
function it_something(Fiz $fiz, Buz $buz)
{
}
}
');
$this->resolve('Fiz')->shouldReturn('Boz\Fiz');
$this->resolve('Buz')->shouldReturn('Boz\Buz');
}
}

View File

@@ -25,7 +25,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar()
{
@@ -35,7 +35,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar()
{
@@ -50,7 +50,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(\Foo\Bar $bar)
{
@@ -60,9 +60,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar)
public function bar( $bar)
{
}
}
@@ -75,7 +75,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(\Foo\Bar $bar)
{
@@ -89,9 +89,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar)
public function bar( $bar)
{
new class($argument) implements InterfaceName
{
@@ -108,7 +108,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(Bar $bar, Baz $baz)
{
@@ -118,9 +118,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar,$baz)
public function bar( $bar, $baz)
{
}
}
@@ -132,14 +132,14 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
{
$namespaceResolver->analyse(Argument::any())->shouldBeCalled();
$namespaceResolver->resolve('Foo')->willReturn('Foo');
$namespaceResolver->resolve('FooSpec')->willReturn('FooSpec');
$namespaceResolver->resolve('Foo\Bar')->willReturn('Foo\Bar');
$namespaceResolver->resolve('Baz')->willReturn('Baz');
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(Foo\Bar $bar, Baz $baz)
{
@@ -148,8 +148,8 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
');
$typeHintIndex->add('Foo', 'bar', '$bar', 'Foo\Bar')->shouldHaveBeenCalled();
$typeHintIndex->add('Foo', 'bar', '$baz', 'Baz')->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$bar', 'Foo\Bar')->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$baz', 'Baz')->shouldHaveBeenCalled();
}
function it_indexes_invalid_typehints(
@@ -159,13 +159,13 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$e = new DisallowedScalarTypehintException();
$namespaceResolver->analyse(Argument::any())->shouldBeCalled();
$namespaceResolver->resolve('Foo')->willReturn('Foo');
$namespaceResolver->resolve('FooSpec')->willReturn('FooSpec');
$namespaceResolver->resolve('int')->willThrow($e);
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(int $bar)
{
@@ -174,7 +174,79 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
');
$typeHintIndex->addInvalid('Foo', 'bar', '$bar', $e)->shouldHaveBeenCalled();
$typeHintIndex->add('Foo', 'bar', '$bar', Argument::any())->shouldNotHaveBeenCalled();
$typeHintIndex->addInvalid('FooSpec', 'bar', '$bar', $e)->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$bar', Argument::any())->shouldNotHaveBeenCalled();
}
function it_preserves_line_numbers()
{
$this->rewrite('
<?php
class FooSpec
{
public function(
$foo,
array $bar,
Foo\Bar $arg3,
$arg4
)
{
}
}
')->shouldReturn('
<?php
class FooSpec
{
public function(
$foo,
array $bar,
$arg3,
$arg4
)
{
}
}
');
}
function it_do_not_remove_typehints_of_non_spec_classes()
{
$this->rewrite('
<?php
class FooSpec
{
public function bar(Bar $bar, Baz $baz)
{
}
}
class Bar
{
public function foo(Baz $baz)
{
}
}
')->shouldReturn('
<?php
class FooSpec
{
public function bar( $bar, $baz)
{
}
}
class Bar
{
public function foo(Baz $baz)
{
}
}
');
}
}

View File

@@ -62,6 +62,15 @@ class TokenizedCodeWriterSpec extends ObjectBehavior
$this->shouldThrow($exception)->during('insertAfterMethod', array($class, 'methodOne', ''));
}
function it_should_generate_a_method_in_a_class_with_a_string_containing_braces()
{
$class = $this->getClassWithBraceText();
$method = $this->getMethod();
$result = $this->getClassWithBraceTextAndNewMethod();
$this->insertMethodLastInClass($class, $method)->shouldReturn($result);
}
private function getSingleMethodClass()
{
return <<<SINGLE_METHOD_CLASS
@@ -232,4 +241,43 @@ final class MyClass
}
ONLY_NEW_METHOD_CLASS;
}
private function getClassWithBraceText()
{
return <<<'BRACE_TEXT_CLASS'
<?php
namespace MyNamespace;
final class MyClass
{
public function braceMethod()
{
return "{$foo}";
}
}
BRACE_TEXT_CLASS;
}
private function getClassWithBraceTextAndNewMethod()
{
return <<<'BRACE_TEXT_RESULT_CLASS'
<?php
namespace MyNamespace;
final class MyClass
{
public function braceMethod()
{
return "{$foo}";
}
public function newMethod()
{
return 'newSomething';
}
}
BRACE_TEXT_RESULT_CLASS;
}
}

View File

@@ -180,4 +180,36 @@ class IOSpec extends ObjectBehavior
$this->getBlockWidth()->shouldReturn(65);
}
function it_writes_a_message_about_broken_code(OutputInterface $output)
{
$message = 'Error message';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg>Error message </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message);
}
function it_wraps_long_broken_message(OutputInterface $output)
{
$message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro maximus nulla eget libero rhoncus lacinia.';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro</broken-bg>')->shouldBeCalled();
$output->writeln('<broken-bg>maximus nulla eget libero rhoncus lacinia. </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message);
}
function it_indents_and_wraps_long_broken_message(OutputInterface $output)
{
$message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin maximus nulla eget libero rhoncus lacinia.';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </broken-bg>')->shouldBeCalled();
$output->writeln('<broken-bg> Proin maximus nulla eget libero rhoncus lacinia. </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message, 2);
}
}

View File

@@ -45,4 +45,12 @@ class SuiteEventSpec extends ObjectBehavior
$this->markAsWorthRerunning();
$this->isWorthRerunning()->shouldReturn(true);
}
function it_can_be_told_that_the_suite_is_no_longer_worth_rerunning()
{
$this->markAsWorthRerunning();
$this->markAsNotWorthRerunning();
$this->isWorthRerunning()->shouldReturn(false);
}
}

View File

@@ -26,6 +26,6 @@ class StringEngineSpec extends ObjectBehavior
</code>
DIFF;
$this->compare('string1', 'string2')->shouldReturn(str_replace("\n", PHP_EOL, $expected));
$this->compare('string1', 'string2')->shouldReturn($expected);
}
}

View File

@@ -11,19 +11,19 @@ use PhpSpec\Locator\ResourceInterface;
use PhpSpec\Locator\ResourceManager;
use PhpSpec\Locator\ResourceManagerInterface;
use PhpSpec\ObjectBehavior;
use PhpSpec\Util\NameCheckerInterface;
use Prophecy\Argument;
use Prophecy\Doubler\DoubleInterface;
use Prophecy\Exception\Doubler\MethodNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
{
function let(
IO $io, ResourceManagerInterface $resources, ExampleEvent $event,
MethodNotFoundException $exception, ResourceInterface $resource, GeneratorManager $generator
)
{
$this->beConstructedWith($io, $resources, $generator);
MethodNotFoundException $exception, ResourceInterface $resource, GeneratorManager $generator,
NameCheckerInterface $nameChecker
) {
$this->beConstructedWith($io, $resources, $generator, $nameChecker);
$event->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
@@ -32,6 +32,7 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
$resources->createResource(Argument::any())->willReturn($resource);
$exception->getArguments()->willReturn(array());
$nameChecker->isNameValid('aMethod')->willReturn(true);
}
function it_is_an_event_subscriber()
@@ -72,7 +73,7 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
function it_does_not_prompt_when_wrong_exception_is_thrown(IO $io, ExampleEvent $event, SuiteEvent $suiteEvent)
{
$event->getException()->willReturn(new RuntimeException());
$event->getException()->willReturn(new \RuntimeException());
$this->afterExample($event);
$this->afterSuite($suiteEvent);
@@ -153,6 +154,49 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
$suiteEvent->markAsWorthRerunning()->shouldHaveBeenCalled();
}
function it_warns_if_a_method_name_is_wrong(
ExampleEvent $event,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$exception = new MethodNotFoundException('Error', new DoubleOfInterface(), 'throw');
$event->getException()->willReturn($exception);
$nameChecker->isNameValid('throw')->willReturn(false);
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation(Argument::any())->shouldNotBeCalled();
$this->afterExample($event);
$this->afterSuite($suiteEvent);
}
function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved(
ExampleEvent $event,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($event, $nameChecker, 'throw', false);
$this->callAfterExample($event, $nameChecker, 'foo');
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation(Argument::any())->shouldBeCalled();
$suiteEvent->markAsNotWorthRerunning()->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
private function callAfterExample($event, $nameChecker, $method, $isNameValid = true)
{
$exception = new MethodNotFoundException('Error', new DoubleOfInterface(), $method);
$event->getException()->willReturn($exception);
$nameChecker->isNameValid($method)->willReturn($isNameValid);
$this->afterExample($event);
}
}
interface ExampleInterface {}

View File

@@ -11,22 +11,27 @@ use PhpSpec\CodeGenerator\GeneratorManager;
use PhpSpec\Event\ExampleEvent;
use PhpSpec\Event\SuiteEvent;
use PhpSpec\Exception\Fracture\MethodNotFoundException;
use PhpSpec\Util\NameCheckerInterface;
class MethodNotFoundListenerSpec extends ObjectBehavior
{
function let(IO $io, ResourceManager $resourceManager, GeneratorManager $generatorManager,
SuiteEvent $suiteEvent, ExampleEvent $exampleEvent)
{
function let(
IO $io,
ResourceManager $resourceManager,
GeneratorManager $generatorManager,
SuiteEvent $suiteEvent,
ExampleEvent $exampleEvent,
NameCheckerInterface $nameChecker
) {
$io->writeln(Argument::any())->willReturn();
$io->askConfirmation(Argument::any())->willReturn();
$this->beConstructedWith($io, $resourceManager, $generatorManager);
$this->beConstructedWith($io, $resourceManager, $generatorManager, $nameChecker);
$io->isCodeGenerationEnabled()->willReturn(true);
}
function it_does_not_prompt_for_method_generation_if_no_exception_was_thrown($exampleEvent, $suiteEvent, $io)
{
$io->isCodeGenerationEnabled()->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -36,7 +41,6 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
function it_does_not_prompt_for_method_generation_if_non_methodnotfoundexception_was_thrown($exampleEvent, $suiteEvent, $io, \InvalidArgumentException $exception)
{
$exampleEvent->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -44,10 +48,16 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
$io->askConfirmation(Argument::any())->shouldNotBeenCalled();
}
function it_prompts_for_method_generation_if_methodnotfoundexception_was_thrown_and_input_is_interactive($exampleEvent, $suiteEvent, $io, MethodNotFoundException $exception)
{
function it_prompts_for_method_generation_if_methodnotfoundexception_was_thrown_and_input_is_interactive(
$exampleEvent,
$suiteEvent,
$io,
NameCheckerInterface $nameChecker
) {
$exception = new MethodNotFoundException('Error', new \stdClass(), 'bar');
$exampleEvent->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
$nameChecker->isNameValid('bar')->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -65,4 +75,42 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
$io->askConfirmation(Argument::any())->shouldNotBeenCalled();
}
function it_warns_when_method_name_is_reserved(
$exampleEvent,
$suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($exampleEvent, $nameChecker, 'throw', false);
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved(
$exampleEvent,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($exampleEvent, $nameChecker, 'throw', false);
$this->callAfterExample($exampleEvent, $nameChecker, 'foo');
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation('Do you want me to create `stdClass::foo()` for you?')->shouldBeCalled();
$suiteEvent->markAsNotWorthRerunning()->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
private function callAfterExample($exampleEvent, $nameChecker, $method, $isNameValid = true)
{
$exception = new MethodNotFoundException('Error', new \stdClass(), $method);
$exampleEvent->getException()->willReturn($exception);
$nameChecker->isNameValid($method)->willReturn($isNameValid);
$this->afterExample($exampleEvent);
}
}

View File

@@ -326,7 +326,7 @@ class PSR0LocatorSpec extends ObjectBehavior
$fs->getFileContents($filePath)->willReturn('no class definition');
$file->getRealPath()->willReturn($filePath);
$exception = new \RuntimeException('Spec file does not contains any class definition.');
$exception = new \RuntimeException(sprintf('Spec file "%s" does not contains any class definition.', $filePath));
$this->shouldThrow($exception)->duringFindResources($this->srcPath);
}

View File

@@ -99,11 +99,21 @@ class PSR0ResourceSpec extends ObjectBehavior
{
$this->beConstructedWith(array('usr', 'lib', 'config_test'), $locator);
$locator->getFullSrcPath()->willReturn('/local/');
$locator->getFullSpecPath()->willReturn('/local/spec/');
$locator->getFullSrcPath()->willReturn($this->convert_to_path('/local/'));
$locator->getFullSpecPath()->willReturn($this->convert_to_path('/local/spec/'));
$locator->isPSR4()->willReturn(true);
$this->getSrcFilename()->shouldReturn('/local/usr/lib/config_test.php');
$this->getSpecFilename()->shouldReturn('/local/spec/usr/lib/config_testSpec.php');
$this->getSrcFilename()->shouldReturn($this->convert_to_path('/local/usr/lib/config_test.php'));
$this->getSpecFilename()->shouldReturn($this->convert_to_path('/local/spec/usr/lib/config_testSpec.php'));
}
private function convert_to_path($path)
{
if ('/' === DIRECTORY_SEPARATOR) {
return $path;
}
return str_replace('/', DIRECTORY_SEPARATOR, $path);
}
}

View File

@@ -7,6 +7,7 @@ use Prophecy\Argument;
use PhpSpec\Wrapper\Unwrapper;
use PhpSpec\Formatter\Presenter\PresenterInterface;
use PhpSpec\Exception\Example\SkippingException;
use ArrayObject;
@@ -32,8 +33,28 @@ class ThrowMatcherSpec extends ObjectBehavior
$this->positiveMatch('throw', $arr, array('\Exception'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_error_should_be_thrown(ArrayObject $arr)
{
if (!class_exists('\Error')) {
throw new SkippingException('The class Error, introduced in PHP 7, does not exist');
}
$arr->ksort()->willThrow('\Error');
$this->positiveMatch('throw', $arr, array('\Error'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_exception_should_not_be_thrown(ArrayObject $arr)
{
$this->negativeMatch('throw', $arr, array('\Exception'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_error_should_not_be_thrown(ArrayObject $arr)
{
if (!class_exists('\Error')) {
throw new SkippingException('The class Error, introduced in PHP 7, does not exist');
}
$this->negativeMatch('throw', $arr, array('\Error'))->during('ksort', array());
}
}

View File

@@ -15,14 +15,12 @@ class CurrentExampleTrackerSpec extends ObjectBehavior
function it_should_set_a_message()
{
$currentExample = new CurrentExampleTracker();
$currentExample->setCurrentExample('test');
expect($currentExample->getCurrentExample())->toBe('test');
$this->setCurrentExample('test');
$this->getCurrentExample()->shouldBe('test');
}
function it_should_be_null_on_construction()
{
$currentExample = new CurrentExampleTracker();
expect($currentExample->getCurrentExample())->toBe(null);
$this->getCurrentExample()->shouldBe(null);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace spec\PhpSpec\Util;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ReservedWordsMethodNameCheckerSpec extends ObjectBehavior
{
function it_is_restriction_provider()
{
$this->shouldHaveType('PhpSpec\Util\NameCheckerInterface');
}
function it_returns_true_for_not_php_restricted_name()
{
$this->isNameValid('foo')->shouldReturn(true);
}
function it_returns_false_for_php_restricted_name()
{
$this->isNameValid('function')->shouldReturn(false);
}
function it_returns_false_for_php_predefined_constant()
{
$this->isNameValid('__CLASS__')->shouldReturn(false);
}
function it_returns_false_for_php_restricted_name_case_insensitive()
{
$this->isNameValid('instanceof')->shouldReturn(false);
$this->isNameValid('instanceOf')->shouldReturn(false);
}
}

View File

@@ -18,10 +18,12 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
const STATE_DEFAULT = 0;
const STATE_READING_NAMESPACE = 1;
const STATE_READING_USE = 2;
const STATE_READING_USE_GROUP = 3;
private $state = self::STATE_DEFAULT;
private $currentNamespace;
private $currentUseGroup;
private $currentUse;
private $uses = array();
@@ -32,6 +34,7 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
{
$this->state = self::STATE_DEFAULT;
$this->currentUse = null;
$this->currentUseGroup = null;
$this->uses = array();
$tokens = token_get_all($code);
@@ -48,12 +51,29 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
$this->currentNamespace .= $token[1];
}
break;
case self::STATE_READING_USE_GROUP:
if ('}' == $token) {
$this->state = self::STATE_READING_USE;
$this->currentUseGroup = null;
}
elseif (',' == $token) {
$this->storeCurrentUse();
}
elseif (is_array($token)) {
$this->currentUse = $this->currentUseGroup . trim($token[1]);
}
break;
case self::STATE_READING_USE:
if (';' == $token) {
$this->storeCurrentUse();
$this->state = self::STATE_DEFAULT;
}
if (',' == $token) {
if ('{' == $token) {
$this->currentUseGroup = trim($this->currentUse);
$this->state = self::STATE_READING_USE_GROUP;
}
elseif (',' == $token) {
$this->storeCurrentUse();
}
elseif (is_array($token)) {

View File

@@ -74,6 +74,7 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$this->currentClass = '';
$this->currentFunction = '';
}
/**
* @param array $tokens
* @return array $tokens
@@ -81,6 +82,13 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
private function stripTypeHints($tokens)
{
foreach ($tokens as $index => $token) {
if ($this->isToken($token, '{')) {
$this->currentBodyLevel++;
}
elseif ($this->isToken($token, '}')) {
$this->currentBodyLevel--;
}
switch ($this->state) {
case self::STATE_READING_ARGUMENTS:
if (')' == $token) {
@@ -103,24 +111,21 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$this->state = self::STATE_READING_FUNCTION_BODY;
$this->currentBodyLevel = 1;
}
elseif ($this->tokenHasType($token, T_STRING) && !$this->currentClass) {
elseif ('}' == $token && $this->currentClass) {
$this->state = self::STATE_DEFAULT;
$this->currentClass = null;
}
elseif ($this->tokenHasType($token, T_STRING) && !$this->currentClass && $this->shouldExtractTokensOfClass($token[1])) {
$this->currentClass = $token[1];
}
elseif($this->tokenHasType($token, T_FUNCTION)) {
elseif ($this->tokenHasType($token, T_FUNCTION) && $this->currentClass) {
$this->state = self::STATE_READING_FUNCTION;
}
break;
case self::STATE_READING_FUNCTION_BODY:
if ('{' == $token) {
$this->currentBodyLevel++;
}
elseif ('}' == $token) {
$this->currentBodyLevel--;
if ($this->currentBodyLevel === 0) {
$this->currentFunction = '';
$this->state = self::STATE_READING_CLASS;
}
if ('}' == $token && $this->currentBodyLevel === 0) {
$this->currentFunction = '';
$this->state = self::STATE_READING_CLASS;
}
break;
@@ -155,7 +160,10 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$typehint = '';
for ($i = $index - 1; in_array($tokens[$i][0], $this->typehintTokens); $i--) {
$typehint = $tokens[$i][1] . $typehint;
unset($tokens[$i]);
if (T_WHITESPACE !== $tokens[$i][0]) {
unset($tokens[$i]);
}
}
if ($typehint = trim($typehint)) {
@@ -189,4 +197,25 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
{
return is_array($token) && $type == $token[0];
}
/**
* @param string $className
*
* @return bool
*/
private function shouldExtractTokensOfClass($className)
{
return substr($className, -4) == 'Spec';
}
/**
* @param array|string $token
* @param string $string
*
* @return bool
*/
private function isToken($token, $string)
{
return $token == $string || (is_array($token) && $token[1] == $string);
}
}

View File

@@ -87,11 +87,12 @@ final class TokenizedCodeWriter implements CodeWriter
$lastLines = array_slice($lines, $line);
$toInsert = trim($toInsert, "\n\r");
if ($leadingNewline) {
$toInsert = PHP_EOL . $toInsert;
$toInsert = "\n" . $toInsert;
}
array_unshift($lastLines, $toInsert);
array_splice($lines, $line, count($lines), $lastLines);
return implode(PHP_EOL, $lines);
return implode("\n", $lines);
}
/**
@@ -105,9 +106,10 @@ final class TokenizedCodeWriter implements CodeWriter
$line--;
$lines = explode("\n", $target);
$lastLines = array_slice($lines, $line);
array_unshift($lastLines, trim($toInsert, "\n\r") . PHP_EOL);
array_unshift($lastLines, trim($toInsert, "\n\r") . "\n");
array_splice($lines, $line, count($lines), $lastLines);
return implode(PHP_EOL, $lines);
return implode("\n", $lines);
}
/**
@@ -120,12 +122,13 @@ final class TokenizedCodeWriter implements CodeWriter
{
$tokens = token_get_all($class);
$searching = false;
$inString = false;
$searchPattern = array();
for ($i = count($tokens) - 1; $i >= 0; $i--) {
$token = $tokens[$i];
if ($token === '}') {
if ($token === '}' && !$inString) {
$searching = true;
continue;
}
@@ -134,6 +137,11 @@ final class TokenizedCodeWriter implements CodeWriter
continue;
}
if ($token === '"') {
$inString = !$inString;
continue;
}
if ($this->isWritePoint($token)) {
$line = $token[2];
return $this->insertStringAfterLine($class, $method, $line, $token[0] === T_COMMENT ?: $prependNewLine);
@@ -144,7 +152,8 @@ final class TokenizedCodeWriter implements CodeWriter
if ($token === '{') {
$search = implode('', $searchPattern);
$position = strpos($class, $search) + strlen($search) - 1;
return substr_replace($class, PHP_EOL . $method . PHP_EOL, $position, 0);
return substr_replace($class, "\n" . $method . "\n", $position, 0);
}
}
}
@@ -155,6 +164,6 @@ final class TokenizedCodeWriter implements CodeWriter
*/
private function isWritePoint($token)
{
return is_array($token) && ($token[1] === PHP_EOL || $token[0] === T_COMMENT);
return is_array($token) && ($token[1] === "\n" || $token[0] === T_COMMENT);
}
}

View File

@@ -20,7 +20,7 @@ use PhpSpec\CodeAnalysis\TokenizedTypeHintRewriter;
use PhpSpec\CodeAnalysis\VisibilityAccessInspector;
use PhpSpec\Console\Assembler\PresenterAssembler;
use PhpSpec\Process\Prerequisites\SuitePrerequisites;
use SebastianBergmann\Exporter\Exporter;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use PhpSpec\Process\ReRunner;
use PhpSpec\Util\MethodAnalyser;
use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -151,7 +151,8 @@ class ContainerAssembler
return new Listener\CollaboratorMethodNotFoundListener(
$c->get('console.io'),
$c->get('locator.resource_manager'),
$c->get('code_generator')
$c->get('code_generator'),
$c->get('util.reserved_words_checker')
);
});
$container->setShared('event_dispatcher.listeners.named_constructor_not_found', function (ServiceContainer $c) {
@@ -165,7 +166,8 @@ class ContainerAssembler
return new Listener\MethodNotFoundListener(
$c->get('console.io'),
$c->get('locator.resource_manager'),
$c->get('code_generator')
$c->get('code_generator'),
$c->get('util.reserved_words_checker')
);
});
$container->setShared('event_dispatcher.listeners.stop_on_failure', function (ServiceContainer $c) {
@@ -195,6 +197,9 @@ class ContainerAssembler
$container->setShared('util.method_analyser', function () {
return new MethodAnalyser();
});
$container->setShared('util.reserved_words_checker', function () {
return new ReservedWordsMethodNameChecker();
});
$container->setShared('event_dispatcher.listeners.bootstrap', function (ServiceContainer $c) {
return new Listener\BootstrapListener(
$c->get('console.io')

View File

@@ -342,4 +342,26 @@ class IO implements IOInterface
}
return $width;
}
/**
* @param string $message
* @param int $indent
*/
public function writeBrokenCodeBlock($message, $indent = 0)
{
$message = wordwrap($message, $this->getBlockWidth() - ($indent * 2), "\n", true);
if ($indent) {
$message = $this->indentText($message, $indent);
}
$this->output->writeln("<broken-bg>".str_repeat(" ", $this->getBlockWidth())."</broken-bg>");
foreach (explode("\n", $message) as $line) {
$this->output->writeln("<broken-bg>".str_pad($line, $this->getBlockWidth(), ' ')."</broken-bg>");
}
$this->output->writeln("<broken-bg>".str_repeat(" ", $this->getBlockWidth())."</broken-bg>");
$this->output->writeln('');
}
}

View File

@@ -89,4 +89,9 @@ class SuiteEvent extends Event implements EventInterface
{
$this->worthRerunning = true;
}
public function markAsNotWorthRerunning()
{
$this->worthRerunning = false;
}
}

View File

@@ -61,13 +61,21 @@ class DotFormatter extends ConsoleFormatter
break;
}
if ($eventsCount % 50 === 0) {
$remainder = $eventsCount % 50;
$lastRow = $eventsCount === $this->examplesCount;
if ($remainder === 0 || $lastRow) {
$length = strlen((string) $this->examplesCount);
$format = sprintf(' %%%dd / %%%dd', $length, $length);
if ($lastRow) {
$io->write(str_repeat(' ', 50 - $remainder));
}
$io->write(sprintf($format, $eventsCount, $this->examplesCount));
if ($eventsCount !== $this->examplesCount) {
$io->writeLn();
$io->writeln();
}
}
}

View File

@@ -22,8 +22,8 @@ class StringEngine implements EngineInterface
public function compare($expected, $actual)
{
$expected = explode(PHP_EOL, (string) $expected);
$actual = explode(PHP_EOL, (string) $actual);
$expected = explode("\n", (string) $expected);
$actual = explode("\n", (string) $actual);
$diff = new \Diff($expected, $actual, array());
@@ -41,6 +41,6 @@ class StringEngine implements EngineInterface
}
}
return sprintf("<code>%s%s</code>", PHP_EOL, implode(PHP_EOL, $lines));
return sprintf("<code>%s%s</code>", "\n", implode("\n", $lines));
}
}

View File

@@ -19,7 +19,7 @@ class BootstrapListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array('beforeSuite' => 'beforeSuite');
return array('beforeSuite' => array('beforeSuite', 1100));
}
public function beforeSuite()

View File

@@ -19,6 +19,8 @@ use PhpSpec\Event\ExampleEvent;
use PhpSpec\Event\SuiteEvent;
use PhpSpec\Exception\Locator\ResourceCreationException;
use PhpSpec\Locator\ResourceManagerInterface;
use PhpSpec\Util\NameCheckerInterface;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Exception\Doubler\MethodNotFoundException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -47,16 +49,32 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
*/
private $generator;
/**
* @var NameCheckerInterface
*/
private $nameChecker;
/**
* @var array
*/
private $wrongMethodNames = array();
/**
* @param IO $io
* @param ResourceManagerInterface $resources
* @param GeneratorManager $generator
* @param NameCheckerInterface $nameChecker
*/
public function __construct(IO $io, ResourceManagerInterface $resources, GeneratorManager $generator)
{
public function __construct(
IO $io,
ResourceManagerInterface $resources,
GeneratorManager $generator,
NameCheckerInterface $nameChecker = null
) {
$this->io = $io;
$this->resources = $resources;
$this->generator = $generator;
$this->nameChecker = $nameChecker ?: new ReservedWordsMethodNameChecker();
}
/**
@@ -87,7 +105,9 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
$this->interfaces[$interface] = array();
}
$this->interfaces[$interface][$exception->getMethodName()] = $exception->getArguments();
$methodName = $exception->getMethodName();
$this->interfaces[$interface][$methodName] = $exception->getArguments();
$this->checkIfMethodNameAllowed($methodName);
}
/**
@@ -119,7 +139,6 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
public function afterSuite(SuiteEvent $event)
{
foreach ($this->interfaces as $interface => $methods) {
try {
$resource = $this->resources->createResource($interface);
} catch (ResourceCreationException $e) {
@@ -127,6 +146,10 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
}
foreach ($methods as $method => $arguments) {
if (in_array($method, $this->wrongMethodNames)) {
continue;
}
if ($this->io->askConfirmation(sprintf(self::PROMPT, $interface, $method))) {
$this->generator->generate(
$resource,
@@ -140,6 +163,11 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
}
}
}
if ($this->wrongMethodNames) {
$this->writeErrorMessage();
$event->markAsNotWorthRerunning();
}
}
/**
@@ -167,4 +195,19 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
return $exception;
}
}
private function checkIfMethodNameAllowed($methodName)
{
if (!$this->nameChecker->isNameValid($methodName)) {
$this->wrongMethodNames[] = $methodName;
}
}
private function writeErrorMessage()
{
foreach ($this->wrongMethodNames as $methodName) {
$message = sprintf("I cannot generate the method '%s' for you because it is a reserved keyword", $methodName);
$this->io->writeBrokenCodeBlock($message, 2);
}
}
}

View File

@@ -13,6 +13,8 @@
namespace PhpSpec\Listener;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use PhpSpec\Util\NameCheckerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use PhpSpec\Console\IO;
use PhpSpec\Locator\ResourceManagerInterface;
@@ -27,12 +29,28 @@ class MethodNotFoundListener implements EventSubscriberInterface
private $resources;
private $generator;
private $methods = array();
private $wrongMethodNames = array();
/**
* @var NameCheckerInterface
*/
private $nameChecker;
public function __construct(IO $io, ResourceManagerInterface $resources, GeneratorManager $generator)
{
/**
* @param IO $io
* @param ResourceManagerInterface $resources
* @param GeneratorManager $generator
* @param NameCheckerInterface $nameChecker
*/
public function __construct(
IO $io,
ResourceManagerInterface $resources,
GeneratorManager $generator,
NameCheckerInterface $nameChecker = null
) {
$this->io = $io;
$this->resources = $resources;
$this->generator = $generator;
$this->nameChecker = $nameChecker ?: new ReservedWordsMethodNameChecker();
}
public static function getSubscribedEvents()
@@ -54,7 +72,9 @@ class MethodNotFoundListener implements EventSubscriberInterface
}
$classname = get_class($exception->getSubject());
$this->methods[$classname .'::'.$exception->getMethodName()] = $exception->getArguments();
$methodName = $exception->getMethodName();
$this->methods[$classname .'::'.$methodName] = $exception->getArguments();
$this->checkIfMethodNameAllowed($methodName);
}
public function afterSuite(SuiteEvent $event)
@@ -65,6 +85,11 @@ class MethodNotFoundListener implements EventSubscriberInterface
foreach ($this->methods as $call => $arguments) {
list($classname, $method) = explode('::', $call);
if (in_array($method, $this->wrongMethodNames)) {
continue;
}
$message = sprintf('Do you want me to create `%s()` for you?', $call);
try {
@@ -81,5 +106,25 @@ class MethodNotFoundListener implements EventSubscriberInterface
$event->markAsWorthRerunning();
}
}
if ($this->wrongMethodNames) {
$this->writeWrongMethodNameMessage();
$event->markAsNotWorthRerunning();
}
}
private function checkIfMethodNameAllowed($methodName)
{
if (!$this->nameChecker->isNameValid($methodName)) {
$this->wrongMethodNames[] = $methodName;
}
}
private function writeWrongMethodNameMessage()
{
foreach ($this->wrongMethodNames as $methodName) {
$message = sprintf("I cannot generate the method '%s' for you because it is a reserved keyword", $methodName);
$this->io->writeBrokenCodeBlock($message, 2);
}
}
}

View File

@@ -282,7 +282,7 @@ class PSR0Locator implements ResourceLocatorInterface
/**
* @param $path
*
*
* @return null|string
*/
private function findSpecClassname($path)
@@ -327,7 +327,7 @@ class PSR0Locator implements ResourceLocatorInterface
$classname = $this->findSpecClassname($path);
if (null === $classname) {
throw new \RuntimeException('Spec file does not contains any class definition.');
throw new \RuntimeException(sprintf('Spec file "%s" does not contains any class definition.', $path));
}
// Remove spec namespace from the begining of the classname.

View File

@@ -28,10 +28,12 @@ class ThrowMatcher implements MatcherInterface
* @var array
*/
private static $ignoredProperties = array('file', 'line', 'string', 'trace', 'previous');
/**
* @var Unwrapper
*/
private $unwrapper;
/**
* @var PresenterInterface
*/
@@ -43,9 +45,9 @@ class ThrowMatcher implements MatcherInterface
private $factory;
/**
* @param Unwrapper $unwrapper
* @param PresenterInterface $presenter
* @param ReflectionFactory $factory
* @param Unwrapper $unwrapper
* @param PresenterInterface $presenter
* @param ReflectionFactory|null $factory
*/
public function __construct(Unwrapper $unwrapper, PresenterInterface $presenter, ReflectionFactory $factory = null)
{
@@ -91,114 +93,134 @@ class ThrowMatcher implements MatcherInterface
}
/**
* @param callable $callable
* @param array $arguments
* @param null $exception
* @param callable $callable
* @param array $arguments
* @param null|object|string $exception
*
* @throws \PhpSpec\Exception\Example\FailureException
* @throws \PhpSpec\Exception\Example\NotEqualException
*/
public function verifyPositive($callable, array $arguments, $exception = null)
{
$exceptionThrown = null;
try {
call_user_func_array($callable, $arguments);
} catch (\Exception $e) {
if (null === $exception) {
return;
}
$exceptionThrown = $e;
} catch (\Throwable $e) {
$exceptionThrown = $e;
}
if (!$e instanceof $exception) {
throw new FailureException(sprintf(
if (!$exceptionThrown) {
throw new FailureException('Expected to get exception / throwable, none got.');
}
if (null === $exception) {
return;
}
if (!$exceptionThrown instanceof $exception) {
throw new FailureException(
sprintf(
'Expected exception of class %s, but got %s.',
$this->presenter->presentValue($exception),
$this->presenter->presentValue($e)
));
}
$this->presenter->presentValue($exceptionThrown)
)
);
}
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties)) {
continue;
}
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties, true)) {
continue;
}
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($e);
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($exceptionThrown);
if (null !== $expected && $actual !== $expected) {
throw new NotEqualException(sprintf(
if (null !== $expected && $actual !== $expected) {
throw new NotEqualException(
sprintf(
'Expected exception `%s` to be %s, but it is %s.',
$property->getName(),
$this->presenter->presentValue($expected),
$this->presenter->presentValue($actual)
), $expected, $actual);
}
), $expected, $actual
);
}
}
return;
}
throw new FailureException('Expected to get exception, none got.');
}
/**
* @param callable $callable
* @param array $arguments
* @param string|null $exception
* @param callable $callable
* @param array $arguments
* @param string|null|object $exception
*
* @throws \PhpSpec\Exception\Example\FailureException
*/
public function verifyNegative($callable, array $arguments, $exception = null)
{
$exceptionThrown = null;
try {
call_user_func_array($callable, $arguments);
} catch (\Exception $e) {
if (null === $exception) {
throw new FailureException(sprintf(
$exceptionThrown = $e;
} catch (\Throwable $e) {
$exceptionThrown = $e;
}
if ($exceptionThrown && null === $exception) {
throw new FailureException(
sprintf(
'Expected to not throw any exceptions, but got %s.',
$this->presenter->presentValue($e)
));
}
$this->presenter->presentValue($exceptionThrown)
)
);
}
if ($e instanceof $exception) {
$invalidProperties = array();
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties)) {
continue;
}
if ($exceptionThrown && $exceptionThrown instanceof $exception) {
$invalidProperties = array();
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties, true)) {
continue;
}
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($e);
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($exceptionThrown);
if (null !== $expected && $actual === $expected) {
$invalidProperties[] = sprintf(
' `%s`=%s',
$property->getName(),
$this->presenter->presentValue($expected)
);
}
if (null !== $expected && $actual === $expected) {
$invalidProperties[] = sprintf(
' `%s`=%s',
$property->getName(),
$this->presenter->presentValue($expected)
);
}
}
}
$withProperties = '';
if (count($invalidProperties) > 0) {
$withProperties = sprintf(
' with'.PHP_EOL.'%s,'.PHP_EOL,
implode(",\n", $invalidProperties)
);
}
$withProperties = '';
if (count($invalidProperties) > 0) {
$withProperties = sprintf(
' with'.PHP_EOL.'%s,'.PHP_EOL,
implode(",\n", $invalidProperties)
);
}
throw new FailureException(sprintf(
throw new FailureException(
sprintf(
'Expected to not throw %s exception%s but got it.',
$this->presenter->presentValue($exception),
$withProperties
));
}
)
);
}
}
@@ -226,7 +248,7 @@ class ThrowMatcher implements MatcherInterface
function ($method, $arguments) use ($check, $subject, $exception, $unwrapper) {
$arguments = $unwrapper->unwrapAll($arguments);
$methodName = $arguments[0];
$methodName = $arguments[0];
$arguments = isset($arguments[1]) ? $arguments[1] : array();
$callable = array($subject, $methodName);
@@ -253,7 +275,7 @@ class ThrowMatcher implements MatcherInterface
*/
private function getException(array $arguments)
{
if (0 == count($arguments)) {
if (0 === count($arguments)) {
return null;
}
@@ -261,15 +283,21 @@ class ThrowMatcher implements MatcherInterface
return $arguments[0];
}
if (is_object($arguments[0]) && $arguments[0] instanceof \Exception) {
return $arguments[0];
if (is_object($arguments[0])) {
if (class_exists('\Throwable') && $arguments[0] instanceof \Throwable) {
return $arguments[0];
} elseif ($arguments[0] instanceof \Exception) {
return $arguments[0];
}
}
throw new MatcherException(sprintf(
"Wrong argument provided in throw matcher.\n".
"Fully qualified classname or exception instance expected,\n".
"Got %s.",
$this->presenter->presentValue($arguments[0])
));
throw new MatcherException(
sprintf(
"Wrong argument provided in throw matcher.\n".
"Fully qualified classname or exception instance expected,\n".
"Got %s.",
$this->presenter->presentValue($arguments[0])
)
);
}
}

View File

@@ -32,6 +32,7 @@ use ArrayAccess;
* @method void beConstructedThrough($factoryMethod, array $constructorArguments = array())
* @method void beAnInstanceOf($class)
* @method void shouldHaveType($type)
* @method void shouldImplement($interface)
* @method Subject\Expectation\DuringCall shouldThrow($exception = null)
*/
class ObjectBehavior implements

View File

@@ -198,7 +198,7 @@ final class ClassFileAnalyser
for ($i = $index, $max = count($tokens); $i < $max; $i++) {
$token = $tokens[$i];
if ('{' === $token) {
if ('{' === $token || $this->isSpecialBraceToken($token)) {
$braceCount++;
continue;
}
@@ -212,6 +212,15 @@ final class ClassFileAnalyser
}
}
private function isSpecialBraceToken($token)
{
if (!is_array($token)) {
return false;
}
return $token[1] === "{";
}
/**
* @param mixed $token
* @return bool

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of PhpSpec, A php toolset to drive emergent
* design by specification.
*
* (c) Marcello Duarte <marcello.duarte@gmail.com>
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpSpec\Util;
interface NameCheckerInterface
{
/**
* @param string $name
*
* @return bool
*/
public function isNameValid($name);
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of PhpSpec, A php toolset to drive emergent
* design by specification.
*
* (c) Marcello Duarte <marcello.duarte@gmail.com>
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpSpec\Util;
final class ReservedWordsMethodNameChecker implements NameCheckerInterface
{
private $reservedWords = array(
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'eval',
'exit',
'extends',
'final',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'__class__',
'__dir__',
'__file__',
'__function__',
'__line__',
'__method__',
'__namespace__',
'__trait__',
);
/**
* {@inheritdoc}
*/
public function isNameValid($name)
{
return !in_array(strtolower($name), $this->reservedWords);
}
}