1from PyObjCTools.TestSupport import *
2import objc
3import warnings
4import sys
5import platform
6
7# Most useful systems will at least have 'NSObject'.
8NSObject = objc.lookUpClass('NSObject')
9
10# XXX : This is a really dumb way to detect < 10.3
11if not NSObject.instancesRespondToSelector_('setValue:forKey:'):
12    # Defining protocols in an MH_BUNDLE makes < 10.3 explode
13    OC_TestProtocol = None
14else:
15    from PyObjCTest.protocol import OC_TestProtocol
16
17MyProto = objc.informal_protocol("MyProto", (
18    objc.selector(None, selector=b"testMethod", signature=b"I@:", isRequired=1),
19    objc.selector(None, selector=b"testMethod2:", signature=b"v@:i", isRequired=0)
20))
21
22class TestInformalProtocols(TestCase):
23
24
25    def testMissingProto(self):
26        class ProtoClass1 (NSObject):
27            def testMethod(self):
28                pass
29        self.assertEqual(ProtoClass1.testMethod.signature, b"I@:")
30
31
32    def doIncompleteClass(self):
33        class ProtoClass2 (NSObject, MyProto):
34            def testMethod2_(self, x):
35                pass
36
37    def testIncompleteClass(self):
38        self.assertRaises(TypeError, self.doIncompleteClass)
39
40
41    @onlyIf(sys.version_info[:2] < (3,2), "not valid for python 3.3 and later")
42    def testOptional(self):
43        class ProtoClass3 (NSObject, MyProto):
44            def testMethod(self):
45                pass
46
47
48
49if (sys.maxsize < 2 ** 32 or platform.mac_ver()[0] >= '10.7') and sys.version_info[0] == 2:
50    EmptyProtocol = objc.formal_protocol("EmptyProtocol", None, ())
51
52    MyProtocol = objc.formal_protocol("MyProtocol", None, (
53        objc.selector(None, selector=b"protoMethod", signature=b"I@:"),
54        objc.selector(None, selector=b"anotherProto:with:", signature=b"v@:ii"),
55    ))
56
57    MyOtherProtocol = objc.formal_protocol("MyOtherProtocol",
58            (MyProtocol,), [
59                objc.selector(None, selector=b"yetAnother:", signature=b"i@:I")
60            ])
61
62    MyClassProtocol = objc.formal_protocol("MyClassProtocol", None, [
63        objc.selector(None, selector=b"anAnotherOne:", signature=b"i@:i"),
64        objc.selector(None, selector=b"aClassOne:", signature=b"@@:i", isClassMethod=1),
65    ])
66
67    if OC_TestProtocol is not None:
68
69        class TestFormalOCProtocols(TestCase):
70            def testMethodInfo(self):
71                actual = OC_TestProtocol.instanceMethods()
72                actual.sort(key=lambda item: item['selector'])
73                expected = [
74                    {'required': True, 'selector': b'method1', 'typestr': b'i@:'},
75                    {'required': True, 'selector': b'method2:', 'typestr': b'v@:i'}
76                ]
77                self.assertEqual(actual, expected)
78                self.assertEqual(OC_TestProtocol.classMethods(), [])
79
80                self.assertEqual(OC_TestProtocol.descriptionForInstanceMethod_(b"method1"), (b"method1", b"i@:"))
81                self.assertEqual(OC_TestProtocol.descriptionForInstanceMethod_(b"method2:"), (b"method2:", b"v@:i"))
82
83            def testImplementFormalProtocol(self):
84
85                class MyClassNotImplementingProtocol(NSObject):
86                    pass
87
88                self.assertFalse(MyClassNotImplementingProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
89
90                try:
91                    class MyClassNotAlsoImplementingProtocol(NSObject, OC_TestProtocol):
92                        def method1(self): pass
93
94                    self.fail("class not implementing protocol, yet created")
95                except TypeError:
96                    pass
97
98                class MyClassImplementingProtocol(NSObject, OC_TestProtocol):
99                    def method1(self): pass
100                    def method2_(self, a): pass
101
102                self.assertTrue(MyClassImplementingProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
103
104
105
106                # The PyObjC implementation of formal protocols is slightly looser
107                # than Objective-C itself: you can inherit part of the protocol
108                # from the superclass.
109                # XXX: not really: you won't inherit the right signatures by default
110
111                class MyClassImplementingHalfOfProtocol(NSObject):
112                    def method1(self): pass
113                    method1 = objc.selector(method1, signature=b'i@:')
114
115                self.assertFalse(MyClassImplementingHalfOfProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
116
117                class MyClassImplementingAllOfProtocol(MyClassImplementingHalfOfProtocol, OC_TestProtocol):
118                    def method2_(self, v): pass
119
120                self.assertTrue(MyClassImplementingAllOfProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
121
122
123
124
125    class TestFormalProtocols (TestCase):
126        # Implement unittests for formal protocols here.
127        #
128
129        def testImplementAnotherObject(self):
130            anObject = NSObject.alloc().init()
131
132            try:
133                class MyClassImplementingAnotherObject(NSObject, anObject):
134                    pass
135                self.fail()
136            except TypeError:
137                pass
138
139            try:
140                class MyClassImplementingAnotherObject(NSObject, 10):
141                    pass
142                self.fail()
143            except TypeError:
144                pass
145
146            try:
147                class MyClassImplementingAnotherObject(NSObject, int):
148                    pass
149                self.fail()
150            except TypeError:
151                pass
152
153        def dont_testDefiningingProtocols(self):
154
155            # Pretty useless, but should work
156
157            self.assertTrue(MyOtherProtocol.conformsTo_(MyProtocol))
158
159
160            try:
161                class MyClassImplementingMyProtocol(NSObject, MyProtocol):
162                    pass
163
164                # Declare to implement a protocol, but don't do it?
165                self.fail()
166            except TypeError:
167                pass
168
169
170            class MyClassImplementingMyProtocol(NSObject, MyProtocol):
171                def protoMethod(self):
172                    return 1
173
174                def anotherProto_with_(self, a1, a2):
175                    pass
176
177            self.assertEqual(MyClassImplementingMyProtocol.protoMethod.signature, b"I@:")
178            self.assertEqual(MyClassImplementingMyProtocol.anotherProto_with_.signature, b"v@:ii")
179            self.assertTrue(MyClassImplementingMyProtocol.pyobjc_classMethods.conformsToProtocol_(MyProtocol))
180
181            class MyClassImplementingMyOtherProtocol(NSObject, MyOtherProtocol):
182                def protoMethod(self): pass
183                def anotherProto_with_(self, a1, a2): pass
184                def yetAnother_(self, a): pass
185
186            self.assertEqual(MyClassImplementingMyOtherProtocol.protoMethod.signature, b"I@:")
187            self.assertEqual(MyClassImplementingMyOtherProtocol.anotherProto_with_.signature, b"v@:ii")
188            self.assertEqual(MyClassImplementingMyOtherProtocol.yetAnother_.signature, b"i@:I")
189            self.assertTrue(MyClassImplementingMyOtherProtocol.pyobjc_classMethods.conformsToProtocol_(MyProtocol))
190            self.assertTrue(MyClassImplementingMyOtherProtocol.pyobjc_classMethods.conformsToProtocol_(MyOtherProtocol))
191
192            try:
193                class ImplementingMyClassProtocol(NSObject, MyClassProtocol):
194                    pass
195
196                self.fail()
197            except TypeError:
198                pass
199
200            class ImplementingMyClassProtocol(NSObject, MyClassProtocol):
201                def anAnotherOne_(self, a):
202                    pass
203
204                def aClassOne_(self, a):
205                    pass
206
207                aClassOne_ = classmethod(aClassOne_)
208
209            self.assertEqual(ImplementingMyClassProtocol.anAnotherOne_.signature, b'i@:i')
210            self.assertEqual(ImplementingMyClassProtocol.aClassOne_.isClassMethod, True)
211            self.assertEqual(ImplementingMyClassProtocol.aClassOne_.signature, b'@@:i')
212
213            # TODO: protocol with class and instance method with different
214            # signatures.
215            # TODO: should not need to specify classmethod() if it can be
216            # deduced from the protocol
217
218
219        def testIncorrectlyDefiningFormalProtocols(self):
220            # Some bad calls to objc.formal_protocol
221            self.assertRaises(TypeError, objc.formal_protocol, [], None, ())
222            self.assertRaises(TypeError, objc.formal_protocol, 'supers', (NSObject,) , ())
223            self.assertRaises(TypeError, objc.formal_protocol, 'supers', objc.protocolNamed('NSLocking') , ())
224            self.assertRaises(TypeError, objc.formal_protocol, 'supers', [
225                    objc.protocolNamed('NSLocking'),
226                    "hello",
227                ], ())
228            self.assertRaises(TypeError, objc.formal_protocol, 'supers', None, [
229                    objc.selector(None, selector=b'fooMethod:', signature=b'v@:i'),
230                    "hello",
231                ])
232
233        def testMethodInfo(self):
234            self.assertEqual(MyProtocol.instanceMethods(), [
235                {'typestr': b'I@:', 'required': True, 'selector': b'protoMethod'},
236                {'typestr': b'v@:ii', 'required': True, 'selector': b'anotherProto:with:'},
237            ])
238            self.assertEqual(MyProtocol.classMethods(), [
239            ])
240            self.assertEqual(
241                    MyProtocol.descriptionForInstanceMethod_(b"protoMethod"),
242                        ("protoMethod", "I@:"))
243
244            self.assertEqual(
245                    MyProtocol.descriptionForInstanceMethod_(b"nosuchmethod"),
246                        None)
247
248            self.assertEqual(MyClassProtocol.classMethods(), [
249                {'required': True, 'selector': 'aClassOne:', 'typestr': '@@:i'}
250            ])
251            self.assertEqual(MyProtocol.classMethods(), [
252            ])
253            self.assertEqual(
254                    MyClassProtocol.descriptionForClassMethod_(b"aClassOne:"),
255                        ("aClassOne:", "@@:i"))
256
257            self.assertEqual(
258                    MyClassProtocol.descriptionForClassMethod_(b"nosuchmethod"),
259                        None)
260
261
262        def dont_testObjCInterface(self):
263            # TODO: tests that access the Objective-C interface of protocols
264            # (those methods should be forwarded to the underlying object, as
265            #  with objc.pyobjc_unicode).
266            # NOTE: This is not very important, the only methods that are not
267            # explicitly wrapped should be compatibility methods that will
268            # cause a warning when called.
269            self.assertEqual(1, 0)
270
271if __name__ == '__main__':
272    main()
273