#!/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 Y; struct Z; struct Annotation1 {}; using XAnnot1 = fruit::Annotated; using YAnnot1 = fruit::Annotated; using ZAnnot1 = fruit::Annotated; using ConstXAnnot1 = fruit::Annotated; using ConstYAnnot1 = fruit::Annotated; using ConstZAnnot1 = fruit::Annotated; struct Annotation2 {}; using XAnnot2 = fruit::Annotated; using YAnnot2 = fruit::Annotated; using ZAnnot2 = fruit::Annotated; using ConstXAnnot2 = fruit::Annotated; using ConstYAnnot2 = fruit::Annotated; using ConstZAnnot2 = fruit::Annotated; struct Annotation3 {}; ''' CONSTRUCTOR_BINDING=( '', '.registerConstructor()') INTERFACE_BINDING=( ''' struct Y : public X {}; ''', ''' .bind() .registerConstructor() ''') INTERFACE_BINDING2=( ''' struct Y2 : public X {}; ''', ''' .bind() .registerConstructor() ''') INSTALL=( ''' fruit::Component getParentComponent() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent)') INSTALL2=( ''' fruit::Component getParentComponent2() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent2)') CONST_BINDING_FROM_INSTALL=( ''' fruit::Component getParentComponent() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent)') CONST_BINDING_FROM_INSTALL2=( ''' fruit::Component getParentComponent2() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent2)') CONST_BINDING=( ''' const X x{}; ''', '.bindInstance(x)') CONST_BINDING2=( ''' const X x2{}; ''', '.bindInstance(x2)') @pytest.mark.parametrize( 'binding1_preparation,binding1,binding2_preparation,binding2', [ CONSTRUCTOR_BINDING + INSTALL, INTERFACE_BINDING + INSTALL, INSTALL + INSTALL2, CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL, INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL, INSTALL2 + CONST_BINDING_FROM_INSTALL, CONST_BINDING_FROM_INSTALL + INSTALL2, CONST_BINDING + INSTALL2, CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2, CONST_BINDING + CONST_BINDING_FROM_INSTALL, ], ids = [ 'CONSTRUCTOR_BINDING + INSTALL', 'INTERFACE_BINDING + INSTALL', 'INSTALL + INSTALL2', 'CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL', 'INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL', 'INSTALL2 + CONST_BINDING_FROM_INSTALL', 'CONST_BINDING_FROM_INSTALL + INSTALL2', 'CONST_BINDING + INSTALL2', 'CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2', 'CONST_BINDING + CONST_BINDING_FROM_INSTALL', ]) @pytest.mark.parametrize('XAnnot,YAnnot,Y2Annot', [ ('X', 'Y', 'Y2'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ]) def test_clash_with_install( binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot): source = ''' struct X{}; %s %s fruit::Component getComponent() { return fruit::createComponent() %s %s; } ''' % (binding1_preparation, binding2_preparation, binding1, binding2) expect_compile_error( 'DuplicateTypesInComponentError', 'The installed component provides some types that are already provided by the current component.', COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize( 'binding1_preparation,binding1,binding2_preparation,binding2', [ CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING, CONSTRUCTOR_BINDING + INTERFACE_BINDING, INTERFACE_BINDING + CONSTRUCTOR_BINDING, INTERFACE_BINDING + INTERFACE_BINDING2, INSTALL + CONSTRUCTOR_BINDING, INSTALL + INTERFACE_BINDING, CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING, CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING, CONST_BINDING + CONSTRUCTOR_BINDING, CONST_BINDING + INTERFACE_BINDING, CONSTRUCTOR_BINDING + CONST_BINDING, INTERFACE_BINDING + CONST_BINDING, INSTALL2 + CONST_BINDING, CONST_BINDING_FROM_INSTALL + CONST_BINDING, CONST_BINDING + CONST_BINDING2, ], ids= [ 'CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING', 'CONSTRUCTOR_BINDING + INTERFACE_BINDING', 'INTERFACE_BINDING + CONSTRUCTOR_BINDING', 'INTERFACE_BINDING + INTERFACE_BINDING2', 'INSTALL + CONSTRUCTOR_BINDING', 'INSTALL + INTERFACE_BINDING', 'CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING', 'CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING', 'CONST_BINDING + CONSTRUCTOR_BINDING', 'CONST_BINDING + INTERFACE_BINDING', 'CONSTRUCTOR_BINDING + CONST_BINDING', 'INTERFACE_BINDING + CONST_BINDING', 'INSTALL2 + CONST_BINDING', 'CONST_BINDING_FROM_INSTALL + CONST_BINDING', 'CONST_BINDING + CONST_BINDING2', ]) @pytest.mark.parametrize('XAnnot,YAnnot,Y2Annot', [ ('X', 'Y', 'Y2'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ]) def test_clash_with_binding(binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot): source = ''' struct X{}; %s %s fruit::Component getComponent() { return fruit::createComponent() %s %s; } ''' % (binding1_preparation, binding2_preparation, binding1, binding2) expect_compile_error( 'TypeAlreadyBoundError', 'Trying to bind C but it is already bound.', COMMON_DEFINITIONS, source, locals()) CONSTRUCTOR_BINDING_ANNOT1=( '', '.registerConstructor()') CONSTRUCTOR_BINDING_ANNOT2=( '', '.registerConstructor()') INTERFACE_BINDING_ANNOT1=( ''' struct Y : public X {}; ''', ''' .bind() .registerConstructor() ''') INTERFACE_BINDING_ANNOT2=( ''' struct Z : public X {}; ''', ''' .bind() .registerConstructor() ''') INSTALL_ANNOT1=( ''' fruit::Component getParentComponent1() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent1)') INSTALL_ANNOT2=( ''' fruit::Component getParentComponent2() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent2)') CONST_BINDING_FROM_INSTALL_ANNOT1=( ''' fruit::Component getParentComponent1() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent1)') CONST_BINDING_FROM_INSTALL_ANNOT2=( ''' fruit::Component getParentComponent2() { return fruit::createComponent() .registerConstructor(); } ''', '.install(getParentComponent2)') CONST_BINDING_ANNOT1=( ''' const X x1{}; ''', '.bindInstance(x1)') CONST_BINDING_ANNOT2=( ''' const X x2{}; ''', '.bindInstance(x2)') @pytest.mark.parametrize( 'binding1_preparation,binding1,binding2_preparation,binding2', [ CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2, CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2, CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2, INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2, CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, INSTALL_ANNOT1 + INSTALL_ANNOT2, CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2, CONST_BINDING_ANNOT1 + INSTALL_ANNOT2, INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, INSTALL_ANNOT1 + CONST_BINDING_ANNOT2, CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, ], ids=[ 'CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', 'CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', 'INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', 'INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', 'INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', 'INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2', 'CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', 'CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', 'CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2', 'CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', 'CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2', 'INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2', 'CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', 'CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', 'INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', 'INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', 'INSTALL_ANNOT1 + INSTALL_ANNOT2', 'CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2', 'CONST_BINDING_ANNOT1 + INSTALL_ANNOT2', 'INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', 'INSTALL_ANNOT1 + CONST_BINDING_ANNOT2', 'CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', 'CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', 'CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', 'CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', ]) def test_no_clash_with_different_annotations(binding1_preparation, binding1, binding2_preparation, binding2): source = ''' struct X {}; %s %s fruit::Component getComponent() { return fruit::createComponent() %s %s; } int main() { fruit::Injector injector(getComponent); injector.get(); injector.get(); } ''' % (binding1_preparation, binding2_preparation, binding1, binding2) expect_success( COMMON_DEFINITIONS, source) @pytest.mark.parametrize('NormalizedComponentXAnnot,ComponentXAnnot,XAnnot', [ ('X', 'X', 'X'), ('const X', 'X', 'X'), ('X', 'const X', 'X'), ('const X', 'const X', 'X'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ('fruit::Annotated', 'fruit::Annotated', 'fruit::Annotated'), ]) def test_during_component_merge(NormalizedComponentXAnnot, ComponentXAnnot, XAnnot): source = ''' struct X {}; fruit::Component getComponent1() { return fruit::createComponent() .registerConstructor(); } fruit::Component getComponent2() { return fruit::createComponent() .registerConstructor(); } void f() { fruit::NormalizedComponent nc(getComponent1); fruit::Injector<> injector(nc, getComponent2); (void) injector; } ''' expect_compile_error( 'DuplicateTypesInComponentError', 'The installed component provides some types that are already provided', COMMON_DEFINITIONS, source, locals()) def test_during_component_merge_with_different_annotation_ok(): source = ''' struct X {}; fruit::Component getComponent1() { return fruit::createComponent() .registerConstructor(); } fruit::Component getComponent2() { return fruit::createComponent() .registerConstructor(); } int main() { fruit::NormalizedComponent nc(getComponent1); fruit::Injector injector(nc, getComponent2); injector.get(); injector.get(); } ''' expect_success( COMMON_DEFINITIONS, source) @pytest.mark.parametrize('XAnnot,XAnnotRegex', [ ('X', '(struct )?X'), ('fruit::Annotated', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), ]) def test_bind_instance_and_bind_instance_runtime(XAnnot, XAnnotRegex): source = ''' struct X {}; fruit::Component<> getComponentForInstanceHelper() { // Note: don't do this in real code, leaks memory. return fruit::createComponent() .bindInstance(*(new X())); } fruit::Component getComponentForInstance() { // Note: don't do this in real code, leaks memory. return fruit::createComponent() .install(getComponentForInstanceHelper) .bindInstance(*(new X())); } int main() { fruit::Injector injector(getComponentForInstance); injector.get(); } ''' expect_runtime_error( 'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.', COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize('XAnnot,XAnnotRegex', [ ('X', '(struct )?X'), ('fruit::Annotated', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), ]) def test_bind_instance_and_binding_runtime(XAnnot, XAnnotRegex): source = ''' struct X {}; fruit::Component<> getComponentForInstanceHelper(X* x) { return fruit::createComponent() .bindInstance(*x); } fruit::Component getComponentForInstance(X* x) { return fruit::createComponent() .install(getComponentForInstanceHelper, x) .registerConstructor(); } int main() { X x; fruit::Injector injector(getComponentForInstance, &x); injector.get(); } ''' expect_runtime_error( 'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.', COMMON_DEFINITIONS, source, locals()) @pytest.mark.parametrize('XAnnot', [ 'X', 'fruit::Annotated', ]) def test_during_component_merge_consistent_ok(XAnnot): source = ''' struct X : public ConstructionTracker { using Inject = X(); }; fruit::Component getComponent() { return fruit::createComponent(); } fruit::Component<> getRootComponent() { return fruit::createComponent() .install(getComponent); } int main() { fruit::NormalizedComponent<> normalizedComponent(getRootComponent); fruit::Injector injector(normalizedComponent, getComponent); Assert(X::num_objects_constructed == 0); injector.get(); Assert(X::num_objects_constructed == 1); } ''' expect_success( COMMON_DEFINITIONS, source, locals()) if __name__== '__main__': main(__file__)