| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960 | <?phprequire_once __DIR__ . '/Annotations.case.php';require_once __DIR__ . '/Annotations.Sample.case.php';use mindplay\annotations\AnnotationFile;use mindplay\annotations\AnnotationCache;use mindplay\annotations\AnnotationManager;use mindplay\annotations\Annotations;use mindplay\annotations\Annotation;use mindplay\annotations\standard\ReturnAnnotation;use mindplay\test\annotations\Package;use mindplay\test\lib\xTest;use mindplay\test\lib\xTestRunner;if (version_compare(PHP_VERSION, '5.4.0', '>=')) {    require_once __DIR__ . '/traits/namespaced.php';    require_once __DIR__ . '/traits/toplevel.php';}/** * This class implements tests for core annotations */class AnnotationsTest extends xTest{    const ANNOTATION_EXCEPTION = 'mindplay\annotations\AnnotationException';    /**     * Run this test.     *     * @param xTestRunner $testRunner Test runner.     * @return boolean     */    public function run(xTestRunner $testRunner)    {        $testRunner->startCoverageCollector(__CLASS__);        $cachePath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'runtime';        Annotations::$config = array(            'cache' => new AnnotationCache($cachePath),        );        if (!is_writable($cachePath)) {            die('cache path is not writable: ' . $cachePath);        }        // manually wipe out the cache:        $pattern = Annotations::getManager()->cache->getRoot() . DIRECTORY_SEPARATOR . '*.annotations.php';        foreach (glob($pattern) as $path) {            unlink($path);        }        // disable some annotations not used during testing:        Annotations::getManager()->registry['var'] = false;        Annotations::getManager()->registry['undefined'] = 'UndefinedAnnotation';        $testRunner->stopCoverageCollector();        return parent::run($testRunner);    }    protected function testCanResolveAnnotationNames()    {        $manager = new AnnotationManager;        $manager->namespace = ''; // look for annotations in the global namespace        $manager->suffix = 'Annotation'; // use a suffix for annotation class-names        $this->check(            $manager->resolveName('test') === 'TestAnnotation',            'should capitalize and suffix annotation names'        );        $this->check(            $manager->resolveName('X\Y\Foo') === 'X\Y\FooAnnotation',            'should suffix fully qualified annotation names'        );        $manager->registry['test'] = 'X\Y\Z\TestAnnotation';        $this->check(            $manager->resolveName('test') === 'X\Y\Z\TestAnnotation',            'should respect registered annotation types'        );        $this->check(            $manager->resolveName('Test') === 'X\Y\Z\TestAnnotation',            'should ignore case of first letter in annotation names'        );        $manager->registry['test'] = false;        $this->check($manager->resolveName('test') === false, 'should respect disabled annotation types');        $manager->namespace = 'ABC';        $this->check($manager->resolveName('hello') === 'ABC\HelloAnnotation', 'should default to standard namespace');    }    protected function testCanGetAnnotationFile()    {        // This test is for an internal API, so we need to perform some invasive maneuvers:        $manager = Annotations::getManager();        $manager_reflection = new ReflectionClass($manager);        $method = $manager_reflection->getMethod('getAnnotationFile');        $method->setAccessible(true);        $class_reflection = new ReflectionClass('mindplay\test\Sample\SampleClass');        // absolute path to the class-file used for testing        $file_path = $class_reflection->getFileName();        // Now get the AnnotationFile instance:        /** @var AnnotationFile $file */        $file = $method->invoke($manager, $file_path);        $this->check($file instanceof AnnotationFile, 'should be an instance of AnnotationFile');        $this->check(count($file->data) > 0, 'should contain Annotation data');        $this->check($file->path === $file_path, 'should reflect path to class-file');        $this->check($file->namespace === 'mindplay\test\Sample', 'should reflect namespace');        $this->check(            $file->uses === array('Test' => 'Test', 'SampleAlias' => 'mindplay\annotations\Annotation'),            'should reflect use-clause'        );    }    protected function testCanParseAnnotations()    {        $manager = new AnnotationManager;        Package::register($manager);        $manager->namespace = ''; // look for annotations in the global namespace        $manager->suffix = 'Annotation'; // use a suffix for annotation class-names        $parser = $manager->getParser();        $source = "            <?php            namespace foo\\bar;            use                baz\\Hat as Zing,                baz\\Zap;            /**             * @doc 123             * @note('abc')             * @required             * @note('xyz');             */            class Sample {                public function test()                {                    \$var = null;                    \$test = function () use (\$var) {                        // this inline function is here to assert that the parser                        // won't pick up the use-clause of an inline function                    };                }            }        ";        $code = $parser->parse($source, 'inline-test');        $test = eval($code);        $this->check($test['#namespace'] === 'foo\bar', 'file namespace should be parsed and cached');        $this->check(            $test['#uses'] === array('Zing' => 'baz\Hat', 'Zap' => 'baz\Zap'),            'use-clauses should be parsed and cached: ' . var_export($test['#uses'], true)        );        $this->check($test['foo\bar\Sample'][0]['#name'] === 'doc', 'first annotation is an @doc annotation');        $this->check($test['foo\bar\Sample'][0]['#type'] === 'DocAnnotation', 'first annotation is a DocAnnotation');        $this->check($test['foo\bar\Sample'][0]['value'] === 123, 'first annotation has the value 123');        $this->check($test['foo\bar\Sample'][1]['#name'] === 'note', 'second annotation is an @note annotation');        $this->check($test['foo\bar\Sample'][1]['#type'] === 'NoteAnnotation', 'second annotation is a NoteAnnotation');        $this->check($test['foo\bar\Sample'][1][0] === 'abc', 'value of second annotation is "abc"');        $this->check(            $test['foo\bar\Sample'][2]['#type'] === 'mindplay\test\annotations\RequiredAnnotation',            'third annotation is a RequiredAnnotation'        );        $this->check($test['foo\bar\Sample'][3]['#type'] === 'NoteAnnotation', 'last annotation is a NoteAnnotation');        $this->check($test['foo\bar\Sample'][3][0] === 'xyz', 'value of last annotation is "xyz"');    }    protected function testCanGetStaticAnnotationManager()    {        if (Annotations::getManager() instanceof AnnotationManager) {            $this->pass();        } else {            $this->fail();        }    }    protected function testCanGetAnnotationUsage()    {        $usage = Annotations::getUsage('NoteAnnotation');        $this->check($usage->class === true);        $this->check($usage->property === true);        $this->check($usage->method === true);        $this->check($usage->inherited === true);        $this->check($usage->multiple === true);    }    protected function testAnnotationWithNonUsageAndUsageAnnotations()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "The class 'UsageAndNonUsageAnnotation' must have exactly one UsageAnnotation (no other Annotations are allowed)"        );        Annotations::getUsage('UsageAndNonUsageAnnotation');    }    protected function testAnnotationWithSingleNonUsageAnnotation()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "The class 'SingleNonUsageAnnotation' must have exactly one UsageAnnotation (no other Annotations are allowed)"        );        Annotations::getUsage('SingleNonUsageAnnotation');    }    protected function testUsageAnnotationIsInherited()    {        $usage = Annotations::getUsage('InheritUsageAnnotation');        $this->check($usage->method === true);    }    protected function testGetUsageOfUndefinedAnnotationClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Annotation type 'NoSuchAnnotation' does not exist"        );        Annotations::getUsage('NoSuchAnnotation');    }    protected function testAnnotationWithoutUsageAnnotation()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "The class 'NoUsageAnnotation' must have exactly one UsageAnnotation"        );        Annotations::getUsage('NoUsageAnnotation');    }    protected function testCanGetClassAnnotations()    {        $annotations = Annotations::ofClass(new \ReflectionClass('Test'));        $this->check(count($annotations) > 0, 'from class reflection');        $annotations = Annotations::ofClass(new Test());        $this->check(count($annotations) > 0, 'from class object');        $annotations = Annotations::ofClass('Test');        $this->check(count($annotations) > 0, 'from class name');    }    protected function testCanGetMethodAnnotations()    {        $annotations = Annotations::ofMethod(new \ReflectionClass('Test'), 'run');        $this->check(count($annotations) > 0, 'from class reflection and method name');        $annotations = Annotations::ofMethod(new \ReflectionMethod('Test', 'run'));        $this->check(count($annotations) > 0, 'from method reflection');        $annotations = Annotations::ofMethod(new Test(), 'run');        $this->check(count($annotations) > 0, 'from class object and method name');        $annotations = Annotations::ofMethod('Test', 'run');        $this->check(count($annotations) > 0, 'from class name and method name');    }    protected function testGetAnnotationsFromMethodOfNonExistingClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Unable to read annotations from an undefined class 'NonExistingClass'"        );        Annotations::ofMethod('NonExistingClass');    }    protected function testGetAnnotationsFromNonExistingMethodOfAClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'Unable to read annotations from an undefined method Test::nonExistingMethod()'        );        Annotations::ofMethod('Test', 'nonExistingMethod');    }    protected function testCanGetPropertyAnnotations()    {        $annotations = Annotations::ofProperty(new \ReflectionClass('Test'), 'sample');        $this->check(count($annotations) > 0, 'from class reflection and property name');        $annotations = Annotations::ofProperty(new \ReflectionProperty('TestBase', 'sample'));        $this->check(count($annotations) > 0, 'from property reflection');        $annotations = Annotations::ofProperty(new Test(), 'sample');        $this->check(count($annotations) > 0, 'from class object and property name');        $annotations = Annotations::ofProperty('Test', 'sample');        $this->check(count($annotations) > 0, 'from class name and property name');    }    protected function testGetAnnotationsFromPropertyOfNonExistingClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Unable to read annotations from an undefined class 'NonExistingClass'"        );        Annotations::ofProperty('NonExistingClass', 'sample');    }    public function testGetAnnotationsFromNonExistingPropertyOfExistingClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'Unable to read annotations from an undefined property Test::$nonExisting'        );        Annotations::ofProperty('Test', 'nonExisting');    }    protected function testCanGetFilteredClassAnnotations()    {        $anns = Annotations::ofClass('TestBase', 'NoteAnnotation');        if (!count($anns)) {            $this->fail('No annotations found');            return;        }        foreach ($anns as $ann) {            if (!$ann instanceof NoteAnnotation) {                $this->fail();            }        }        $this->pass();    }    protected function testCanGetFilteredMethodAnnotations()    {        $anns = Annotations::ofMethod('TestBase', 'run', 'NoteAnnotation');        if (!count($anns)) {            $this->fail('No annotations found');            return;        }        foreach ($anns as $ann) {            if (!$ann instanceof NoteAnnotation) {                $this->fail();            }        }        $this->pass();    }    protected function testCanGetFilteredPropertyAnnotations()    {        $anns = Annotations::ofProperty('Test', 'mixed', 'NoteAnnotation');        if (!count($anns)) {            $this->fail('No annotations found');            return;        }        foreach ($anns as $ann) {            if (!$ann instanceof NoteAnnotation) {                $this->fail();            }        }        $this->pass();    }    protected function testCanGetInheritedClassAnnotations()    {        $anns = Annotations::ofClass('Test');        foreach ($anns as $ann) {            if ($ann->note == 'Applied to the TestBase class') {                $this->pass();                return;            }        }        $this->fail();    }    protected function testCanGetInheritedMethodAnnotations()    {        $anns = Annotations::ofMethod('Test', 'run');        foreach ($anns as $ann) {            if ($ann->note == 'Applied to a hidden TestBase method') {                $this->pass();                return;            }        }        $this->fail();    }    protected function testCanGetInheritedPropertyAnnotations()    {        $anns = Annotations::ofProperty('Test', 'sample');        foreach ($anns as $ann) {            if ($ann->note == 'Applied to a TestBase member') {                $this->pass();                return;            }        }        $this->fail();    }    protected function testDoesNotInheritUninheritableAnnotations()    {        $anns = Annotations::ofClass('Test');        if (count($anns) == 0) {            $this->fail();            return;        }        foreach ($anns as $ann) {            if ($ann instanceof UninheritableAnnotation) {                $this->fail();                return;            }        }        $this->pass();    }    protected function testThrowsExceptionIfSingleAnnotationAppliedTwice()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Only one annotation of 'SingleAnnotation' type may be applied to the same property"        );        Annotations::ofProperty('Test', 'only_one');    }    protected function testCanOverrideSingleAnnotation()    {        $anns = Annotations::ofProperty('Test', 'override_me');        if (count($anns) != 1) {            $this->fail(count($anns) . ' annotations found - expected 1');            return;        }        $ann = reset($anns);        if ($ann->test != 'This annotation overrides the one in TestBase') {            $this->fail();        } else {            $this->pass();        }    }    protected function testCanHandleEdgeCaseInParser()    {        // an edge-case was found in the parser - this test asserts that a php-doc style        // annotation with no trailing characters after it will be parsed correctly.        $anns = Annotations::ofClass('TestBase', 'DocAnnotation');        $this->check(count($anns) == 1, 'one DocAnnotation was expected - found ' . count($anns));    }    protected function testCanHandleNamespaces()    {        // This test asserts that a namespaced class can be annotated, that annotations can        // be namespaced, and that asking for annotations of a namespaced annotation-type        // yields the expected result.        $anns = Annotations::ofClass('mindplay\test\Sample\SampleClass', 'mindplay\test\Sample\SampleAnnotation');        $this->check(count($anns) == 1, 'one SampleAnnotation was expected - found ' . count($anns));    }    protected function testCanUseAnnotationsInDefaultNamespace()    {        $manager = new AnnotationManager();        $manager->namespace = 'mindplay\test\Sample';        $manager->cache = false;        $anns = $manager->getClassAnnotations(            'mindplay\test\Sample\AnnotationInDefaultNamespace',            'mindplay\test\Sample\SampleAnnotation'        );        $this->check(count($anns) == 1, 'one SampleAnnotation was expected - found ' . count($anns));    }    protected function testCanIgnoreAnnotations()    {        $manager = new AnnotationManager();        $manager->namespace = 'mindplay\test\Sample';        $manager->cache = false;        $manager->registry['ignored'] = false;        $anns = $manager->getClassAnnotations('mindplay\test\Sample\IgnoreMe');        $this->check(count($anns) == 0, 'the @ignored annotation should be ignored');    }    protected function testCanUseAnnotationAlias()    {        $manager = new AnnotationManager();        $manager->namespace = 'mindplay\test\Sample';        $manager->cache = false;        $manager->registry['aliased'] = 'mindplay\test\Sample\SampleAnnotation';        /** @var Annotation[] $anns */        $anns = $manager->getClassAnnotations('mindplay\test\Sample\AliasMe');        $this->check(count($anns) == 1, 'the @aliased annotation should be aliased');        $this->check(            get_class($anns[0]) == 'mindplay\test\Sample\SampleAnnotation',            'returned @aliased annotation should map to mindplay\test\Sample\SampleAnnotation'        );    }    protected function testCanFindAnnotationsByAlias()    {        $ann = Annotations::ofProperty('TestBase', 'sample', '@note');        $this->check(count($ann) === 1, 'TestBase::$sample has one @note annotation');    }    protected function testParseUserDefinedClasses()    {        $annotations = Annotations::ofClass('TestClassExtendingUserDefined', '@note');        $this->check(count($annotations) == 2, 'TestClassExtendingUserDefined has two note annotations.');    }    protected function testDoNotParseCoreClasses()    {        $annotations = Annotations::ofClass('TestClassExtendingCore', '@note');        $this->check(count($annotations) == 1, 'TestClassExtendingCore has one note annotations.');    }    protected function testDoNotParseExtensionClasses()    {        $annotations = Annotations::ofClass('TestClassExtendingExtension', '@note');        $this->check(count($annotations) == 1, 'TestClassExtendingExtension has one note annotations.');    }    protected function testGetAnnotationsFromNonExistingClass()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Unable to read annotations from an undefined class/trait 'NonExistingClass'"        );        Annotations::ofClass('NonExistingClass', '@note');    }    protected function testGetAnnotationsFromAnInterface()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Reading annotations from interface 'TestInterface' is not supported"        );        Annotations::ofClass('TestInterface', '@note');    }    protected function testGetAnnotationsFromTrait()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofClass('SimpleTrait', '@note');        $this->check(count($annotations) === 1, 'SimpleTrait has one note annotation.');    }    protected function testCanGetMethodAnnotationsIncludedFromTrait()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofMethod('SimpleTraitTester', 'runFromTrait');        $this->check(count($annotations) > 0, 'for unnamespaced trait');        $annotations = Annotations::ofMethod('SimpleTraitTester', 'runFromAnotherTrait');        $this->check(count($annotations) > 0, 'for namespaced trait');    }    protected function testHandlesMethodInheritanceWithTraits()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofMethod('InheritanceTraitTester', 'baseTraitAndParent');        $this->check(count($annotations) === 2, 'baseTraitAndParent inherits parent annotations');        $this->check($annotations[0]->note === 'inheritance-base-trait-tester', 'parent annotation first');        $this->check($annotations[1]->note === 'inheritance-base-trait', 'trait annotation second');        $annotations = Annotations::ofMethod('InheritanceTraitTester', 'traitAndParent');        $this->check(count($annotations) === 2, 'traitAndParent inherits parent annotations');        $this->check($annotations[0]->note === 'inheritance-base-trait-tester', 'parent annotation first');        $this->check($annotations[1]->note === 'inheritance-trait', 'trait annotation second');        $annotations = Annotations::ofMethod('InheritanceTraitTester', 'traitAndChild');        $this->check(count($annotations) === 1, 'traitAndChild does not inherit trait');        $this->check($annotations[0]->note === 'inheritance-trait-tester', 'child annotation first');        $annotations = Annotations::ofMethod('InheritanceTraitTester', 'traitAndParentAndChild');        $this->check(count($annotations) === 2, 'traitAndParentAndChild does not inherit trait annotation');        $this->check($annotations[0]->note === 'inheritance-base-trait-tester', 'parent annotation first');        $this->check($annotations[1]->note === 'inheritance-trait-tester', 'child annotation second');    }    protected function testHandlesMethodAliasingWithTraits()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofMethod('AliasTraitTester', 'baseTraitRun');        $this->check(count($annotations) === 2, 'baseTraitRun inherits annotation');        $this->check($annotations[0]->note === 'alias-base-trait-tester', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'alias-base-trait', 'non-inherited annotation goes second');        $annotations = Annotations::ofMethod('AliasTraitTester', 'traitRun');        $this->check(count($annotations) === 2, 'traitRun inherits annotation');        $this->check($annotations[0]->note === 'alias-base-trait-tester', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'alias-trait', 'non-inherited annotation goes second');        $annotations = Annotations::ofMethod('AliasTraitTester', 'run');        $this->check(count($annotations) === 2, 'run inherits annotation');        $this->check($annotations[0]->note === 'alias-base-trait-tester', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'alias-trait-tester', 'non-inherited annotation goes second');    }    protected function testHandlesConflictedMethodSelectionWithTraits()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofMethod('InsteadofTraitTester', 'baseTrait');        $this->check(count($annotations) === 2, 'baseTrait inherits annotation');        $this->check($annotations[0]->note === 'insteadof-base-trait-tester', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'insteadof-base-trait-b', 'non-inherited annotation goes second');        $annotations = Annotations::ofMethod('InsteadofTraitTester', 'trate');        $this->check(count($annotations) === 2, 'trate inherits annotation');        $this->check($annotations[0]->note === 'insteadof-base-trait-tester', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'insteadof-trait-a', 'non-inherited annotation goes second');    }    protected function testCanGetPropertyAnnotationsIncludedFromTrait()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        $annotations = Annotations::ofProperty('SimpleTraitTester', 'sampleFromTrait');        $this->check(count($annotations) > 0, 'for unnamespaced trait');        $annotations = Annotations::ofProperty('SimpleTraitTester', 'sampleFromAnotherTrait');        $this->check(count($annotations) > 0, 'for namespaced trait');    }    protected function testHandlesPropertyConflictWithTraits()    {        if (version_compare(PHP_VERSION, '5.4.0', '<')) {            $this->pass();            return;        }        set_error_handler(function ($errno, $errstring) { });        require_once __DIR__ . '/traits/property_conflict.php';        restore_error_handler();        $annotations = Annotations::ofProperty('PropertyConflictTraitTester', 'traitAndChild');        $this->check(count($annotations) === 1, 'traitAndChild does not inherit trait');        $this->check($annotations[0]->note === 'property-conflict-trait-tester', 'child annotation first');        $annotations = Annotations::ofProperty('PropertyConflictTraitTester', 'traitAndTraitAndParent');        $this->check(count($annotations) === 2, 'traitAndTraitAndParent inherits parent annotations');        $this->check($annotations[0]->note === 'property-conflict-base-trait-tester', 'parent annotation first');        $this->check($annotations[1]->note === 'property-conflict-trait-two', 'first listed trait annotation second');        $annotations = Annotations::ofProperty('PropertyConflictTraitTester', 'unannotatedTraitAndAnnotatedTrait');        $this->check(count($annotations) === 0, 'unannotatedTraitAndAnnotatedTrait has no annotations');        $annotations = Annotations::ofProperty('PropertyConflictTraitTester', 'traitAndParentAndChild');        $this->check(count($annotations) === 2, 'traitAndParentAndChild does not inherit trait annotation');        $this->check($annotations[0]->note === 'property-conflict-base-trait-tester', 'parent annotation first');        $this->check($annotations[1]->note === 'property-conflict-trait-tester', 'child annotation second');    }    protected function testDisallowReadingUndefinedAnnotationProperties()    {        $nodeAnnotation = new NoteAnnotation();        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'NoteAnnotation::$nonExisting is not a valid property name'        );        $result = $nodeAnnotation->nonExisting;    }    protected function testDisallowWritingUndefinedAnnotationProperties()    {        $nodeAnnotation = new NoteAnnotation();        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'NoteAnnotation::$nonExisting is not a valid property name'        );        $nodeAnnotation->nonExisting = 'new value';    }    protected function testAnnotationCacheGetTimestamp()    {        $annotationCache = new AnnotationCache(sys_get_temp_dir());        $annotationCache->store('sample', '');        $this->check(            $annotationCache->getTimestamp('sample') > strtotime('midnight'),            'Annotation cache last update timestamp is not stale'        );    }    protected function testAnnotationFileTypeResolution()    {        $annotationFile = new AnnotationFile('', array(            '#namespace' => 'LevelA\NS',            '#uses' => array(                'LevelBClass' => 'LevelB\Class',            ),        ));        $this->check(            $annotationFile->resolveType('SubNS1\SubClass') == 'LevelA\NS\SubNS1\SubClass',            'Class in sub-namespace is resolved correctly'        );        $this->check(            $annotationFile->resolveType('\SubNS1\SubClass') == '\SubNS1\SubClass',            'Fully qualified class name is not changed during resolution'        );        $this->check(            $annotationFile->resolveType('LevelBClass') == 'LevelB\Class',            'The "uses ..." clause (exact match) is being used during resolution'        );        $this->check(            $annotationFile->resolveType('SomeClass[]') == 'LevelA\NS\SomeClass[]',            'The [] at then end of data type are preserved during resolution'        );        $this->check(            $annotationFile->resolveType('integer') == 'integer',            'Simple data type is kept as-is during resolution'        );    }    protected function testAnnotationManagerWithoutCache()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'AnnotationManager::$cache is not configured'        );        $annotationManager = new AnnotationManager();        $annotationManager->getClassAnnotations('NoteAnnotation');    }    protected function testUsingNonExistentAnnotation()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Annotation type 'WrongInterfaceAnnotation' does not implement the mandatory IAnnotation interface"        );        Annotations::ofClass('TestClassWrongInterface');    }    protected function testReadingFileAwareAnnotation()    {        $annotations = Annotations::ofProperty('TestClassFileAwareAnnotation', 'prop', '@TypeAware');        $this->check(count($annotations) == 1, 'the @TypeAware annotation was found');        $this->check(            $annotations[0]->type == 'mindplay\annotations\IAnnotationParser',            'data type of type-aware annotation was resolved'        );    }    protected function testAnnotationsConstrainedByCorrectUsageAnnotation()    {        $annotations = array(new NoteAnnotation());        $this->assertApplyConstrains($annotations, 'class');    }    protected function testAnnotationsConstrainedByClass()    {        $annotations = array(new UselessAnnotation());        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Annotation type 'UselessAnnotation' cannot be applied to a class"        );        $this->assertApplyConstrains($annotations, AnnotationManager::MEMBER_CLASS);    }    protected function testAnnotationsConstrainedByMethod()    {        $annotations = array(new UselessAnnotation());        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Annotation type 'UselessAnnotation' cannot be applied to a method"        );        $this->assertApplyConstrains($annotations, AnnotationManager::MEMBER_METHOD);    }    protected function testAnnotationsConstrainedByProperty()    {        $annotations = array(new UselessAnnotation());        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            "Annotation type 'UselessAnnotation' cannot be applied to a property"        );        $this->assertApplyConstrains($annotations, AnnotationManager::MEMBER_PROPERTY);    }    protected function assertApplyConstrains(array &$annotations, $memberType)    {        $manager = Annotations::getManager();        $methodReflection = new ReflectionMethod(get_class($manager), 'applyConstraints');        $methodReflection->setAccessible(true);        $methodReflection->invokeArgs($manager, array(&$annotations, $memberType));        $this->check(count($annotations) > 0);    }    public function testStopAnnotationPreventsClassLevelAnnotationInheritance()    {        $annotations = Annotations::ofClass('SecondClass', '@note');        $this->check(count($annotations) === 1, 'class level annotation after own "@stop" not present');        $this->check($annotations[0]->note === 'class-second', 'non-inherited annotation goes first');        $annotations = Annotations::ofClass('ThirdClass', '@note');        $this->check(count($annotations) === 2, 'class level annotation after parent "@stop" not present');        $this->check($annotations[0]->note === 'class-second', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'class-third', 'non-inherited annotation goes second');    }    public function testStopAnnotationPreventsPropertyLevelAnnotationInheritance()    {        $annotations = Annotations::ofProperty('SecondClass', 'prop', '@note');        $this->check(count($annotations) === 1, 'property level annotation after own "@stop" not present');        $this->check($annotations[0]->note === 'prop-second', 'non-inherited annotation goes first');        $annotations = Annotations::ofProperty('ThirdClass', 'prop', '@note');        $this->check(count($annotations) === 2, 'property level annotation after parent "@stop" not present');        $this->check($annotations[0]->note === 'prop-second', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'prop-third', 'non-inherited annotation goes second');    }    public function testStopAnnotationPreventsMethodLevelAnnotationInheritance()    {        $annotations = Annotations::ofMethod('SecondClass', 'someMethod', '@note');        $this->check(count($annotations) === 1, 'method level annotation after own "@stop" not present');        $this->check($annotations[0]->note === 'method-second', 'non-inherited annotation goes first');        $annotations = Annotations::ofMethod('ThirdClass', 'someMethod', '@note');        $this->check(count($annotations) === 2, 'method level annotation after parent "@stop" not present');        $this->check($annotations[0]->note === 'method-second', 'inherited annotation goes first');        $this->check($annotations[1]->note === 'method-third', 'non-inherited annotation goes second');    }    public function testDirectAccessToClassIgnoresStopAnnotation()    {        $annotations = Annotations::ofClass('FirstClass', '@note');        $this->check(count($annotations) === 1);        $this->check($annotations[0]->note === 'class-first');        $annotations = Annotations::ofProperty('FirstClass', 'prop', '@note');        $this->check(count($annotations) === 1);        $this->check($annotations[0]->note === 'prop-first');        $annotations = Annotations::ofMethod('FirstClass', 'someMethod', '@note');        $this->check(count($annotations) === 1);        $this->check($annotations[0]->note === 'method-first');    }    protected function testFilterUnresolvedAnnotationClass()    {        $annotations = Annotations::ofClass('TestBase', false);        $this->check($annotations === array(), 'empty annotation list when filtering failed');    }    public function testMalformedParamAnnotationThrowsException()    {        $this->setExpectedException(            self::ANNOTATION_EXCEPTION,            'ParamAnnotation requires a type property'        );        Annotations::ofMethod('BrokenParamAnnotationClass', 'brokenParamAnnotation');    }    protected function testOrphanedAnnotationsAreIgnored()    {        $manager = new AnnotationManager();        $manager->namespace = 'mindplay\test\Sample';        $manager->cache = false;        /** @var Annotation[] $annotations */        $annotations = $manager->getMethodAnnotations('mindplay\test\Sample\OrphanedAnnotations', 'someMethod');        $this->check(count($annotations) == 1, 'the @return annotation was found');        $this->check(            $annotations[0] instanceof ReturnAnnotation,            'the @return annotation has correct type'        );    }}return new AnnotationsTest;
 |