| #!/usr/bin/env python3 |
| # Copyright 2016 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS-IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| import pytest |
| |
| from fruit_test_common import * |
| |
| COMMON_DEFINITIONS = ''' |
| #include "test_common.h" |
| |
| struct X; |
| |
| struct Annotation1 {}; |
| using XAnnot = fruit::Annotated<Annotation1, X>; |
| |
| struct Annotation2 {}; |
| |
| struct Annotation3 {}; |
| |
| template <typename T> |
| using WithNoAnnotation = T; |
| |
| template <typename T> |
| using WithAnnotation1 = fruit::Annotated<Annotation1, T>; |
| ''' |
| |
| def test_register_constructor_success_copyable_and_movable(): |
| source = ''' |
| struct X { |
| INJECT(X()) = default; |
| X(X&&) = default; |
| X(const X&) = default; |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent(); |
| } |
| |
| int main() { |
| fruit::Injector<X> injector(getComponent); |
| injector.get<X*>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_register_constructor_success_movable_only(): |
| source = ''' |
| struct X { |
| INJECT(X()) = default; |
| X(X&&) = default; |
| X(const X&) = delete; |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent(); |
| } |
| |
| int main() { |
| fruit::Injector<X> injector(getComponent); |
| injector.get<X*>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_register_constructor_success_not_movable(): |
| source = ''' |
| struct X { |
| INJECT(X()) = default; |
| X(X&&) = delete; |
| X(const X&) = delete; |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent(); |
| } |
| |
| int main() { |
| fruit::Injector<X> injector(getComponent); |
| injector.get<X*>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source) |
| |
| # TODO: consider moving to test_normalized_component.py |
| @pytest.mark.parametrize('XAnnot,YAnnot,MaybeConstYAnnot,ZAnnot', [ |
| ('X', 'Y', 'Y', 'Z'), |
| ('X', 'Y', 'const Y', 'Z'), |
| ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation2, const Y>', 'fruit::Annotated<Annotation3, Z>'), |
| ]) |
| def test_autoinject_with_annotation_success(XAnnot, YAnnot, MaybeConstYAnnot, ZAnnot): |
| source = ''' |
| struct X { |
| using Inject = X(); |
| }; |
| |
| struct Y : public ConstructionTracker<Y> { |
| using Inject = Y(); |
| }; |
| |
| struct Z { |
| using Inject = Z(); |
| }; |
| |
| fruit::Component<ZAnnot, MaybeConstYAnnot, XAnnot> getComponent() { |
| return fruit::createComponent(); |
| } |
| |
| fruit::Component<> getEmptyComponent() { |
| return fruit::createComponent(); |
| } |
| |
| int main() { |
| fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent); |
| fruit::Injector<MaybeConstYAnnot> injector(normalizedComponent, getComponent); |
| |
| Assert(Y::num_objects_constructed == 0); |
| injector.get<YAnnot>(); |
| Assert(Y::num_objects_constructed == 1); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_autoinject_annotation_in_signature_return_type(): |
| source = ''' |
| struct X { |
| using Inject = XAnnot(); |
| }; |
| |
| fruit::Component<XAnnot> getComponent() { |
| return fruit::createComponent(); |
| } |
| ''' |
| expect_compile_error( |
| 'InjectTypedefWithAnnotationError<X>', |
| 'C::Inject is a signature that returns an annotated type', |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_autoinject_wrong_class_in_typedef(): |
| source = ''' |
| struct X { |
| using Inject = X(); |
| }; |
| |
| struct Y : public X { |
| }; |
| |
| fruit::Component<Y> getComponent() { |
| return fruit::createComponent(); |
| } |
| ''' |
| expect_compile_error( |
| 'InjectTypedefForWrongClassError<Y,X>', |
| 'C::Inject is a signature, but does not return a C. Maybe the class C has no Inject typedef and', |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_register_constructor_error_abstract_class(): |
| source = ''' |
| struct X { |
| X(int*) {} |
| |
| virtual void foo() = 0; |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent() |
| .registerConstructor<fruit::Annotated<Annotation1, X>(int*)>(); |
| } |
| ''' |
| if re.search('GNU|MSVC', CXX_COMPILER_NAME) is not None: |
| expect_generic_compile_error( |
| 'invalid abstract return type' |
| '|.X.: cannot instantiate abstract class', |
| COMMON_DEFINITIONS, |
| source) |
| else: |
| expect_compile_error( |
| 'CannotConstructAbstractClassError<X>', |
| 'The specified class can.t be constructed because it.s an abstract class', |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_register_constructor_error_malformed_signature(): |
| source = ''' |
| struct X { |
| X(int) {} |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent() |
| .registerConstructor<X[]>(); |
| } |
| ''' |
| expect_compile_error( |
| 'NotASignatureError<X\[\]>', |
| 'CandidateSignature was specified as parameter, but it.s not a signature. Signatures are of the form', |
| COMMON_DEFINITIONS, |
| source) |
| |
| def test_register_constructor_error_malformed_signature_autoinject(): |
| source = ''' |
| struct X { |
| using Inject = X[]; |
| X(int) {} |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent(); |
| } |
| ''' |
| expect_compile_error( |
| 'InjectTypedefNotASignatureError<X,X\[\]>', |
| 'C::Inject should be a typedef to a signature', |
| COMMON_DEFINITIONS, |
| source) |
| |
| @pytest.mark.parametrize('charPtrAnnot', [ |
| 'char*', |
| 'fruit::Annotated<Annotation1, char*>', |
| ]) |
| def test_register_constructor_does_not_exist_error(charPtrAnnot): |
| source = ''' |
| struct X { |
| X(int*) {} |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent() |
| .registerConstructor<X(charPtrAnnot)>(); |
| } |
| ''' |
| expect_compile_error( |
| 'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>', |
| 'contains an Inject typedef but it.s not constructible with the specified types', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| @pytest.mark.parametrize('charPtrAnnot', [ |
| 'char*', |
| 'fruit::Annotated<Annotation1, char*>', |
| ]) |
| def test_autoinject_constructor_does_not_exist_error(charPtrAnnot): |
| source = ''' |
| struct X { |
| using Inject = X(charPtrAnnot); |
| X(int*) {} |
| }; |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent(); |
| } |
| ''' |
| expect_compile_error( |
| 'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>', |
| 'contains an Inject typedef but it.s not constructible with the specified types', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_autoinject_abstract_class_error(): |
| source = ''' |
| struct X { |
| using Inject = fruit::Annotated<Annotation1, X>(); |
| |
| virtual void scale() = 0; |
| // Note: here we "forgot" to implement scale() (on purpose, for this test) so X is an abstract class. |
| }; |
| |
| fruit::Component<fruit::Annotated<Annotation1, X>> getComponent() { |
| return fruit::createComponent(); |
| } |
| ''' |
| expect_compile_error( |
| 'CannotConstructAbstractClassError<X>', |
| 'The specified class can.t be constructed because it.s an abstract class.', |
| COMMON_DEFINITIONS, |
| source) |
| |
| @pytest.mark.parametrize('WithAnnotation', [ |
| 'WithNoAnnotation', |
| 'WithAnnotation1', |
| ]) |
| @pytest.mark.parametrize('YVariant', [ |
| 'Y', |
| 'const Y', |
| 'Y*', |
| 'const Y*', |
| 'Y&', |
| 'const Y&', |
| 'std::shared_ptr<Y>', |
| 'fruit::Provider<Y>', |
| 'fruit::Provider<const Y>', |
| ]) |
| def test_register_constructor_with_param_success(WithAnnotation, YVariant): |
| source = ''' |
| struct Y {}; |
| struct X { |
| X(YVariant) { |
| } |
| }; |
| |
| fruit::Component<WithAnnotation<Y>> getYComponent() { |
| return fruit::createComponent() |
| .registerConstructor<WithAnnotation<Y>()>(); |
| } |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent() |
| .install(getYComponent) |
| .registerConstructor<X(WithAnnotation<YVariant>)>(); |
| } |
| |
| int main() { |
| fruit::Injector<X> injector(getComponent); |
| injector.get<X>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| @pytest.mark.parametrize('WithAnnotation', [ |
| 'WithNoAnnotation', |
| 'WithAnnotation1', |
| ]) |
| @pytest.mark.parametrize('YVariant', [ |
| 'Y', |
| 'const Y', |
| 'const Y*', |
| 'const Y&', |
| 'fruit::Provider<const Y>', |
| ]) |
| def test_register_constructor_with_param_const_binding_success(WithAnnotation, YVariant): |
| source = ''' |
| struct Y {}; |
| struct X { |
| X(YVariant) { |
| } |
| }; |
| |
| const Y y{}; |
| |
| fruit::Component<WithAnnotation<const Y>> getYComponent() { |
| return fruit::createComponent() |
| .bindInstance<WithAnnotation<Y>, Y>(y); |
| } |
| |
| fruit::Component<X> getComponent() { |
| return fruit::createComponent() |
| .install(getYComponent) |
| .registerConstructor<X(WithAnnotation<YVariant>)>(); |
| } |
| |
| int main() { |
| fruit::Injector<X> injector(getComponent); |
| injector.get<X>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| @pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [ |
| ('WithNoAnnotation', 'Y'), |
| ('WithAnnotation1', 'fruit::Annotated<Annotation1,Y>'), |
| ]) |
| @pytest.mark.parametrize('YVariant', [ |
| 'Y*', |
| 'Y&', |
| 'std::shared_ptr<Y>', |
| 'fruit::Provider<Y>', |
| ]) |
| def test_register_constructor_with_param_error_nonconst_param_required(WithAnnotation, YAnnotRegex, YVariant): |
| source = ''' |
| struct Y {}; |
| struct X { |
| X(YVariant); |
| }; |
| |
| fruit::Component<WithAnnotation<const Y>> getYComponent(); |
| |
| fruit::Component<> getComponent() { |
| return fruit::createComponent() |
| .install(getYComponent) |
| .registerConstructor<X(WithAnnotation<YVariant>)>(); |
| } |
| ''' |
| expect_compile_error( |
| 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', |
| 'The type T was provided as constant, however one of the constructors/providers/factories in this component', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| @pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [ |
| ('WithNoAnnotation', 'Y'), |
| ('WithAnnotation1', 'fruit::Annotated<Annotation1, Y>'), |
| ]) |
| @pytest.mark.parametrize('YVariant', [ |
| 'Y*', |
| 'Y&', |
| 'std::shared_ptr<Y>', |
| 'fruit::Provider<Y>', |
| ]) |
| def test_register_constructor_with_param_error_nonconst_param_required_install_after(WithAnnotation, YAnnotRegex, YVariant): |
| source = ''' |
| struct Y {}; |
| struct X { |
| X(YVariant); |
| }; |
| |
| fruit::Component<WithAnnotation<const Y>> getYComponent(); |
| |
| fruit::Component<> getComponent() { |
| return fruit::createComponent() |
| .registerConstructor<X(WithAnnotation<YVariant>)>() |
| .install(getYComponent); |
| } |
| ''' |
| expect_compile_error( |
| 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', |
| 'The type T was provided as constant, however one of the constructors/providers/factories in this component', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_register_constructor_requiring_nonconst_then_requiring_const_ok(): |
| source = ''' |
| struct X {}; |
| |
| struct Y { |
| Y(X&) {} |
| }; |
| |
| struct Z { |
| Z(const X&) {} |
| }; |
| |
| fruit::Component<Y, Z> getRootComponent() { |
| return fruit::createComponent() |
| .registerConstructor<Y(X&)>() |
| .registerConstructor<Z(const X&)>() |
| .registerConstructor<X()>(); |
| } |
| |
| int main() { |
| fruit::Injector<Y, Z> injector(getRootComponent); |
| injector.get<Y>(); |
| injector.get<Z>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_register_constructor_requiring_nonconst_then_requiring_const_declaring_const_requirement_error(): |
| source = ''' |
| struct X {}; |
| |
| struct Y { |
| Y(X&) {} |
| }; |
| |
| struct Z { |
| Z(const X&) {} |
| }; |
| |
| fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { |
| return fruit::createComponent() |
| .registerConstructor<Y(X&)>() |
| .registerConstructor<Z(const X&)>(); |
| } |
| ''' |
| expect_compile_error( |
| 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', |
| 'The type T was declared as a const Required type in the returned Component, however', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_register_constructor_requiring_const_then_requiring_nonconst_ok(): |
| source = ''' |
| struct X {}; |
| |
| struct Y { |
| Y(const X&) {} |
| }; |
| |
| struct Z { |
| Z(X&) {} |
| }; |
| |
| fruit::Component<Y, Z> getRootComponent() { |
| return fruit::createComponent() |
| .registerConstructor<Y(const X&)>() |
| .registerConstructor<Z(X&)>() |
| .registerConstructor<X()>(); |
| } |
| |
| int main() { |
| fruit::Injector<Y, Z> injector(getRootComponent); |
| injector.get<Y>(); |
| injector.get<Z>(); |
| } |
| ''' |
| expect_success( |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| def test_register_constructor_requiring_const_then_requiring_nonconst_declaring_const_requirement_error(): |
| source = ''' |
| struct X {}; |
| |
| struct Y { |
| Y(const X&) {} |
| }; |
| |
| struct Z { |
| Z(X&) {} |
| }; |
| |
| fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { |
| return fruit::createComponent() |
| .registerConstructor<Y(const X&)>() |
| .registerConstructor<Z(X&)>(); |
| } |
| ''' |
| expect_compile_error( |
| 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', |
| 'The type T was declared as a const Required type in the returned Component, however', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| @pytest.mark.parametrize('YVariant,YVariantRegex', [ |
| ('Y**', r'Y\*\*'), |
| ('std::shared_ptr<Y>*', r'std::shared_ptr<Y>\*'), |
| ('std::nullptr_t', r'(std::)?nullptr(_t)?'), |
| ('Y*&', r'Y\*&'), |
| ('Y(*)()', r'Y(\((__cdecl)?\*\))?\((void)?\)'), |
| ('fruit::Annotated<Annotation1, Y**>', r'Y\*\*'), |
| ]) |
| def test_register_constructor_with_param_error_type_not_injectable(YVariant, YVariantRegex): |
| source = ''' |
| struct Y {}; |
| struct X { |
| X(YVariant); |
| }; |
| |
| fruit::Component<> getComponent() { |
| return fruit::createComponent() |
| .registerConstructor<X(YVariant)>(); |
| } |
| ''' |
| expect_compile_error( |
| 'NonInjectableTypeError<YVariantRegex>', |
| 'The type T is not injectable.', |
| COMMON_DEFINITIONS, |
| source, |
| locals()) |
| |
| |
| if __name__== '__main__': |
| main(__file__) |