#!/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__)