Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Gwenaël Samain
cython
Commits
6ae02221
Commit
6ae02221
authored
Oct 15, 2011
by
Stefan Behnel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
extended test case to include test code for ticket #3
parent
c95fe868
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
473 additions
and
102 deletions
+473
-102
tests/run/special_methods_T561.pyx
tests/run/special_methods_T561.pyx
+473
-102
No files found.
tests/run/special_methods_T561.pyx
View file @
6ae02221
# mode: run
# ticket: 561
# ticket: 3
# The patch in #561 changes code generation for most special methods
# to remove the Cython-generated wrapper and let PyType_Ready()
# generate its own wrapper. (This wrapper would be used, for instance,
...
...
@@ -12,13 +15,117 @@
# special_methods_T561_py3.pyx for tests of the differences between
# Python 2 and 3.
# Regarding ticket 3, we should additionally test that unbound method
# calls to these special methods (e.g. ExtType.__init__()) do not use
# a runtime lookup indirection.
import
sys
__doc__
=
u"""
>>> # If you define either setitem or delitem, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> si_setitem = SetItem().__setitem__
>>> si_setitem('foo', 'bar')
SetItem setitem 'foo' 'bar'
>>> si_delitem = SetItem().__delitem__
>>> si_delitem('foo')
Traceback (most recent call last):
...
NotImplementedError: Subscript deletion not supported by special_methods_T561.SetItem
>>> di_setitem = DelItem().__setitem__
>>> di_setitem('foo', 'bar')
Traceback (most recent call last):
...
NotImplementedError: Subscript assignment not supported by special_methods_T561.DelItem
>>> di_delitem = DelItem().__delitem__
>>> di_delitem('foo')
DelItem delitem 'foo'
>>> sdi_setitem = SetDelItem().__setitem__
>>> sdi_setitem('foo', 'bar')
SetDelItem setitem 'foo' 'bar'
>>> sdi_delitem = SetDelItem().__delitem__
>>> sdi_delitem('foo')
SetDelItem delitem 'foo'
>>> g01 = object.__getattribute__(GetAttr(), '__getattribute__')
>>> g01('attr')
GetAttr getattr 'attr'
>>> g10 = object.__getattribute__(GetAttribute(), '__getattr__')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.GetAttribute' object has no attribute '__getattr__'
>>> g11 = object.__getattribute__(GetAttribute(), '__getattribute__')
>>> g11('attr')
GetAttribute getattribute 'attr'
>>> # If you define either setattr or delattr, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> sa_setattr = SetAttr().__setattr__
>>> sa_setattr('foo', 'bar')
SetAttr setattr 'foo' 'bar'
>>> sa_delattr = SetAttr().__delattr__
>>> sa_delattr('foo')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.SetAttr' object has no attribute 'foo'
>>> da_setattr = DelAttr().__setattr__
>>> da_setattr('foo', 'bar')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.DelAttr' object has no attribute 'foo'
>>> da_delattr = DelAttr().__delattr__
>>> da_delattr('foo')
DelAttr delattr 'foo'
>>> sda_setattr = SetDelAttr().__setattr__
>>> sda_setattr('foo', 'bar')
SetDelAttr setattr 'foo' 'bar'
>>> sda_delattr = SetDelAttr().__delattr__
>>> sda_delattr('foo')
SetDelAttr delattr 'foo'
>>> # If you define either set or delete, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> s_set = Set().__set__
>>> s_set('instance', 'val')
Set set 'instance' 'val'
>>> s_delete = Set().__delete__
>>> s_delete('instance')
Traceback (most recent call last):
...
NotImplementedError: __delete__
>>> d_set = Delete().__set__
>>> d_set('instance', 'val')
Traceback (most recent call last):
...
NotImplementedError: __set__
>>> d_delete = Delete().__delete__
>>> d_delete('instance')
Delete delete 'instance'
>>> sd_set = SetDelete().__set__
>>> sd_set('instance', 'val')
SetDelete set 'instance' 'val'
>>> sd_delete = SetDelete().__delete__
>>> sd_delete('instance')
SetDelete delete 'instance'
>>> # If you define __long__, you get a wrapper object for __int__.
>>> # (This behavior is unchanged by #561.)
>>> Li = Long().__int__
>>> Li()
Long __long__
"""
if
sys
.
version_info
>=
(
2
,
5
):
__doc__
+=
u"""
\
>>> vs0 = VerySpecial(0)
VS __init__ 0
>>> vs0_index = vs0.__index__
>>> vs0_index()
VS __index__ 0
"""
cdef
class
VerySpecial
:
"""
>>> vs0 = VerySpecial(0)
VS __init__ 0
>>> vs1 = VerySpecial(1)
VS __init__ 1
>>> vs0_add = vs0.__add__
>>> vs0_add(vs1)
VS __add__ 0 1
...
...
@@ -114,8 +221,9 @@ __doc__ = u"""
>>> vs0_itruediv = vs0.__itruediv__
>>> vs0_itruediv(vs1)
VS __itruediv__ 0 /= 1
>>> # If you define an arithmetic method, you get wrapper objects for
>>> # the reversed version as well. (This behavior is unchanged by #561.)
# If you define an arithmetic method, you get wrapper objects for
# the reversed version as well. (This behavior is unchanged by #561.)
>>> vs0_radd = vs0.__radd__
>>> vs0_radd(vs1)
VS __add__ 1 0
...
...
@@ -166,30 +274,6 @@ __doc__ = u"""
>>> vs0_len()
VS __len__ 0
0
>>> # If you define either setitem or delitem, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> si_setitem = SetItem().__setitem__
>>> si_setitem('foo', 'bar')
SetItem setitem 'foo' 'bar'
>>> si_delitem = SetItem().__delitem__
>>> si_delitem('foo')
Traceback (most recent call last):
...
NotImplementedError: Subscript deletion not supported by special_methods_T561.SetItem
>>> di_setitem = DelItem().__setitem__
>>> di_setitem('foo', 'bar')
Traceback (most recent call last):
...
NotImplementedError: Subscript assignment not supported by special_methods_T561.DelItem
>>> di_delitem = DelItem().__delitem__
>>> di_delitem('foo')
DelItem delitem 'foo'
>>> sdi_setitem = SetDelItem().__setitem__
>>> sdi_setitem('foo', 'bar')
SetDelItem setitem 'foo' 'bar'
>>> sdi_delitem = SetDelItem().__delitem__
>>> sdi_delitem('foo')
SetDelItem delitem 'foo'
>>> vs0_repr = vs0.__repr__
>>> vs0_repr()
VS __repr__ 0
...
...
@@ -203,44 +287,11 @@ __doc__ = u"""
>>> vs0_str = vs0.__str__
>>> vs0_str()
VS __str__ 0
>>> g01 = object.__getattribute__(GetAttr(), '__getattribute__')
>>> g01('attr')
GetAttr getattr 'attr'
>>> g10 = object.__getattribute__(GetAttribute(), '__getattr__')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.GetAttribute' object has no attribute '__getattr__'
>>> g11 = object.__getattribute__(GetAttribute(), '__getattribute__')
>>> g11('attr')
GetAttribute getattribute 'attr'
>>> # If you define either setattr or delattr, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> sa_setattr = SetAttr().__setattr__
>>> sa_setattr('foo', 'bar')
SetAttr setattr 'foo' 'bar'
>>> sa_delattr = SetAttr().__delattr__
>>> sa_delattr('foo')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.SetAttr' object has no attribute 'foo'
>>> da_setattr = DelAttr().__setattr__
>>> da_setattr('foo', 'bar')
Traceback (most recent call last):
...
AttributeError: 'special_methods_T561.DelAttr' object has no attribute 'foo'
>>> da_delattr = DelAttr().__delattr__
>>> da_delattr('foo')
DelAttr delattr 'foo'
>>> sda_setattr = SetDelAttr().__setattr__
>>> sda_setattr('foo', 'bar')
SetDelAttr setattr 'foo' 'bar'
>>> sda_delattr = SetDelAttr().__delattr__
>>> sda_delattr('foo')
SetDelAttr delattr 'foo'
>>> # If you define __richcmp__, you get all of __lt__, __le__,
>>> # __eq__, __ne__, __gt__, __ge__ (this behavior is unchanged by #561).
>>> # (you don't get a __richcmp__ method, because it doesn't have a
>>> # Python signature)
# If you define __richcmp__, you get all of __lt__, __le__,
# __eq__, __ne__, __gt__, __ge__ (this behavior is unchanged by #561).
# (you don't get a __richcmp__ method, because it doesn't have a
# Python signature)
>>> vs0_lt = vs0.__lt__
>>> vs0_lt(vs1)
VS richcmp 0 1 (kind=0)
...
...
@@ -265,50 +316,14 @@ __doc__ = u"""
>>> vs0_next = vs0.__next__
>>> vs0_next()
VS next/__next__ 0
>>> vs0_get = vs0.__get__
>>> vs0_get('instance', 'owner')
VS __get__ 0 'instance' 'owner'
>>> # If you define either set or delete, you get wrapper objects
>>> # for both methods. (This behavior is unchanged by #561.)
>>> s_set = Set().__set__
>>> s_set('instance', 'val')
Set set 'instance' 'val'
>>> s_delete = Set().__delete__
>>> s_delete('instance')
Traceback (most recent call last):
...
NotImplementedError: __delete__
>>> d_set = Delete().__set__
>>> d_set('instance', 'val')
Traceback (most recent call last):
...
NotImplementedError: __set__
>>> d_delete = Delete().__delete__
>>> d_delete('instance')
Delete delete 'instance'
>>> sd_set = SetDelete().__set__
>>> sd_set('instance', 'val')
SetDelete set 'instance' 'val'
>>> sd_delete = SetDelete().__delete__
>>> sd_delete('instance')
SetDelete delete 'instance'
>>> vs0_init = vs0.__init__
>>> vs0_init(0)
VS __init__ 0
>>> # If you define __long__, you get a wrapper object for __int__.
>>> # (This behavior is unchanged by #561.)
>>> Li = Long().__int__
>>> Li()
Long __long__
"""
if
sys
.
version_info
>=
(
2
,
5
):
__doc__
+=
u"""
\
>>> vs0_index = vs0.__index__
>>> vs0_index()
VS __index__ 0
"""
cdef
class
VerySpecial
:
"""
cdef
readonly
int
value
def
__init__
(
self
,
v
):
...
...
@@ -552,3 +567,359 @@ cdef class GetAttrGetItemRedirect:
if
key
==
'attr'
:
return
getattr
(
self
,
key
)
return
(
'item'
,
self
.
obj
)
# test unbound method usage in subtypes
cdef
class
VerySpecialSubType
(
VerySpecial
):
"""
>>> vs0 = VerySpecialSubType(0)
VS __init__ 0
>>> vs1 = VerySpecialSubType(1)
VS __init__ 1
>>> vs0_add = vs0.__add__
>>> vs0_add(vs1)
VS __add__ 0 1
>>> vs0_sub = vs0.__sub__
>>> vs0_sub(vs1)
VS __sub__ 0 1
>>> vs0_mul = vs0.__mul__
>>> vs0_mul(vs1)
VS __mul__ 0 1
>>> vs0_mod = vs0.__mod__
>>> vs0_mod(vs1)
VS __mod__ 0 1
>>> vs0_divmod = vs0.__divmod__
>>> vs0_divmod(vs1)
VS __divmod__ 0 1
>>> vs0_pow = vs0.__pow__
>>> vs0_pow(vs1)
VS __pow__ pow(0, 1, None)
>>> vs0_pow(vs1, 13)
VS __pow__ pow(0, 1, 13)
>>> vs0_neg = vs0.__neg__
>>> vs0_neg()
VS __neg__ 0
>>> vs0_pos = vs0.__pos__
>>> vs0_pos()
VS __pos__ 0
>>> vs0_abs = vs0.__abs__
>>> vs0_abs()
VS __abs__ 0
>>> vs0_invert = vs0.__invert__
>>> vs0_invert()
VS __invert__ 0
>>> vs0_lshift = vs0.__lshift__
>>> vs0_lshift(vs1)
VS __lshift__ 0 << 1
>>> vs0_rshift = vs0.__rshift__
>>> vs0_rshift(vs1)
VS __rshift__ 0 >> 1
>>> vs0_and = vs0.__and__
>>> vs0_and(vs1)
VS __and__ 0 & 1
>>> vs0_xor = vs0.__xor__
>>> vs0_xor(vs1)
VS __xor__ 0 ^ 1
>>> vs0_or = vs0.__or__
>>> vs0_or(vs1)
VS __or__ 0 | 1
>>> vs0_int = vs0.__int__
>>> vs0_int()
VS __int__ 0
>>> vs0_float = vs0.__float__
>>> vs0_float()
VS __float__ 0
>>> vs0_iadd = vs0.__iadd__
>>> vs0_iadd(vs1)
VS __iadd__ 0 += 1
>>> vs0_isub = vs0.__isub__
>>> vs0_isub(vs1)
VS __isub__ 0 -= 1
>>> vs0_imul = vs0.__imul__
>>> vs0_imul(vs1)
VS __imul__ 0 *= 1
>>> vs0_imod = vs0.__imod__
>>> vs0_imod(vs1)
VS __imod__ 0 %= 1
>>> vs0_ipow = vs0.__ipow__
>>> vs0_ipow(vs1)
VS __ipow__ 0 1
>>> vs0_ilshift = vs0.__ilshift__
>>> vs0_ilshift(vs1)
VS __ilshift__ 0 <<= 1
>>> vs0_irshift = vs0.__irshift__
>>> vs0_irshift(vs1)
VS __irshift__ 0 >>= 1
>>> vs0_iand = vs0.__iand__
>>> vs0_iand(vs1)
VS __iand__ 0 &= 1
>>> vs0_ixor = vs0.__ixor__
>>> vs0_ixor(vs1)
VS __ixor__ 0 ^= 1
>>> vs0_ior = vs0.__ior__
>>> vs0_ior(vs1)
VS __ior__ 0 |= 1
>>> vs0_floordiv = vs0.__floordiv__
>>> vs0_floordiv(vs1)
VS __floordiv__ 0 / 1
>>> vs0_truediv = vs0.__truediv__
>>> vs0_truediv(vs1)
VS __truediv__ 0 / 1
>>> vs0_ifloordiv = vs0.__ifloordiv__
>>> vs0_ifloordiv(vs1)
VS __ifloordiv__ 0 /= 1
>>> vs0_itruediv = vs0.__itruediv__
>>> vs0_itruediv(vs1)
VS __itruediv__ 0 /= 1
# If you define an arithmetic method, you get wrapper objects for
# the reversed version as well. (This behavior is unchanged by #561.)
>>> vs0_radd = vs0.__radd__
>>> vs0_radd(vs1)
VS __add__ 1 0
>>> vs0_rsub = vs0.__rsub__
>>> vs0_rsub(vs1)
VS __sub__ 1 0
>>> vs0_rmul = vs0.__rmul__
>>> vs0_rmul(vs1)
VS __mul__ 1 0
>>> vs0_rmod = vs0.__rmod__
>>> vs0_rmod(vs1)
VS __mod__ 1 0
>>> vs0_rdivmod = vs0.__rdivmod__
>>> vs0_rdivmod(vs1)
VS __divmod__ 1 0
>>> vs0_rpow = vs0.__rpow__
>>> vs0_rpow(vs1)
VS __pow__ pow(1, 0, None)
>>> vs0_rlshift = vs0.__rlshift__
>>> vs0_rlshift(vs1)
VS __lshift__ 1 << 0
>>> vs0_rrshift = vs0.__rrshift__
>>> vs0_rrshift(vs1)
VS __rshift__ 1 >> 0
>>> vs0_rand = vs0.__rand__
>>> vs0_rand(vs1)
VS __and__ 1 & 0
>>> vs0_rxor = vs0.__rxor__
>>> vs0_rxor(vs1)
VS __xor__ 1 ^ 0
>>> vs0_ror = vs0.__ror__
>>> vs0_ror(vs1)
VS __or__ 1 | 0
>>> vs0_rfloordiv = vs0.__rfloordiv__
>>> vs0_rfloordiv(vs1)
VS __floordiv__ 1 / 0
>>> vs0_rtruediv = vs0.__rtruediv__
>>> vs0_rtruediv(vs1)
VS __truediv__ 1 / 0
>>> vs0_getitem = vs0.__getitem__
>>> vs0_getitem('foo')
VS __getitem__ 0['foo']
>>> vs0_contains = vs0.__contains__
>>> vs0_contains(vs1)
VS __contains__ 0 1
False
>>> vs0_len = vs0.__len__
>>> vs0_len()
VS __len__ 0
0
>>> vs0_repr = vs0.__repr__
>>> vs0_repr()
VS __repr__ 0
>>> vs0_hash = vs0.__hash__
>>> vs0_hash()
VS __hash__ 0
1000
>>> vs0_call = vs0.__call__
>>> vs0_call(vs1)
VS __call__ 0(1)
>>> vs0_str = vs0.__str__
>>> vs0_str()
VS __str__ 0
>>> vs0_lt = vs0.__lt__
>>> vs0_lt(vs1)
VS richcmp 0 1 (kind=0)
>>> vs0_le = vs0.__le__
>>> vs0_le(vs1)
VS richcmp 0 1 (kind=1)
>>> vs0_eq = vs0.__eq__
>>> vs0_eq(vs1)
VS richcmp 0 1 (kind=2)
>>> vs0_ne = vs0.__ne__
>>> vs0_ne(vs1)
VS richcmp 0 1 (kind=3)
>>> vs0_gt = vs0.__gt__
>>> vs0_gt(vs1)
VS richcmp 0 1 (kind=4)
>>> vs0_ge = vs0.__ge__
>>> vs0_ge(vs1)
VS richcmp 0 1 (kind=5)
>>> vs0_iter = vs0.__iter__
>>> vs0_iter()
VS __iter__ 0
>>> vs0_next = vs0.__next__
>>> vs0_next()
VS next/__next__ 0
>>> vs0_get = vs0.__get__
>>> vs0_get('instance', 'owner')
VS __get__ 0 'instance' 'owner'
>>> vs0_init = vs0.__init__
>>> vs0_init(0)
VS __init__ 0
"""
def
__init__
(
self
,
v
):
VerySpecial
.
__init__
(
self
,
v
)
def
__add__
(
self
,
other
):
return
VerySpecial
.
__add__
(
self
,
other
)
def
__sub__
(
self
,
other
):
return
VerySpecial
.
__sub__
(
self
,
other
)
def
__mul__
(
self
,
other
):
return
VerySpecial
.
__mul__
(
self
,
other
)
def
__div__
(
self
,
other
):
return
VerySpecial
.
__div__
(
self
,
other
)
def
__mod__
(
self
,
other
):
return
VerySpecial
.
__mod__
(
self
,
other
)
def
__divmod__
(
self
,
other
):
return
VerySpecial
.
__divmod__
(
self
,
other
)
def
__pow__
(
self
,
other
,
mod
):
return
VerySpecial
.
__pow__
(
self
,
other
,
mod
)
def
__lshift__
(
self
,
other
):
return
VerySpecial
.
__lshift__
(
self
,
other
)
def
__rshift__
(
self
,
other
):
return
VerySpecial
.
__rshift__
(
self
,
other
)
def
__and__
(
self
,
other
):
return
VerySpecial
.
__and__
(
self
,
other
)
def
__xor__
(
self
,
other
):
return
VerySpecial
.
__xor__
(
self
,
other
)
def
__or__
(
self
,
other
):
return
VerySpecial
.
__or__
(
self
,
other
)
def
__floordiv__
(
self
,
other
):
return
VerySpecial
.
__floordiv__
(
self
,
other
)
def
__truediv__
(
self
,
other
):
return
VerySpecial
.
__truediv__
(
self
,
other
)
def
__neg__
(
self
):
return
VerySpecial
.
__neg__
(
self
)
def
__pos__
(
self
):
return
VerySpecial
.
__pos__
(
self
)
def
__abs__
(
self
):
return
VerySpecial
.
__abs__
(
self
)
def
__nonzero__
(
self
):
return
VerySpecial
.
__nonzero__
(
self
)
def
__invert__
(
self
):
return
VerySpecial
.
__invert__
(
self
)
def
__int__
(
self
):
return
VerySpecial
.
__int__
(
self
)
def
__long__
(
self
):
return
VerySpecial
.
__long__
(
self
)
def
__float__
(
self
):
return
VerySpecial
.
__float__
(
self
)
def
__oct__
(
self
):
return
VerySpecial
.
__oct__
(
self
)
def
__hex__
(
self
):
return
VerySpecial
.
__hex__
(
self
)
def
__iadd__
(
self
,
other
):
return
VerySpecial
.
__iadd__
(
self
,
other
)
def
__isub__
(
self
,
other
):
return
VerySpecial
.
__isub__
(
self
,
other
)
def
__imul__
(
self
,
other
):
return
VerySpecial
.
__imul__
(
self
,
other
)
def
__idiv__
(
self
,
other
):
return
VerySpecial
.
__idiv__
(
self
,
other
)
def
__imod__
(
self
,
other
):
return
VerySpecial
.
__imod__
(
self
,
other
)
def
__ipow__
(
self
,
other
):
return
VerySpecial
.
__ipow__
(
self
,
other
)
def
__ilshift__
(
self
,
other
):
return
VerySpecial
.
__ilshift__
(
self
,
other
)
def
__irshift__
(
self
,
other
):
return
VerySpecial
.
__irshift__
(
self
,
other
)
def
__iand__
(
self
,
other
):
return
VerySpecial
.
__iand__
(
self
,
other
)
def
__ixor__
(
self
,
other
):
return
VerySpecial
.
__ixor__
(
self
,
other
)
def
__ior__
(
self
,
other
):
return
VerySpecial
.
__ior__
(
self
,
other
)
def
__ifloordiv__
(
self
,
other
):
return
VerySpecial
.
__ifloordiv__
(
self
,
other
)
def
__itruediv__
(
self
,
other
):
return
VerySpecial
.
__itruediv__
(
self
,
other
)
def
__index__
(
self
):
return
VerySpecial
.
__index__
(
self
)
def
__getitem__
(
self
,
index
):
return
VerySpecial
.
__getitem__
(
self
,
index
)
def
__contains__
(
self
,
other
):
return
VerySpecial
.
__contains__
(
self
,
other
)
def
__len__
(
self
):
return
VerySpecial
.
__len__
(
self
)
def
__cmp__
(
self
,
other
):
return
VerySpecial
.
__cmp__
(
self
,
other
)
def
__repr__
(
self
):
return
VerySpecial
.
__repr__
(
self
)
def
__hash__
(
self
):
return
VerySpecial
.
__hash__
(
self
)
def
__call__
(
self
,
arg
):
return
VerySpecial
.
__call__
(
self
,
arg
)
def
__str__
(
self
):
return
VerySpecial
.
__str__
(
self
)
# there is no __richcmp__ at the Python level
# def __richcmp__(self, other, kind):
# return VerySpecial.__richcmp__(self, other, kind)
def
__iter__
(
self
):
return
VerySpecial
.
__iter__
(
self
)
def
__next__
(
self
):
return
VerySpecial
.
__next__
(
self
)
def
__get__
(
self
,
inst
,
own
):
return
VerySpecial
.
__get__
(
self
,
inst
,
own
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment