summaryrefslogtreecommitdiff
path: root/tests/functional/m/member
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional/m/member')
-rw-r--r--tests/functional/m/member/member_checks.py233
-rw-r--r--tests/functional/m/member/member_checks.txt19
-rw-r--r--tests/functional/m/member/member_checks_hints.py27
-rw-r--r--tests/functional/m/member/member_checks_hints.rc3
-rw-r--r--tests/functional/m/member/member_checks_hints.txt5
-rw-r--r--tests/functional/m/member/member_checks_ignore_none.py7
-rw-r--r--tests/functional/m/member/member_checks_ignore_none.rc2
-rw-r--r--tests/functional/m/member/member_checks_ignore_none.txt1
-rw-r--r--tests/functional/m/member/member_checks_inference_improvements.py11
-rw-r--r--tests/functional/m/member/member_checks_no_hints.py27
-rw-r--r--tests/functional/m/member/member_checks_no_hints.rc2
-rw-r--r--tests/functional/m/member/member_checks_no_hints.txt5
-rw-r--r--tests/functional/m/member/member_checks_opaque.py12
-rw-r--r--tests/functional/m/member/member_checks_opaque.rc2
-rw-r--r--tests/functional/m/member/member_checks_opaque.txt1
-rw-r--r--tests/functional/m/member/member_checks_py37.py19
-rw-r--r--tests/functional/m/member/member_checks_py37.rc2
-rw-r--r--tests/functional/m/member/member_checks_py37.txt1
-rw-r--r--tests/functional/m/member/member_checks_typed_annotations.py25
-rw-r--r--tests/functional/m/member/member_checks_typed_annotations.txt1
-rw-r--r--tests/functional/m/member/membership_protocol.py123
-rw-r--r--tests/functional/m/member/membership_protocol.txt7
-rw-r--r--tests/functional/m/member/membership_protocol_py3.py36
-rw-r--r--tests/functional/m/member/membership_protocol_py3.rc2
-rw-r--r--tests/functional/m/member/membership_protocol_py3.txt3
25 files changed, 576 insertions, 0 deletions
diff --git a/tests/functional/m/member/member_checks.py b/tests/functional/m/member/member_checks.py
new file mode 100644
index 000000000..291daee12
--- /dev/null
+++ b/tests/functional/m/member/member_checks.py
@@ -0,0 +1,233 @@
+# pylint: disable=print-statement,missing-docstring,no-self-use,too-few-public-methods,bare-except,broad-except, useless-object-inheritance
+# pylint: disable=using-constant-test,expression-not-assigned, assigning-non-slot, unused-variable,pointless-statement, wrong-import-order, wrong-import-position,import-outside-toplevel
+from __future__ import print_function
+class Provider(object):
+ """provide some attributes and method"""
+ cattr = 4
+ def __init__(self):
+ self.attr = 4
+ def method(self, val):
+ """impressive method"""
+ return self.attr * val
+ def hophop(self):
+ """hop method"""
+ print('hop hop hop', self)
+
+
+class Client(object):
+ """use provider class"""
+
+ def __init__(self):
+ self._prov = Provider()
+ self._prov_attr = Provider.cattr
+ self._prov_attr2 = Provider.cattribute # [no-member]
+ self.set_later = 0
+
+ def set_set_later(self, value):
+ """set set_later attribute (introduce an inference ambiguity)"""
+ self.set_later = value
+
+ def use_method(self):
+ """use provider's method"""
+ self._prov.hophop()
+ self._prov.hophophop() # [no-member]
+
+ def use_attr(self):
+ """use provider's attr"""
+ print(self._prov.attr)
+ print(self._prov.attribute) # [no-member]
+
+ def debug(self):
+ """print debug information"""
+ print(self.__class__.__name__)
+ print(self.__doc__)
+ print(self.__dict__)
+ print(self.__module__)
+
+ def test_bt_types(self):
+ """test access to unexistant member of builtin types"""
+ lis = []
+ lis.apppend(self) # [no-member]
+ dic = {}
+ dic.set(self) # [no-member]
+ tup = ()
+ tup.append(self) # [no-member]
+ string = 'toto'
+ print(string.loower()) # [no-member]
+ integer = 1
+ print(integer.whatever) # [no-member]
+
+ def test_no_false_positives(self):
+ none = None
+ print(none.whatever)
+ # No misssing in the parents.
+ super().misssing() # [no-member]
+
+
+class Mixin(object):
+ """No no-member should be emitted for mixins."""
+
+class Getattr(object):
+ """no-member shouldn't be emitted for classes with dunder getattr."""
+
+ def __getattr__(self, attr):
+ return self.__dict__[attr]
+
+
+class Getattribute(object):
+ """no-member shouldn't be emitted for classes with dunder getattribute."""
+
+ def __getattribute__(self, attr):
+ return 42
+
+print(object.__init__)
+print(property.__init__)
+print(Client().set_later.lower())
+print(Mixin().nanana())
+print(Getattr().nananan())
+print(Getattribute().batman())
+
+try:
+ Client().missing_method()
+except AttributeError:
+ pass
+
+try:
+ Client().indeed() # [no-member]
+except ImportError:
+ pass
+
+try:
+ Client.missing()
+except AttributeError:
+ Client.missing() # [no-member]
+
+try:
+ Client.missing()
+except AttributeError:
+ try:
+ Client.missing() # [no-member]
+ except ValueError:
+ pass
+
+try:
+ if Client:
+ Client().missing()
+except AttributeError:
+ pass
+
+try:
+ Client().indeed()
+except AttributeError:
+ try:
+ Client.missing() # [no-member]
+ except Exception:
+ pass
+
+
+class SuperChecks(str, str): # pylint: disable=duplicate-bases
+ """Don't fail when the MRO is invalid."""
+ def test(self):
+ super().lalala()
+
+type(Client()).ala # [no-member]
+type({}).bala # [no-member]
+type('').portocala # [no-member]
+
+
+def socket_false_positive():
+ """Test a regression
+ Version used:
+
+ - Pylint 0.10.0
+ - Logilab common 0.15.0
+ - Logilab astroid 0.15.1
+
+ False E1101 positive, line 23:
+ Instance of '_socketobject' has no 'connect' member
+ """
+
+ import socket
+ sckt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sckt.connect(('127.0.0.1', 80))
+ sckt.close()
+
+
+def no_conjugate_member(magic_flag):
+ """should not raise E1101 on something.conjugate"""
+ if magic_flag:
+ something = 1.0
+ else:
+ something = 1.0j
+ if isinstance(something, float):
+ return something
+ return something.conjugate()
+
+
+class NoDunderNameInInstance(object):
+ """Emit a warning when accessing __name__ from an instance."""
+ def __init__(self):
+ self.var = self.__name__ # [no-member]
+
+
+class InvalidAccessBySlots(object):
+ __slots__ = ('a', )
+ def __init__(self):
+ var = self.teta # [no-member]
+ self.teta = 24
+
+
+class MetaWithDynamicGetattr(type):
+
+ def __getattr__(cls, attr):
+ return attr
+
+
+class SomeClass(object, metaclass=MetaWithDynamicGetattr):
+ pass
+
+
+SomeClass.does_not_exist
+
+class ClassWithMangledAttribute(object):
+ def __init__(self):
+ self.name = 'Bug1643'
+ def __bar(self):
+ print(self.name + "xD")
+
+ClassWithMangledAttribute()._ClassWithMangledAttribute__bar() # pylint: disable=protected-access
+
+
+import enum
+
+
+class Cls(enum.IntEnum):
+ BAR = 0
+
+
+SOME_VALUE = Cls.BAZ # [no-member]
+
+
+
+# Does not crash when inferring the `append` attribute on the slice object
+class SomeClassUsingSlice:
+ def __init__(self, flag):
+ if flag:
+ self.attribute = slice(None)
+ else:
+ self.attribute = []
+ self.attribute.append(1)
+
+from enum import Enum
+class Animal(Enum):
+ ANT = 1
+ BEE = 2
+ CAT = 3
+ DOG = 4
+# To test false positive no-member on Enum.__members__.items()
+for itm in Animal.__members__.items():
+ print(itm)
+for keyy in Animal.__members__.keys:
+ print(keyy)
+for vall in Animal.__members__.values:
+ print(vall)
diff --git a/tests/functional/m/member/member_checks.txt b/tests/functional/m/member/member_checks.txt
new file mode 100644
index 000000000..0b7ee9478
--- /dev/null
+++ b/tests/functional/m/member/member_checks.txt
@@ -0,0 +1,19 @@
+no-member:23:27:Client.__init__:Class 'Provider' has no 'cattribute' member:INFERENCE
+no-member:33:8:Client.use_method:Instance of 'Provider' has no 'hophophop' member:INFERENCE
+no-member:38:14:Client.use_attr:Instance of 'Provider' has no 'attribute' member:INFERENCE
+no-member:50:8:Client.test_bt_types:Instance of 'list' has no 'apppend' member; maybe 'append'?:INFERENCE
+no-member:52:8:Client.test_bt_types:Instance of 'dict' has no 'set' member; maybe 'get'?:INFERENCE
+no-member:54:8:Client.test_bt_types:Instance of 'tuple' has no 'append' member:INFERENCE
+no-member:56:14:Client.test_bt_types:Instance of 'str' has no 'loower' member; maybe 'lower'?:INFERENCE
+no-member:58:14:Client.test_bt_types:Instance of 'int' has no 'whatever' member:INFERENCE
+no-member:64:8:Client.test_no_false_positives:Super of 'Client' has no 'misssing' member:INFERENCE
+no-member:96:4::Instance of 'Client' has no 'indeed' member:INFERENCE
+no-member:103:4::Class 'Client' has no 'missing' member:INFERENCE
+no-member:109:8::Class 'Client' has no 'missing' member:INFERENCE
+no-member:123:8::Class 'Client' has no 'missing' member:INFERENCE
+no-member:133:0::Class 'Client' has no 'ala' member:INFERENCE
+no-member:134:0::Class 'dict' has no 'bala' member:INFERENCE
+no-member:135:0::Class 'str' has no 'portocala' member:INFERENCE
+no-member:170:19:NoDunderNameInInstance.__init__:Instance of 'NoDunderNameInInstance' has no '__name__' member:INFERENCE
+no-member:176:14:InvalidAccessBySlots.__init__:Instance of 'InvalidAccessBySlots' has no 'teta' member:INFERENCE
+no-member:208:13::Class 'Cls' has no 'BAZ' member; maybe 'BAR'?:INFERENCE
diff --git a/tests/functional/m/member/member_checks_hints.py b/tests/functional/m/member/member_checks_hints.py
new file mode 100644
index 000000000..3925f4741
--- /dev/null
+++ b/tests/functional/m/member/member_checks_hints.py
@@ -0,0 +1,27 @@
+# pylint: disable=missing-docstring, too-few-public-methods, pointless-statement, useless-object-inheritance
+
+
+class Parent(object):
+
+ def __init__(self):
+ self._parent = 42
+ self._registry = {}
+ self._similar1 = 24
+ self._similar2 = 23
+ self._similar3 = 24
+ self._really_similar1 = 23
+ self._really_similar2 = 42
+
+
+class Child(Parent):
+
+ def __init__(self):
+ super().__init__()
+
+ self._similar # [no-member]
+ self._really_similar # [no-member]
+ self._paren # [no-member]
+ # Distance is too big
+ self._registryyyy # [no-member]
+ # Nothing close.
+ self._pretty_sure_this_wont_match # [no-member]
diff --git a/tests/functional/m/member/member_checks_hints.rc b/tests/functional/m/member/member_checks_hints.rc
new file mode 100644
index 000000000..8d4b587f4
--- /dev/null
+++ b/tests/functional/m/member/member_checks_hints.rc
@@ -0,0 +1,3 @@
+[TYPECHECK]
+missing-member-max-choices = 3
+missing-member-hint-distance = 1
diff --git a/tests/functional/m/member/member_checks_hints.txt b/tests/functional/m/member/member_checks_hints.txt
new file mode 100644
index 000000000..9dd977b13
--- /dev/null
+++ b/tests/functional/m/member/member_checks_hints.txt
@@ -0,0 +1,5 @@
+no-member:21:8:Child.__init__:Instance of 'Child' has no '_similar' member; maybe one of '_similar1', '_similar2' or '_similar3'?:INFERENCE
+no-member:22:8:Child.__init__:Instance of 'Child' has no '_really_similar' member; maybe one of '_really_similar1' or '_really_similar2'?:INFERENCE
+no-member:23:8:Child.__init__:Instance of 'Child' has no '_paren' member; maybe '_parent'?:INFERENCE
+no-member:25:8:Child.__init__:Instance of 'Child' has no '_registryyyy' member:INFERENCE
+no-member:27:8:Child.__init__:Instance of 'Child' has no '_pretty_sure_this_wont_match' member:INFERENCE
diff --git a/tests/functional/m/member/member_checks_ignore_none.py b/tests/functional/m/member/member_checks_ignore_none.py
new file mode 100644
index 000000000..06f01cb6a
--- /dev/null
+++ b/tests/functional/m/member/member_checks_ignore_none.py
@@ -0,0 +1,7 @@
+# pylint: disable=missing-docstring, useless-return
+
+def func():
+ return None
+
+
+SOME_VAR = func().DOES_NOT_EXIST # [no-member]
diff --git a/tests/functional/m/member/member_checks_ignore_none.rc b/tests/functional/m/member/member_checks_ignore_none.rc
new file mode 100644
index 000000000..9ec706ec6
--- /dev/null
+++ b/tests/functional/m/member/member_checks_ignore_none.rc
@@ -0,0 +1,2 @@
+[TYPECHECK]
+ignore-none=no
diff --git a/tests/functional/m/member/member_checks_ignore_none.txt b/tests/functional/m/member/member_checks_ignore_none.txt
new file mode 100644
index 000000000..ead0e7efe
--- /dev/null
+++ b/tests/functional/m/member/member_checks_ignore_none.txt
@@ -0,0 +1 @@
+no-member:7:11::Instance of 'NoneType' has no 'DOES_NOT_EXIST' member:INFERENCE
diff --git a/tests/functional/m/member/member_checks_inference_improvements.py b/tests/functional/m/member/member_checks_inference_improvements.py
new file mode 100644
index 000000000..6eefa7958
--- /dev/null
+++ b/tests/functional/m/member/member_checks_inference_improvements.py
@@ -0,0 +1,11 @@
+# pylint: disable=missing-docstring
+
+
+def test_oserror_has_strerror():
+ # https://github.com/PyCQA/pylint/issues/2553
+ try:
+ raise OSError()
+ except OSError as exc:
+ if exc.strerror is not None:
+ return exc.strerror.lower()
+ return None
diff --git a/tests/functional/m/member/member_checks_no_hints.py b/tests/functional/m/member/member_checks_no_hints.py
new file mode 100644
index 000000000..3925f4741
--- /dev/null
+++ b/tests/functional/m/member/member_checks_no_hints.py
@@ -0,0 +1,27 @@
+# pylint: disable=missing-docstring, too-few-public-methods, pointless-statement, useless-object-inheritance
+
+
+class Parent(object):
+
+ def __init__(self):
+ self._parent = 42
+ self._registry = {}
+ self._similar1 = 24
+ self._similar2 = 23
+ self._similar3 = 24
+ self._really_similar1 = 23
+ self._really_similar2 = 42
+
+
+class Child(Parent):
+
+ def __init__(self):
+ super().__init__()
+
+ self._similar # [no-member]
+ self._really_similar # [no-member]
+ self._paren # [no-member]
+ # Distance is too big
+ self._registryyyy # [no-member]
+ # Nothing close.
+ self._pretty_sure_this_wont_match # [no-member]
diff --git a/tests/functional/m/member/member_checks_no_hints.rc b/tests/functional/m/member/member_checks_no_hints.rc
new file mode 100644
index 000000000..0f9c9ca3b
--- /dev/null
+++ b/tests/functional/m/member/member_checks_no_hints.rc
@@ -0,0 +1,2 @@
+[TYPECHECK]
+missing-member-hint=no
diff --git a/tests/functional/m/member/member_checks_no_hints.txt b/tests/functional/m/member/member_checks_no_hints.txt
new file mode 100644
index 000000000..6d818641e
--- /dev/null
+++ b/tests/functional/m/member/member_checks_no_hints.txt
@@ -0,0 +1,5 @@
+no-member:21:8:Child.__init__:Instance of 'Child' has no '_similar' member:INFERENCE
+no-member:22:8:Child.__init__:Instance of 'Child' has no '_really_similar' member:INFERENCE
+no-member:23:8:Child.__init__:Instance of 'Child' has no '_paren' member:INFERENCE
+no-member:25:8:Child.__init__:Instance of 'Child' has no '_registryyyy' member:INFERENCE
+no-member:27:8:Child.__init__:Instance of 'Child' has no '_pretty_sure_this_wont_match' member:INFERENCE
diff --git a/tests/functional/m/member/member_checks_opaque.py b/tests/functional/m/member/member_checks_opaque.py
new file mode 100644
index 000000000..9b592af64
--- /dev/null
+++ b/tests/functional/m/member/member_checks_opaque.py
@@ -0,0 +1,12 @@
+# pylint: disable=missing-docstring,import-error
+
+from unknown import Unknown, some_value
+
+
+def test(argument):
+ if argument:
+ return 42
+ return Unknown
+
+
+test(some_value).test() # [no-member]
diff --git a/tests/functional/m/member/member_checks_opaque.rc b/tests/functional/m/member/member_checks_opaque.rc
new file mode 100644
index 000000000..025cd2a0f
--- /dev/null
+++ b/tests/functional/m/member/member_checks_opaque.rc
@@ -0,0 +1,2 @@
+[TYPECHECK]
+ignore-on-opaque-inference=n
diff --git a/tests/functional/m/member/member_checks_opaque.txt b/tests/functional/m/member/member_checks_opaque.txt
new file mode 100644
index 000000000..2fabce4e2
--- /dev/null
+++ b/tests/functional/m/member/member_checks_opaque.txt
@@ -0,0 +1 @@
+no-member:12:0::Instance of 'int' has no 'test' member:INFERENCE
diff --git a/tests/functional/m/member/member_checks_py37.py b/tests/functional/m/member/member_checks_py37.py
new file mode 100644
index 000000000..8d59b7d80
--- /dev/null
+++ b/tests/functional/m/member/member_checks_py37.py
@@ -0,0 +1,19 @@
+# pylint: disable=missing-docstring
+import collections
+import asyncio
+
+isinstance([], collections.Iterable)
+
+
+async def gen():
+ await asyncio.sleep(0.1)
+ value = yield 42
+ print(value)
+ await asyncio.sleep(0.2)
+
+
+async def test():
+ generator = gen()
+
+ await generator.asend(None)
+ await generator.send(None) # [no-member]
diff --git a/tests/functional/m/member/member_checks_py37.rc b/tests/functional/m/member/member_checks_py37.rc
new file mode 100644
index 000000000..a17bb22da
--- /dev/null
+++ b/tests/functional/m/member/member_checks_py37.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.7
diff --git a/tests/functional/m/member/member_checks_py37.txt b/tests/functional/m/member/member_checks_py37.txt
new file mode 100644
index 000000000..c52eab13c
--- /dev/null
+++ b/tests/functional/m/member/member_checks_py37.txt
@@ -0,0 +1 @@
+no-member:19:10:test:AsyncGenerator 'async_generator' has no 'send' member; maybe 'asend'?:INFERENCE
diff --git a/tests/functional/m/member/member_checks_typed_annotations.py b/tests/functional/m/member/member_checks_typed_annotations.py
new file mode 100644
index 000000000..90f775a43
--- /dev/null
+++ b/tests/functional/m/member/member_checks_typed_annotations.py
@@ -0,0 +1,25 @@
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods
+class A:
+ myfield: int
+
+class B(A):
+ pass
+
+class C:
+ pass
+
+class D(C, B):
+ pass
+
+
+a = A()
+print(a.myfield)
+
+b = B()
+print(b.myfield)
+
+d = D()
+print(d.myfield)
+
+c = C()
+print(c.myfield) # [no-member]
diff --git a/tests/functional/m/member/member_checks_typed_annotations.txt b/tests/functional/m/member/member_checks_typed_annotations.txt
new file mode 100644
index 000000000..6261a31aa
--- /dev/null
+++ b/tests/functional/m/member/member_checks_typed_annotations.txt
@@ -0,0 +1 @@
+no-member:25:6::Instance of 'C' has no 'myfield' member:INFERENCE
diff --git a/tests/functional/m/member/membership_protocol.py b/tests/functional/m/member/membership_protocol.py
new file mode 100644
index 000000000..3401f4bb4
--- /dev/null
+++ b/tests/functional/m/member/membership_protocol.py
@@ -0,0 +1,123 @@
+# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,too-few-public-methods,import-error,no-init,wrong-import-position,no-else-return, comparison-with-itself, useless-object-inheritance
+
+# standard types
+1 in [1, 2, 3]
+1 in {'a': 1, 'b': 2}
+1 in {1, 2, 3}
+1 in (1, 2, 3)
+'1' in "123"
+'1' in u"123"
+'1' in bytearray(b"123")
+1 in frozenset([1, 2, 3])
+
+# comprehensions
+1 in [x ** 2 % 10 for x in range(10)]
+1 in {x ** 2 % 10 for x in range(10)}
+1 in {x: x ** 2 % 10 for x in range(10)}
+
+# iterators
+1 in iter([1, 2, 3])
+
+# generator
+def count(upto=float("inf")):
+ i = 0
+ while True:
+ if i > upto:
+ break
+ yield i
+ i += 1
+
+10 in count(upto=10)
+
+# custom instance
+class UniversalContainer(object):
+ def __contains__(self, key):
+ return True
+
+42 in UniversalContainer()
+
+# custom iterable
+class CustomIterable(object):
+ def __iter__(self):
+ return iter((1, 2, 3))
+3 in CustomIterable()
+
+# old-style iterable
+class OldStyleIterable(object):
+ def __getitem__(self, key):
+ if key < 10:
+ return 2 ** key
+ else:
+ raise IndexError("bad index")
+64 in OldStyleIterable()
+
+# do not emit warning if class has unknown bases
+from some_missing_module import ImportedClass
+
+class MaybeIterable(ImportedClass):
+ pass
+
+10 in MaybeIterable()
+
+# do not emit warning inside mixins/abstract/base classes
+class UsefulMixin(object):
+ stuff = None
+
+ def get_stuff(self):
+ return self.stuff
+
+ def act(self, thing):
+ stuff = self.get_stuff()
+ if thing in stuff:
+ pass
+
+class BaseThing(object):
+ valid_values = None
+
+ def validate(self, value):
+ if self.valid_values is None:
+ return True
+ else:
+ # error should not be emitted here
+ return value in self.valid_values
+
+class AbstractThing(object):
+ valid_values = None
+
+ def validate(self, value):
+ if self.valid_values is None:
+ return True
+ else:
+ # error should not be emitted here
+ return value in self.valid_values
+
+# class is not named as abstract
+# but still is deduceably abstract
+class Thing(object):
+ valid_values = None
+
+ def __init__(self):
+ self._init_values()
+
+ def validate(self, value):
+ if self.valid_values is None:
+ return True
+ else:
+ # error should not be emitted here
+ return value in self.valid_values
+
+ def _init_values(self):
+ raise NotImplementedError
+
+# error cases
+42 in 42 # [unsupported-membership-test]
+42 not in None # [unsupported-membership-test]
+42 in 8.5 # [unsupported-membership-test]
+
+class EmptyClass(object):
+ pass
+
+42 not in EmptyClass() # [unsupported-membership-test]
+42 in EmptyClass # [unsupported-membership-test]
+42 not in count # [unsupported-membership-test]
+42 in range # [unsupported-membership-test]
diff --git a/tests/functional/m/member/membership_protocol.txt b/tests/functional/m/member/membership_protocol.txt
new file mode 100644
index 000000000..e021062da
--- /dev/null
+++ b/tests/functional/m/member/membership_protocol.txt
@@ -0,0 +1,7 @@
+unsupported-membership-test:113:6::Value '42' doesn't support membership test
+unsupported-membership-test:114:10::Value 'None' doesn't support membership test
+unsupported-membership-test:115:6::Value '8.5' doesn't support membership test
+unsupported-membership-test:120:10::Value 'EmptyClass()' doesn't support membership test
+unsupported-membership-test:121:6::Value 'EmptyClass' doesn't support membership test
+unsupported-membership-test:122:10::Value 'count' doesn't support membership test
+unsupported-membership-test:123:6::Value 'range' doesn't support membership test
diff --git a/tests/functional/m/member/membership_protocol_py3.py b/tests/functional/m/member/membership_protocol_py3.py
new file mode 100644
index 000000000..678072354
--- /dev/null
+++ b/tests/functional/m/member/membership_protocol_py3.py
@@ -0,0 +1,36 @@
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,pointless-statement,expression-not-assigned
+
+# metaclasses that support membership test protocol
+class MetaIterable(type):
+ def __iter__(cls):
+ return iter((1, 2, 3))
+
+class MetaOldIterable(type):
+ def __getitem__(cls, key):
+ if key < 10:
+ return key ** 2
+
+ raise IndexError("bad index")
+
+class MetaContainer(type):
+ def __contains__(cls, key):
+ return False
+
+
+class IterableClass(metaclass=MetaOldIterable):
+ pass
+
+class OldIterableClass(metaclass=MetaOldIterable):
+ pass
+
+class ContainerClass(metaclass=MetaContainer):
+ pass
+
+
+def test():
+ 1 in IterableClass
+ 1 in OldIterableClass
+ 1 in ContainerClass
+ 1 in IterableClass() # [unsupported-membership-test]
+ 1 in OldIterableClass() # [unsupported-membership-test]
+ 1 in ContainerClass() # [unsupported-membership-test]
diff --git a/tests/functional/m/member/membership_protocol_py3.rc b/tests/functional/m/member/membership_protocol_py3.rc
new file mode 100644
index 000000000..c093be204
--- /dev/null
+++ b/tests/functional/m/member/membership_protocol_py3.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.0
diff --git a/tests/functional/m/member/membership_protocol_py3.txt b/tests/functional/m/member/membership_protocol_py3.txt
new file mode 100644
index 000000000..495cf94db
--- /dev/null
+++ b/tests/functional/m/member/membership_protocol_py3.txt
@@ -0,0 +1,3 @@
+unsupported-membership-test:34:9:test:Value 'IterableClass()' doesn't support membership test
+unsupported-membership-test:35:9:test:Value 'OldIterableClass()' doesn't support membership test
+unsupported-membership-test:36:9:test:Value 'ContainerClass()' doesn't support membership test