1 /* ObjectTesting - Include basic object tests for the GNUstep Testsuite
2 
3    Copyright (C) 2005 Free Software Foundation, Inc.
4 
5    Written by: Matt Rice?
6 
7    This package is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public
9    License as published by the Free Software Foundation; either
10    version 2 of the License, or (at your option) any later version.
11 
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17 */
18 #import <Testing.h>
19 #import <Foundation/NSArray.h>
20 #import <Foundation/NSDictionary.h>
21 #import <Foundation/NSData.h>
22 #import <Foundation/NSArchiver.h>
23 #import <Foundation/NSKeyedArchiver.h>
24 
25 /* This file contains macros of testing some basic protocol implementation
26  * common to almost all classes.
27  *
28  * The reason for using macros rather than functions is that it allows the
29  * preprocessor and compiler to generate messages containign the file name
30  * and line number of the location of problems in your own testcase code.
31  *
32  * Sometimes, with a complex testing process, you want the location of the
33  * problem  within that process ... so to aid that we also have function
34  * equivalents of the macros.
35  */
36 
37 /* Macro to perform basic allocation tests
38  */
39 #define TEST_ALLOC(CN) \
40 { \
41   NSString *className = (CN); \
42   Class theClass = NSClassFromString(className); \
43   id obj0 = nil; \
44   id obj1 = nil; \
45   const char *prefix = [[NSString stringWithFormat: @"Class '%@'", className] \
46     UTF8String]; \
47   NSZone *testZone = NSCreateZone(1024, 1024, 1); \
48   PASS(theClass != Nil, "%s exists", prefix); \
49 \
50   obj0 = [theClass alloc]; \
51   PASS(obj0 != nil, "%s has working alloc", prefix); \
52   PASS([obj0 isKindOfClass: theClass], \
53     "%s alloc gives the correct class", prefix); \
54   obj0 = [[theClass alloc] init]; \
55   PASS([obj0 isKindOfClass: theClass], "%s has working init", prefix); \
56 \
57   obj0 = [theClass new]; \
58   PASS([obj0 isKindOfClass: theClass], "%s has working new", prefix); \
59 \
60   obj1 = [theClass allocWithZone: testZone]; \
61   PASS([obj1 isKindOfClass: theClass],"%s has working allocWithZone",prefix); \
62 }
63 static void test_alloc(NSString *CN) __attribute__ ((unused));
test_alloc(NSString * CN)64 static void test_alloc(NSString *CN)
65 {
66   TEST_ALLOC(CN);
67 }
68 
69 /* Macro to perform basic allocation tests without initialisation
70  */
71 #define TEST_ALLOC_ONLY(CN) \
72 { \
73   NSString *className = (CN); \
74   Class theClass = NSClassFromString(className); \
75   id obj0 = nil; \
76   id obj1 = nil; \
77   const char *prefix = [[NSString stringWithFormat: @"Class '%@'", className] \
78     UTF8String]; \
79   NSZone *testZone = NSCreateZone(1024, 1024, 1); \
80   PASS(theClass != Nil, "%s exists", prefix); \
81    \
82   obj0 = [theClass alloc]; \
83   PASS(obj0 != nil, "%s has working alloc", prefix); \
84   PASS([obj0 isKindOfClass: theClass], \
85     "%s alloc gives the correct class", prefix); \
86   PASS_EXCEPTION([obj0 description], NSInvalidArgumentException, \
87     "raises NSInvalidArgumentException in description") \
88 \
89   PASS_EXCEPTION(if([obj0 init]==nil)[NSException raise: NSInvalidArgumentException format: @""], \
90     NSInvalidArgumentException, \
91     "returns nil or raises NSInvalidArgumentException in init") \
92 \
93   PASS_EXCEPTION(if([theClass new]==nil)[NSException raise: NSInvalidArgumentException format: @""], \
94     NSInvalidArgumentException, \
95     "returns nil or raises NSInvalidArgumentException in new") \
96 \
97   obj1 = [theClass allocWithZone: testZone]; \
98   PASS([obj1 isKindOfClass: theClass],"%s has working allocWithZone",prefix); \
99 }
100 static void test_alloc_only(NSString *CN) __attribute__ ((unused));
test_alloc_only(NSString * CN)101 static void test_alloc_only(NSString *CN)
102 {
103   TEST_ALLOC_ONLY(CN);
104 }
105 
106 
107 /* Macro to test for the NSObject protocol
108  * Arguments are:
109  * CN   The name of the class to be tested
110  * OJS  An arraayof objects to be tested
111  */
112 #define TEST_NSOBJECT(CN, OJS) \
113 { \
114   NSString *className = (CN); \
115   NSArray *objects = (OJS); \
116   int i; \
117   Class theClass = Nil; \
118   theClass = NSClassFromString(className); \
119   PASS(theClass != Nil, "%s is a known className", [className UTF8String]); \
120    \
121   for (i = 0; i < [objects count]; i++) \
122     { \
123       id theObj = [objects objectAtIndex: i]; \
124       id mySelf = nil; \
125       Class myClass = Nil; \
126       int count1; \
127       int count2; \
128       Class sup = Nil; \
129       const char *prefix; \
130       id r; \
131  \
132       prefix = [[NSString stringWithFormat: @"Object %i of class '%@'", \
133         i, className] UTF8String]; \
134       PASS([theObj conformsToProtocol: @protocol(NSObject)], \
135 	"%s conforms to NSObject", prefix); \
136       mySelf = [theObj self]; \
137       PASS(mySelf == theObj, "%s can return self", prefix); \
138       myClass = [theObj class]; \
139       PASS(myClass != Nil, "%s can return own class", prefix); \
140       PASS([theObj isKindOfClass: theClass], \
141 	"%s object %.160s is of correct class", prefix, \
142 	[[theObj description] UTF8String]); \
143       PASS(mySelf == myClass ? ![theObj isMemberOfClass: myClass] \
144 	: [theObj isMemberOfClass: myClass], \
145         "%s isMemberOfClass works", prefix); \
146       sup = [theObj superclass]; \
147       PASS(theClass == NSClassFromString(@"NSObject") ? sup == nil \
148 	: (sup != nil && sup != myClass), "%s can return superclass", prefix); \
149       PASS([theObj respondsToSelector: @selector(hash)], \
150 	"%s responds to hash", prefix); \
151       PASS([theObj isEqual: theObj], "%s isEqual: to self", prefix); \
152       PASS([theObj respondsToSelector: @selector(self)], \
153 	"%s respondsToSelector: ", prefix); \
154       [theObj isProxy]; \
155       r = [theObj retain]; \
156       PASS(theObj == r, "%s handles retain", prefix);  \
157       [theObj release]; \
158       [theObj retain]; \
159       [theObj autorelease]; \
160  \
161       count1 = [theObj retainCount]; \
162       [theObj retain]; \
163       [theObj release]; \
164       count2 = [theObj retainCount]; \
165       PASS((count1 == count2), "%s has working retainCount", prefix); \
166       PASS([[theObj description] isKindOfClass: [NSString class]], \
167 	"%s has NSString description", prefix); \
168       PASS([theObj performSelector: @selector(self)] == theObj, \
169 	"%s handles performSelector", prefix);     \
170     } \
171 }
172 static void test_NSObject(NSString *CN, NSArray *OJS) __attribute__ ((unused));
test_NSObject(NSString * CN,NSArray * OJS)173 static void test_NSObject(NSString *CN, NSArray *OJS)
174 {
175   TEST_NSOBJECT(CN, OJS)
176 }
177 
178 
179 /* Archives each object in the array, then unarchives it and checks that
180  * the two are equal (using the PASS_EQUAL macro).
181  */
182 #define TEST_NSCODING(OJS) \
183 { \
184   NSArray *objects = (OJS); \
185   int i; \
186   for (i = 0; i < [objects count]; i++) \
187     { \
188       char buf[100]; \
189       id obj = [objects objectAtIndex: i]; \
190       const char *prefix; \
191       NSMutableData *data; \
192       NSArchiver *archiver; \
193       id decoded; \
194 \
195       snprintf(buf, sizeof(buf), "test_NSCoding object %u", i); \
196       START_SET(buf) \
197 	PASS([[[obj class] description] length], \
198 	  "I can extract a class name for object"); \
199 \
200 	prefix = [[NSString stringWithFormat: @"Object %i of class '%s'", i, \
201 	  [NSStringFromClass([obj class]) UTF8String]] UTF8String]; \
202 	PASS([obj conformsToProtocol: @protocol(NSCoding)], \
203 	  "conforms to NSCoding protocol"); \
204 	data = (NSMutableData *)[NSMutableData data]; \
205 	archiver = [[NSArchiver alloc] initForWritingWithMutableData: data]; \
206 	PASS(archiver != nil, "I am able to set up an archiver"); \
207 	data = nil; \
208 	[archiver encodeRootObject: obj]; \
209 	data = [archiver archiverData]; \
210 	PASS(data && [data length] > 0, "%s can be encoded", prefix); \
211 	decoded = [NSUnarchiver unarchiveObjectWithData: data]; \
212 	PASS(decoded != nil, "can be decoded"); \
213         PASS_EQUAL(decoded, obj, "decoded object equals the original"); \
214       END_SET(buf) \
215     } \
216 }
217 static void test_NSCoding(NSArray *OJS) __attribute__ ((unused));
test_NSCoding(NSArray * OJS)218 static void test_NSCoding(NSArray *OJS)
219 {
220   TEST_NSCODING(OJS);
221 }
222 
223 
224 /* Archives each object in the argument array,
225  * then unarchives it and checks that the two are
226  * equal using the PASS_EQUAL macro.
227  */
228 #define TEST_KEYED_NSCODING(OJS) \
229 { \
230   NSArray       *objects = (OJS); \
231   int i; \
232   for (i = 0; i < [objects count]; i++) \
233     { \
234       char buf[100]; \
235       id obj = [objects objectAtIndex: i]; \
236       const char *prefix; \
237       NSData *data; \
238       id decoded; \
239 \
240       snprintf(buf, sizeof(buf), "test_keyed_NSCoding object %u", i); \
241       START_SET(buf) \
242 	PASS([[[obj class] description] length], \
243 	  "I can extract a class name for object"); \
244 \
245 	prefix = [[NSString stringWithFormat: @"Object %i of class '%s'", i, \
246 	  [NSStringFromClass([obj class]) UTF8String]] UTF8String]; \
247 	PASS([obj conformsToProtocol: @protocol(NSCoding)], \
248 	  "conforms to NSCoding protocol"); \
249 	data = [NSKeyedArchiver archivedDataWithRootObject: obj]; \
250 	PASS([data length] > 0, "%s can be encoded", prefix); \
251 	decoded = [NSKeyedUnarchiver unarchiveObjectWithData: data]; \
252 	PASS(decoded != nil, "can be decoded"); \
253         PASS_EQUAL(decoded, obj, "decoded object equals the original") \
254       END_SET(buf) \
255     } \
256 }
257 static void test_keyed_NSCoding(NSArray *OJS) __attribute__ ((unused));
test_keyed_NSCoding(NSArray * OJS)258 static void test_keyed_NSCoding(NSArray *OJS)
259 {
260   TEST_KEYED_NSCODING(OJS);
261 }
262 
263 
264 /* A macro for testing that objects conform to, and
265  * implement the NSCopying protocol.
266  * Macro arguments are:
267  * ICN  An NSString object containing the name of the
268  *      immutable class to be immutably copied.
269  * MCN  An NSString object containing the name of the
270  *      mutable class corresponding to the immutable
271  *      class.
272  * OJS  An NSArray object containg one or more objects
273  *      to be tested.
274  * MRT  A flag saying whether copies of an immutable
275  *      instance in the same zone must be implemented
276  *      by simply retaining the original.
277  * MCP  A flag saying whether copies of an immutable
278  *      instance must always be made as real copies
279  *      rather than by retaining the original.
280  */
281 #define TEST_NSCOPYING(ICN, MCN, OJS, MRT, MCP) \
282 { \
283   NSString *iClassName = (ICN); \
284   NSString *mClassName = (MCN); \
285   NSArray *objects = (OJS); \
286   BOOL mustRetain = (MRT); \
287   BOOL mustCopy = (MCP); \
288   Class iClass = NSClassFromString(iClassName); \
289   Class mClass = NSClassFromString(mClassName); \
290   int i; \
291   NSZone *testZone = NSCreateZone(1024, 1024, 1); \
292 \
293   PASS(iClass != Nil, "%s is a known class", [iClassName UTF8String]); \
294   PASS(mClass != Nil, "%s is a known class", [mClassName UTF8String]); \
295 \
296   for (i = 0; i < [objects count]; i++) \
297     { \
298       char buf[100]; \
299       BOOL immutable; \
300       NSString *theName; \
301       const char *prefix; \
302       id theCopy = nil; \
303       Class theClass = Nil; \
304       id theObj = [objects objectAtIndex: i]; \
305 \
306       snprintf(buf, sizeof(buf), "test_NSCopying object %u", i); \
307       START_SET(buf) \
308 	if (iClass != mClass && [theObj isKindOfClass: mClass]) \
309 	  { \
310 	    immutable = NO; \
311 	    theName = iClassName; \
312 	    theClass = iClass; \
313 	  } \
314 	else \
315 	  { \
316 	    immutable = YES; \
317 	    theName = mClassName; \
318 	    theClass = mClass; \
319 	  } \
320 \
321 	prefix = [[NSString stringWithFormat: @"Object %i of class '%s'", \
322 	  i, [theName UTF8String]] UTF8String]; \
323 	PASS([theObj conformsToProtocol: @protocol(NSCopying)], \
324 	  "conforms to NSCopying"); \
325 	theCopy = [theObj copy]; \
326 	PASS(theCopy != nil, "%s understands -copy", prefix); \
327 	PASS([theCopy isKindOfClass: iClass], \
328 	  "%s copy is of correct type", prefix); \
329 	PASS_EQUAL(theCopy, theObj, \
330 	  "%s original and copy are equal", prefix); \
331 	if (immutable) \
332 	  { \
333 	    if (YES == mustRetain) \
334 	      { \
335 		PASS(theCopy == theObj, \
336 		  "%s is retained by copy with same zone", prefix); \
337 	      } \
338 	    else if (YES == mustCopy) \
339 	      { \
340 		PASS(theCopy != theObj, \
341 		  "%s is not retained by copy with same zone", prefix); \
342 	      } \
343 	  } \
344 	if (theClass != iClass) \
345 	  { \
346 	    PASS(![theCopy isKindOfClass: theClass], \
347 	      "%s result of copy is not immutable", prefix); \
348 	  } \
349 \
350 	theCopy = [theObj copyWithZone: testZone]; \
351 	PASS(theCopy != nil, "%s understands -copyWithZone", prefix); \
352 	PASS([theCopy isKindOfClass: iClass], \
353 	  "%s zCopy has correct type", prefix); \
354 	PASS_EQUAL(theCopy, theObj, \
355 	  "%s copy and original are equal", prefix); \
356 	if (immutable) \
357 	  { \
358 	     if (YES == mustRetain) \
359 	       { \
360 		 PASS(theCopy == theObj, \
361 		   "%s is retained by copy with other zone", prefix); \
362 	       } \
363 	     else if (YES == mustCopy) \
364 	       { \
365 		 PASS(theCopy != theObj, \
366 		   "%s is not retained by copy with other zone", prefix); \
367 	       } \
368 	  } \
369        if (theClass != iClass) \
370 	 PASS(![theCopy isKindOfClass: theClass], \
371 	   "%s result of copyWithZone: is not immutable", prefix); \
372       END_SET(buf) \
373     } \
374 }
375 static void test_NSCopying(
376   NSString *ICN, NSString *MCN, NSArray *OJS, BOOL MRT, BOOL MCP)
377   __attribute__ ((unused));
test_NSCopying(NSString * ICN,NSString * MCN,NSArray * OJS,BOOL MRT,BOOL MCP)378 static void test_NSCopying(
379   NSString *ICN, NSString *MCN, NSArray *OJS, BOOL MRT, BOOL MCP)
380 {
381   TEST_NSCOPYING(ICN, MCN, OJS, MRT, MCP);
382 }
383 
384 
385 
386 /* A macro for testing that objects conform to, and
387  * implement the mutable copying protocol.
388  * Macro arguments are:
389  * ICN  An NSString object containing the name of the
390  *      immutable class to be mutably copied.
391  * MCN  An NSString object containing the name of the
392  *      mutable class to be produced as a result of
393  *      the copy.
394  * OJS  An NSArray object containg one or more objects
395  *      to be tested.
396  */
397 #define TEST_NSMUTABLECOPYING(ICN, MCN, OJS) \
398 { \
399   NSString *iClassName = (ICN); \
400   NSString *mClassName = (MCN); \
401   NSArray *objects = (OJS); \
402   int i; \
403   Class iClass = Nil; \
404   Class mClass = Nil; \
405   NSZone *testZone = NSCreateZone(1024, 1024, 1); \
406   iClass = NSClassFromString(iClassName); \
407   PASS(iClass != Nil, "%s is a known class", [iClassName UTF8String]); \
408 \
409   mClass = NSClassFromString(mClassName); \
410   PASS(mClass != Nil, "%s is a known class", [mClassName UTF8String]); \
411 \
412   for (i = 0; i < [objects count]; i++) \
413     { \
414       char buf[100]; \
415       id theObj = [objects objectAtIndex: i]; \
416       NSString *theName = nil; \
417       const char *prefix; \
418       BOOL immutable; \
419       id theCopy = nil; \
420 \
421       snprintf(buf, sizeof(buf), "test_NSMutableCopying object %u", i); \
422       START_SET(buf); \
423 	if (iClass == mClass && [theObj isKindOfClass: mClass]) \
424 	  immutable = NO; \
425 	else \
426 	  immutable = YES; \
427 \
428 	if (YES == immutable) \
429 	  { \
430 	    theName = iClassName; \
431 	  } \
432 	else \
433 	  { \
434 	    theName = mClassName; \
435 	  } \
436 \
437 	prefix = [[NSString stringWithFormat: \
438 	  @"Object %i of class '%s'", i, [theName UTF8String]] UTF8String]; \
439 	PASS([theObj conformsToProtocol: @protocol(NSMutableCopying)], \
440 	  "%s conforms to NSMutableCopying protocol", prefix); \
441 	theCopy = [theObj mutableCopy]; \
442 	PASS(theCopy != nil, "%s understands -mutableCopy", prefix); \
443 	PASS([theCopy isKindOfClass: mClass], \
444 	  "%s mutable copy is of correct type", prefix); \
445         PASS_EQUAL(theCopy, theObj, \
446           "%s copy object equals the original", prefix); \
447 	PASS(theCopy != theObj, \
448 	  "%s not retained by mutable copy in the same zone", \
449 	  [mClassName UTF8String]); \
450 \
451 	theCopy = [theObj mutableCopyWithZone: testZone]; \
452 	PASS(theCopy != nil, \
453 	  "%s understands mutableCopyWithZone", [mClassName UTF8String]); \
454 	PASS(theCopy != theObj, \
455           "%s not retained by mutable copy in other zone", \
456 	  [mClassName UTF8String]); \
457       END_SET(buf) \
458     } \
459 }
460 static void test_NSMutableCopying(
461   NSString *ICN, NSString *MCN, NSArray *OJS) __attribute__ ((unused));
test_NSMutableCopying(NSString * ICN,NSString * MCN,NSArray * OJS)462 static void test_NSMutableCopying(NSString *ICN, NSString *MCN, NSArray *OJS)
463 {
464   TEST_NSMUTABLECOPYING(ICN, MCN, OJS);
465 }
466 
467 
468 
469 
470 /* DEPRECATED ... please use the START_SET/END_SET and PASS macros instead.
471    START_TEST/END_TEST can be used if the code being tested could raise
472    and the exception should be considered a test failure.  The exception
473    is not reraised to allow subsequent tests to execute.  The START_TEST
474    macro takes an argument which will skip the test as Skipped if it
475    evaluates to 0, allowing runtime control of whether the code block
476    should be executed.
477  */
478 #define START_TEST(supported) if ((supported)) { NS_DURING
479 #define END_TEST(result, desc, args...) \
480   pass(result, desc, ## args); \
481   NS_HANDLER \
482     fprintf(stderr, "EXCEPTION: %s %s %s\n", \
483       [[localException name] UTF8String], \
484       [[localException reason] UTF8String], \
485       [[[localException userInfo] description] UTF8String]); \
486     pass (NO, desc, ## args); NS_ENDHANDLER } \
487   else { fprintf(stderr, "Failed test: " desc, ## args); \
488     fprintf(stderr, "\n"); }
489 
490 
491 /* Quick test to check that we have the class we expect.
492  */
493 #define TEST_FOR_CLASS(aClassName, aClass, TestDescription) \
494   PASS([aClass isKindOfClass: NSClassFromString(aClassName)], TestDescription)
495 
496 /* Quick test to check for a non-empty string in the case where we don't
497  * actually know what value we should be expecting.
498  */
499 #define TEST_STRING(code, description) \
500   { \
501     NSString *_testString = code; \
502     pass(_testString != nil \
503       && [_testString isKindOfClass: [NSString class]] \
504       && [_testString length], description); \
505   }
506 
507