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