1/*
2copyright 2004 Alexander Malmberg <alexander@malmberg.org>
3*/
4
5#include "Testing.h"
6
7#include <stdio.h>
8
9#include <Foundation/NSArchiver.h>
10#include <Foundation/NSArray.h>
11#include <Foundation/NSAttributedString.h>
12#include <Foundation/NSAutoreleasePool.h>
13#include <Foundation/NSByteOrder.h>
14#include <Foundation/NSCharacterSet.h>
15#include <Foundation/NSData.h>
16#include <Foundation/NSDate.h>
17#include <Foundation/NSDateFormatter.h>
18#include <Foundation/NSDictionary.h>
19#include <Foundation/NSException.h>
20#include <Foundation/NSFormatter.h>
21#include <Foundation/NSGeometry.h>
22#include <Foundation/NSNotification.h>
23#include <Foundation/NSNull.h>
24#include <Foundation/NSObject.h>
25#include <Foundation/NSSet.h>
26#include <Foundation/NSString.h>
27#include <Foundation/NSURL.h>
28#include <Foundation/NSValue.h>
29
30@interface NSObject (Equality)
31- (BOOL) testEquality: (id)other;
32@end
33
34@interface NSObject (DecodingTests)
35+ (NSObject*) createTestInstance;
36+ (BOOL) verifyTestInstance: (NSObject *)instance
37	ofVersion: (int)version;
38- (BOOL) testEquality;
39@end
40
41@implementation NSObject (DecodingTests)
42+ (NSObject *) createTestInstance
43{
44  if (self == [NSException class])
45    {
46      return [[NSException alloc] initWithName: @"Test"
47					reason: @"Testing"
48				      userInfo: nil];
49    }
50  else
51    {
52      return [[self alloc] init];
53    }
54}
55
56+ (BOOL) verifyTestInstance: (NSObject *)instance
57		  ofVersion: (int)version
58{
59  id    o;
60
61  if (instance == nil) return NO;
62  o = [self createTestInstance];
63  if (YES == [o respondsToSelector: @selector(testEquality:)])
64    return [o testEquality: instance];
65  if (NO == [instance testEquality]) return YES;
66  return [o isEqual: instance];
67}
68
69- (BOOL) testEquality
70{
71  static IMP impNSObject = 0;
72  /* By default, assume that every class that overrides NSObject's
73     isEqual: implementation can compare archived instances.
74     subclasses for which this doesn't hold can simply override this
75     method in a category and return a constant YES/NO.  */
76
77  if (!impNSObject)
78    {
79      impNSObject = [NSObject instanceMethodForSelector:@selector(isEqual:)];
80    }
81  return [self methodForSelector:@selector(isEqual:)] == impNSObject
82    ? NO : YES;
83}
84@end
85
86@implementation NSCharacterSet (DecodingTests)
87+ (NSObject *) createTestInstance
88{
89  return [[self characterSetWithCharactersInString: @"qwertzuiop"] retain];
90}
91@end
92
93@implementation NSValue (DecodingTests)
94+ (NSObject *) createTestInstance
95{
96  return [[self valueWithSize: NSMakeSize(1.1, 1.2)] retain];
97}
98- (BOOL) testEquality: (id)other
99{
100  if (strcmp([self objCType], @encode(NSSize)) == 0)
101    {
102      NSSize        mSize = [self sizeValue];
103      NSSize        oSize = [other sizeValue];
104
105      if (EQ(mSize.height, oSize.height) && EQ(mSize.width, oSize.width))
106        return YES;
107      return NO;
108    }
109  return [self isEqual: other];
110}
111@end
112
113@implementation NSNumber (DecodingTests)
114+ (NSObject *) createTestInstance
115{
116  return [[self numberWithInt: 1] retain];
117}
118@end
119
120@implementation NSData (DecodingTests)
121+ (NSObject *) createTestInstance
122{
123  NSString	*source = @"We need constant data";
124  NSData	*data = [source dataUsingEncoding: NSUnicodeStringEncoding];
125
126  if (NSHostByteOrder() == NS_BigEndian)
127    {
128      NSMutableData	*m = [data mutableCopy];
129      uint8_t		*p = (uint8_t*)[m mutableBytes];
130      uint8_t		*e = p + [m length];
131
132      while (p < e)
133	{
134	  uint8_t	tmp = p[0];
135
136	  p[0] = p[1];
137	  p[1] = tmp;
138	  p += 2;
139	}
140      return m;
141    }
142  else
143    {
144      return [data retain];
145    }
146}
147@end
148
149@implementation NSDate (DecodingTests)
150+ (NSObject *) createTestInstance
151{
152  return [[NSDate dateWithTimeIntervalSince1970: 4294967296.0] retain];
153}
154@end
155
156@implementation NSURL (DecodingTests)
157+ (NSObject *) createTestInstance
158{
159  return [[self alloc] initWithString: @"http://www.gnustep.org/"];
160}
161@end
162
163
164/*
165If set, we write out new .data files for the current versions for classes
166that don't have them.
167*/
168BOOL update;
169
170void test(Class class)
171{
172  NS_DURING
173    {
174      /*
175      In order to catch decoders that don't consume all the data that they
176      should, we decode/encode an array that includes the object and a string.
177      We verify that the string was correctly decoded, although any errors will
178      likely be caught by crashes in the unarchiver.
179      */
180      NSString	*sentinel = @"quux!";
181
182      int	v = [class version];
183      NSObject	*instance;
184      NSArray	*decodedInstance;
185      NSData	*d;
186      NSString	*filename;
187
188      instance = [class createTestInstance];
189
190      d = [NSArchiver archivedDataWithRootObject:
191	[NSArray arrayWithObjects: instance, sentinel, nil]];
192      decodedInstance = [NSUnarchiver unarchiveObjectWithData: d];
193
194      NSCAssert([sentinel isEqual: [decodedInstance objectAtIndex: 1]],
195	NSInternalInconsistencyException);
196
197      PASS([class verifyTestInstance: [decodedInstance objectAtIndex: 0]
198	ofVersion: v], "decoding current version of class %s", POBJECT(class));
199
200      for (; v >= 0; v--)
201	{
202          int   w;
203
204          for (w = 0; w < 2; w++)
205            {
206              const char        *width;
207
208              if (0 == w)
209                {
210                  if (YES == update && 4 != sizeof(void*))
211                    {
212                      continue; // Can't write a 32bit update.
213                    }
214                  width = "32bit";
215                }
216              else
217                {
218                  if (YES == update && 8 != sizeof(void*))
219                    {
220                      continue; // Can't write a 64bit update.
221                    }
222                  width = "64bit";
223                }
224
225              filename = [NSString stringWithFormat: @"%@.%i.%s",
226                class, v, width];
227              d = [NSData dataWithContentsOfFile: filename];
228              if (!d)
229                {
230                  if (v == [class version])
231                    {
232                      if (!update)
233                        PASS(0, "%s has %s reference data for current version",
234                          POBJECT(class), width)
235                      else
236                        [NSArchiver archiveRootObject:
237                          [NSArray arrayWithObjects: instance, sentinel, nil]
238                                             toFile: filename];
239                    }
240                  continue;
241                }
242
243              decodedInstance = [NSUnarchiver unarchiveObjectWithData: d];
244              NSCAssert([sentinel isEqual: [decodedInstance objectAtIndex: 1]],
245                NSInternalInconsistencyException);
246              PASS([class verifyTestInstance: [decodedInstance objectAtIndex: 0]
247                ofVersion: v], "decoding %s version %i of class %s",
248                width, v, POBJECT(class));
249            }
250	}
251    }
252  NS_HANDLER
253    {
254      PASS(0, "decoding class %s: %s",
255	POBJECT(class), POBJECT(localException));
256    }
257  NS_ENDHANDLER
258}
259
260int main(int argc, char **argv)
261{
262  NSAutoreleasePool   *arp = [NSAutoreleasePool new];
263
264  update = argc == 2 && !strcmp(argv[1], "--update");
265
266#define T(c) test([c class]);
267  T(NSArray)
268  T(NSAttributedString)
269  T(NSCharacterSet)
270  T(NSData)
271  T(NSMutableData)
272  T(NSDate)
273  T(NSDateFormatter)
274  T(NSDictionary)
275  T(NSException)
276  T(NSNotification)
277  T(NSNull)
278  T(NSObject)
279  T(NSSet)
280  T(NSString)
281  T(NSURL)
282  T(NSValue)
283  T(NSNumber)
284
285  [arp release]; arp = nil;
286
287  return 0;
288}
289
290