Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Commit

Permalink
#35 More work on unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
swisspol committed Apr 12, 2014
1 parent c062d9d commit a28ac82
Show file tree
Hide file tree
Showing 722 changed files with 8,890 additions and 96 deletions.
2 changes: 2 additions & 0 deletions CGDWebServer/GCDWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ NSDate* GCDWebServerParseISO8601(NSString* string);
#if !TARGET_OS_IPHONE
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; // Creates files in the current directory containing the raw data for all requests and responses (directory most NOT contain prior recordings)
- (BOOL)runWithPort:(NSUInteger)port; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only)
#endif
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
- (NSInteger)runTestsInDirectory:(NSString*)path withPort:(NSUInteger)port; // Returns number of failed tests or -1 if server failed to start
#endif
@end
Expand Down
207 changes: 116 additions & 91 deletions CGDWebServer/GCDWebServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -622,45 +622,56 @@ - (BOOL)runWithPort:(NSUInteger)port {
return success;
}

static CFHTTPMessageRef _CreateHTTPMessageFromFileDump(NSString* path, BOOL isRequest) {
NSData* data = [NSData dataWithContentsOfFile:path];
if (data) {
CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest);
if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) {
return message;
}
CFRelease(message);
#endif

#ifdef __GCDWEBSERVER_ENABLE_TESTING__

static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) {
CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest);
if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) {
return message;
}
CFRelease(message);
return NULL;
}

static CFHTTPMessageRef _CreateHTTPMessageFromHTTPRequestResponse(CFHTTPMessageRef request) {
static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) {
CFHTTPMessageRef response = NULL;
CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
if (CFReadStreamOpen(stream)) {
CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
CFDataSetLength(data, 256 * 1024);
CFIndex length = 0;
while (1) {
CFIndex result = CFReadStreamRead(stream, CFDataGetMutableBytePtr(data) + length, CFDataGetLength(data) - length);
if (result <= 0) {
break;
}
length += result;
if (length >= CFDataGetLength(data)) {
CFDataSetLength(data, 2 * CFDataGetLength(data));
}
}
if (CFReadStreamGetStatus(stream) == kCFStreamStatusAtEnd) {
response = (CFHTTPMessageRef)CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
if (response) {
CFDataSetLength(data, length);
CFHTTPMessageSetBody(response, data);
int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (httpSocket > 0) {
struct sockaddr_in addr4;
bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(port);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(8080);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) {
if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) {
NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)];
NSUInteger length = 0;
while (1) {
ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length);
if (result < 0) {
length = NSNotFound;
break;
} else if (result == 0) {
break;
}
length += result;
if (length >= outData.length) {
outData.length = 2 * outData.length;
}
}
if (length != NSNotFound) {
outData.length = length;
response = _CreateHTTPMessageFromData(outData, NO);
} else {
DNOT_REACHED();
}
ARC_RELEASE(outData);
}
}
CFRelease(data);
CFReadStreamClose(stream);
CFRelease(stream);
close(httpSocket);
}
return response;
}
Expand All @@ -675,6 +686,7 @@ static void _LogResult(NSString* format, ...) {
}

- (NSInteger)runTestsInDirectory:(NSString*)path withPort:(NSUInteger)port {
NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs
NSInteger result = -1;
if ([self startWithPort:port bonjourName:nil]) {

Expand All @@ -687,72 +699,85 @@ - (NSInteger)runTestsInDirectory:(NSString*)path withPort:(NSUInteger)port {
@autoreleasepool {
NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject];
BOOL success = NO;
CFHTTPMessageRef request = _CreateHTTPMessageFromFileDump([path stringByAppendingPathComponent:requestFile], YES);
if (request) {
_LogResult(@"[%i] %@ %@", (int)[index integerValue], ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(request)), [ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(request)) path]);
NSString* prefix = [index stringByAppendingString:@"-"];
for (NSString* responseFile in files) {
if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) {
CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromFileDump([path stringByAppendingPathComponent:responseFile], NO);
if (expectedResponse) {
CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromHTTPRequestResponse(request);
if (actualResponse) {
success = YES;

CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse);
CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse);
if (actualStatusCode != expectedStatusCode) {
_LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode);
success = NO;
}

NSDictionary* expectedHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(expectedResponse));
NSDictionary* actualHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(actualResponse));
for (NSString* expectedHeader in expectedHeaders) {
if ([expectedHeader isEqualToString:@"Date"]) {
continue;
}
NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader];
NSString* actualValue = [actualHeaders objectForKey:expectedHeader];
if (![actualValue isEqualToString:expectedValue]) {
_LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue);
success = NO;
}
}
for (NSString* actualHeader in actualHeaders) {
if (![expectedHeaders objectForKey:actualHeader]) {
_LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]);
success = NO;
}
}

NSData* expectedBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(expectedResponse));
NSData* actualBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(actualResponse));
if (![actualBody isEqualToData:expectedBody]) {
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
success = NO;

if (_IsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/opendiff"];
[task setArguments:@[expectedPath, actualPath]];
[task launch];
ARC_RELEASE(task);
NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]];
if (requestData) {
CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES);
if (request) {
NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(request));
NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(request));
_LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path);
NSString* prefix = [index stringByAppendingString:@"-"];
for (NSString* responseFile in files) {
if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) {
NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]];
if (responseData) {
CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
if (expectedResponse) {
CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, port);
if (actualResponse) {
success = YES;

CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse);
CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse);
if (actualStatusCode != expectedStatusCode) {
_LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode);
success = NO;
}

