#!/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 {}; ''' @pytest.mark.parametrize('XVariant,XVariantRegexp', [ ('X*', 'X\*'), ('const X*', 'const X\*'), ('X&', 'X&'), ('const X&', 'const X&'), ('std::shared_ptr<X>', 'std::shared_ptr<X>'), ]) def test_error_non_class_type_parameter(XVariant, XVariantRegexp): source = ''' struct X {}; fruit::Provider<XVariant> provider; ''' expect_compile_error( 'NonClassTypeError<XVariantRegexp,X>', 'A non-class type T was specified. Use C instead', COMMON_DEFINITIONS, source, locals()) def test_error_annotated_type_parameter(): source = ''' struct X {}; fruit::Provider<XAnnot> provider; ''' expect_compile_error( 'AnnotatedTypeError<fruit::Annotated<Annotation1,X>,X>', 'An annotated type was specified where a non-annotated type was expected.', COMMON_DEFINITIONS, source) @pytest.mark.parametrize('XBindingInInjector,XProviderAnnot,XParamInProvider,XProviderGetParam', [ ('X', 'fruit::Provider<X>', 'X', 'X'), ('X', 'fruit::Provider<X>', 'X', 'const X&'), ('X', 'fruit::Provider<X>', 'X', 'const X*'), ('X', 'fruit::Provider<X>', 'X', 'X&'), ('X', 'fruit::Provider<X>', 'X', 'X*'), ('X', 'fruit::Provider<X>', 'X', 'std::shared_ptr<X>'), ('X', 'fruit::Provider<X>', 'X', 'fruit::Provider<X>'), ('X', 'fruit::Provider<X>', 'X', 'fruit::Provider<const X>'), ('X', 'fruit::Provider<const X>', 'const X', 'const X&'), ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, fruit::Provider<X>>', 'X', 'const X&'), ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, fruit::Provider<const X>>', 'const X', 'const X&'), ]) def test_provider_get_ok(XBindingInInjector, XProviderAnnot, XParamInProvider, XProviderGetParam): source = ''' struct X { using Inject = X(); }; fruit::Component<XBindingInInjector> getComponent() { return fruit::createComponent(); } int main() { fruit::Injector<XBindingInInjector> injector(getComponent); fruit::Provider<XParamInProvider> provider = injector.get<XProviderAnnot>(); XProviderGetParam x = provider.get<XProviderGetParam>(); (void)x; } ''' expect_success( COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize('XBindingInInjector,XProviderAnnot,XParamInProvider,XProviderGetParam', [ ('const X', 'fruit::Provider<const X>', 'const X', 'X'), ('const X', 'fruit::Provider<const X>', 'const X', 'const X&'), ('const X', 'fruit::Provider<const X>', 'const X', 'const X*'), ('const X', 'fruit::Provider<const X>', 'const X', 'fruit::Provider<const X>'), ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, fruit::Provider<const X>>', 'const X', 'const X&'), ]) def test_provider_get_const_binding_ok(XBindingInInjector, XProviderAnnot, XParamInProvider, XProviderGetParam): XBindingInInjectorWithoutConst = XBindingInInjector.replace('const ', '') source = ''' struct X {}; const X x{}; fruit::Component<XBindingInInjector> getComponent() { return fruit::createComponent() .bindInstance<XBindingInInjectorWithoutConst, X>(x); } int main() { fruit::Injector<XBindingInInjector> injector(getComponent); fruit::Provider<XParamInProvider> provider = injector.get<XProviderAnnot>(); XProviderGetParam x = provider.get<XProviderGetParam>(); (void)x; } ''' expect_success( COMMON_DEFINITIONS, source, locals()) def test_provider_get_during_injection_ok(): source = ''' struct X { INJECT(X()) = default; void foo() { } }; struct Y { X x; INJECT(Y(fruit::Provider<X> xProvider)) : x(xProvider.get<X>()) { } void foo() { x.foo(); } }; struct Z { Y y; INJECT(Z(fruit::Provider<Y> yProvider)) : y(yProvider.get<Y>()) { } void foo() { y.foo(); } }; fruit::Component<Z> getZComponent() { return fruit::createComponent(); } int main() { fruit::Injector<Z> injector(getZComponent); fruit::Provider<Z> provider(injector); // During provider.get<Z>(), yProvider.get() is called, and during that xProvider.get() // is called. Z z = provider.get<Z>(); z.foo(); } ''' expect_success( COMMON_DEFINITIONS, source) def test_provider_get_error_type_not_provided(): source = ''' struct X {}; struct Y {}; void f(fruit::Provider<X> provider) { provider.get<Y>(); } ''' expect_compile_error( 'TypeNotProvidedError<Y>', 'Trying to get an instance of T, but it is not provided by this Provider/Injector.', COMMON_DEFINITIONS, source) @pytest.mark.parametrize('XVariant,XVariantRegex', [ ('X**', r'X\*\*'), ('std::shared_ptr<X>*', r'std::shared_ptr<X>\*'), ('const std::shared_ptr<X>', r'const std::shared_ptr<X>'), ('X* const', r'X\* const'), ('const X* const', r'const X\* const'), ('std::nullptr_t', r'(std::)?nullptr(_t)?'), ('X*&', r'X\*&'), ('X(*)()', r'X(\((__cdecl)?\*\))?\((void)?\)'), ('void', r'void'), ('fruit::Annotated<Annotation1, fruit::Annotated<Annotation1, X>>', r'fruit::Annotated<Annotation1, X>'), ]) def test_provider_get_error_type_not_injectable(XVariant,XVariantRegex): source = ''' struct X {}; void f(fruit::Provider<X> provider) { provider.get<XVariant>(); } ''' expect_compile_error( 'NonInjectableTypeError<XVariantRegex>', 'The type T is not injectable', COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize('XProviderGetParam,XProviderGetParamRegex', [ ('X&', 'X&'), ('X*', 'X\*'), ('std::shared_ptr<X>', 'std::shared_ptr<X>'), ('fruit::Provider<X>', 'fruit::Provider<X>'), ]) def test_const_provider_get_does_not_allow_injecting_nonconst_variants(XProviderGetParam, XProviderGetParamRegex): source = ''' void f(fruit::Provider<const X> provider) { provider.get<XProviderGetParam>(); } ''' expect_compile_error( 'TypeProvidedAsConstOnlyError<XProviderGetParamRegex>', 'Trying to get an instance of T, but it is only provided as a constant by this Provider/Injector', COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize('Y_PROVIDER_ANNOT', [ ('fruit::Provider<Y>'), ('ANNOTATED(Annotation1, fruit::Provider<Y>)'), ]) def test_lazy_injection_with_annotations(Y_PROVIDER_ANNOT): source = ''' struct Y : public ConstructionTracker<Y> { using Inject = Y(); }; struct X : public ConstructionTracker<X> { INJECT(X(Y_PROVIDER_ANNOT provider)) : provider(provider) { } void run() { Y* y(provider); (void) y; } fruit::Provider<Y> provider; }; fruit::Component<X> getComponent() { return fruit::createComponent(); } fruit::Component<> getEmptyComponent() { return fruit::createComponent(); } int main() { fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent); fruit::Injector<X> injector(normalizedComponent, getComponent); Assert(X::num_objects_constructed == 0); Assert(Y::num_objects_constructed == 0); X* x(injector); Assert(X::num_objects_constructed == 1); Assert(Y::num_objects_constructed == 0); x->run(); Assert(X::num_objects_constructed == 1); Assert(Y::num_objects_constructed == 1); } ''' expect_success( COMMON_DEFINITIONS, source, locals()) if __name__== '__main__': main(__file__)