NSDictionary* expectedHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(expectedResponse));
NSDictionary* actualHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(actualResponse));
for (NSString* expectedHeader in expectedHeaders) {
if ([ignoredHeaders containsObject:expectedHeader]) {
continue;
}
NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader];
NSString* actualValue = [actualHeaders objectForKey:expectedHeader];
if (![actualValue isEqualToString:expectedValue]) {
_LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue);
success = NO;
}
}
for (NSString* actualHeader in actualHeaders) {
if (![expectedHeaders objectForKey:actualHeader]) {
_LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]);
success = NO;
}
}

NSData* expectedBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(expectedResponse));
NSData* actualBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(actualResponse));
if (![actualBody isEqualToData:expectedBody]) {
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
success = NO;
#ifndef NDEBUG
if (_IsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/opendiff"];
[task setArguments:@[expectedPath, actualPath]];
[task launch];
ARC_RELEASE(task);
}
}
#endif
}

CFRelease(actualResponse);
}
CFRelease(expectedResponse);
}
CFRelease(actualResponse);
} else {
DNOT_REACHED();
}
CFRelease(expectedResponse);
break;
}
break;
}
CFRelease(request);
}
CFRelease(request);
} else {
DNOT_REACHED();
}
_LogResult(@"");
if (!success) {
Expand Down
16 changes: 16 additions & 0 deletions CGDWebServer/GCDWebServerFileRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ - (BOOL)close:(NSError**)error {
*error = _MakePosixError(errno);
return NO;
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}
NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"];
if (modifiedDateHeader) {
NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}
#endif
return YES;
}

Expand Down
15 changes: 15 additions & 0 deletions GCDWebDAVServer/GCDWebDAVServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ - (GCDWebServerResponse*)performMKCOL:(GCDWebServerDataRequest*)request {
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* creationDateHeader = [request.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed setting creation date for directory \"%@\"", relativePath];
}
}
#endif

if ([_delegate respondsToSelector:@selector(davServer:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
Expand Down Expand Up @@ -519,6 +528,12 @@ - (GCDWebServerResponse*)performLOCK:(GCDWebServerDataRequest*)request {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
}

#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* lockTokenHeader = [request.headers objectForKey:@"X-GCDWebServer-LockToken"];
if (lockTokenHeader) {
token = lockTokenHeader;
}
#endif
if (!token) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef string = CFUUIDCreateString(kCFAllocatorDefault, uuid);
Expand Down
2 changes: 2 additions & 0 deletions GCDWebServer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
ONLY_ACTIVE_ARCH = YES;
WARNING_CFLAGS = (
Expand Down Expand Up @@ -488,6 +489,7 @@
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
WARNING_CFLAGS = "-Wall";
Expand Down
18 changes: 13 additions & 5 deletions Run-Tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

TARGET="GCDWebServer (Mac)"
CONFIGURATION="Release"
PAYLOAD_ZIP="Tests/Payload.zip"
PAYLOAD_DIR="/tmp/payload"

MRC_BUILD_DIR="/tmp/GCDWebServer-MRC"
MRC_PRODUCT="$MRC_BUILD_DIR/$CONFIGURATION/GCDWebServer"
ARC_BUILD_DIR="/tmp/GCDWebServer-ARC"
ARC_PRODUCT="$ARC_BUILD_DIR/$CONFIGURATION/GCDWebServer"

PAYLOAD_ZIP="Tests/Payload.zip"
PAYLOAD_DIR="/tmp/GCDWebServer"

function runTests {
rm -rf "$PAYLOAD_DIR"
ditto -x -k "$PAYLOAD_ZIP" "$PAYLOAD_DIR"
logLevel=2 $1 -root "$PAYLOAD_DIR" -tests "$2"
find "$PAYLOAD_DIR" -type d -exec SetFile -d "1/1/2014" -m "1/1/2014" '{}' \; # ZIP archives do not preserve directories dates
logLevel=2 $1 -mode "$2" -root "$PAYLOAD_DIR/Payload" -tests "$3"
}

# Build in manual memory management mode
Expand All @@ -25,8 +27,14 @@ rm -rf "ARC_BUILD_DIR"
xcodebuild -target "$TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" > /dev/null

# Run tests
runTests $MRC_PRODUCT "WebServer"
runTests $ARC_PRODUCT "WebServer"
runTests $MRC_PRODUCT "webServer" "Tests/WebServer"
runTests $ARC_PRODUCT "webServer" "Tests/WebServer"
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Transmit"
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Transmit"
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Finder"
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Finder"

# Done
echo "\nAll tests completed successfully!"
6 changes: 6 additions & 0 deletions Tests/WebDAV-Cyberduck/001-200.response
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:42 GMT

6 changes: 6 additions & 0 deletions Tests/WebDAV-Cyberduck/001-HEAD.request
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HEAD / HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate

Loading

0 comments on commit a28ac82

Please sign in to comment.