+2005-08-17 Maciej Stachowiak <mjs@apple.com>
+
+ Changes by Ben Lamonica and Eric Seidel, reviewed mostly by Eric and
+ somewhat by me, and also tweaked by me a little bit.
+
+ - better support for pixel-dumping
+ - use checksums of the images so the tests are fast
+ - change output format to make the tests run faster
+ - don't dump pixel results for tests that dump as text
+
+ * DumpKCanvasTree/DumpKCanvasTree.m:
+ (main):
+ (dumpRenderTree):
+ (md5HashStringForBitmap):
+ (dumpPixelTests):
+ (constrainSizeToMaximum):
+ (getBitmapImageRepForSVGDocument):
+ * DumpKCanvasTree/DumpKCanvasTree.xcodeproj/project.pbxproj:
+ * DumpRenderTree/DumpRenderTree.m:
+ (main):
+ (dump):
+ (dumpRenderTree):
+ (md5HashStringForBitmap):
+ * DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj:
+ * Scripts/run-webkit-tests:
+
+2005-08-17 Maciej Stachowiak <mjs@apple.com>
+
+ Reviewed by Darin.
+
+ - hacked DumpRenderTree to make the scrollbars appear and disappear properly.
+
+ * DumpRenderTree/DumpRenderTree.m:
+ (main):
+
2005-08-14 Oliver Hunt <ojh16@student.canterbury.ac.nz>
Reviewed and landed by Darin.
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#import <getopt.h>
#import <WebCore+SVG/DrawDocumentPrivate.h>
+#import <WebCore+SVG/DrawView.h>
+
+#define COMMON_DIGEST_FOR_OPENSSL
+#import <CommonCrypto/CommonDigest.h>
+
+#define maxHeightOption 1
+#define maxWidthOption 2
static void dumpRenderTree(const char *filename);
+void dumpPixelTests(DrawDocument *document, NSString *svgPath);
+static NSBitmapImageRep *getBitmapImageRepForSVGDocument(DrawDocument *document);
+NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap);
+
+/* global variables */
+int pixelTests = 0; // run the pixel comparison tests?
+NSNumber *maxWidth = nil;
+NSNumber *maxHeight = nil;
+DrawView *view = nil;
int main(int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ int ch = 0;
+
+ // define the commandline options that this program accepts
+ static struct option longopts[] = {
+ { "pixel-tests", no_argument, &pixelTests, 1 },
+ { "max-height", required_argument, NULL, maxHeightOption },
+ { "max-width", required_argument, NULL, maxWidthOption },
+ { NULL, 0, NULL, 0 }
+ };
+
+ // parse the options that the program receives
+ while ((ch = getopt_long(argc, (char * const *) argv, "", longopts, NULL)) != -1) {
+ switch (ch) {
+ case maxHeightOption:
+ maxHeight = [NSNumber numberWithInt:[[NSString stringWithUTF8String:optarg] intValue]];
+ break;
+ case maxWidthOption:
+ maxWidth = [NSNumber numberWithInt:[[NSString stringWithUTF8String:optarg] intValue]];
+ break;
+ case '?': // unknown or ambiguous option
+ case ':': // missing argument
+ exit(1);
+ break;
+ }
+ }
+
+ // remove the options that were parsed by getopt_long
+ argc -= optind;
+ argv += optind;
+
+ // if we are running the pixel comparison tests, instantiate our DrawView
+ if (pixelTests)
+ view = [[DrawView alloc] initWithFrame:NSZeroRect];
- if (argc == 2 && strcmp(argv[1], "-") == 0) {
+ if (argc == 1 && strcmp(argv[0], "-") == 0) {
char filenameBuffer[2048];
+
while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
char *newLineCharacter = strchr(filenameBuffer, '\n');
if (newLineCharacter) {
}
}
+ [view release];
[pool release];
return 0;
}
puts("error");
else
fputs([result UTF8String], stdout);
+
+ // generate the image and perform pixel tests
+ if (pixelTests) {
+ fputs("#EOF\n", stdout);
+ dumpPixelTests(document, svgPath);
+ }
[document release];
[pool release];
}
+
+NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap)
+{
+ MD5_CTX md5Context;
+ unsigned char hash[16];
+
+ MD5_Init(&md5Context);
+ MD5_Update(&md5Context, [bitmap bitmapData], [bitmap bytesPerPlane]);
+ MD5_Final(hash, &md5Context);
+
+ char hex[33] = "";
+ for (int i = 0; i < 16; i++) {
+ snprintf(hex, 33, "%s%02x", hex, hash[i]);
+ }
+
+ return [NSString stringWithUTF8String:hex];
+}
+
+void dumpPixelTests(DrawDocument *document, NSString *svgPath)
+{
+ // Creates an image from the document object
+ NSBitmapImageRep *actualBitmap = getBitmapImageRepForSVGDocument(document);
+
+ // lookup the expected hash value for that image
+ NSString *baseTestPath = [svgPath stringByDeletingPathExtension];
+ NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
+ NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
+
+ // compute the hash for the rendered image, print both.
+ NSString *actualHash = md5HashStringForBitmap(actualBitmap);
+ fprintf(stdout,"ActualHash: %s\n", [actualHash UTF8String]);
+ fprintf(stdout,"BaselineHash: %s\n", [baselineHash UTF8String]);
+
+ // if the hashes don't match, then perform more pixel tests
+ if ([baselineHash isEqualToString:actualHash] == NO) {
+ // send the PNG compressed data through STDOUT
+ NSData *svgPNGData = [actualBitmap representationUsingType:NSPNGFileType properties:nil];
+ fprintf(stdout, "Content-length: %d\n", [svgPNGData length]);
+ fwrite([svgPNGData bytes], [svgPNGData length], 1, stdout);
+ }
+}
+
+static NSSize constrainSizeToMaximum(NSSize boundsSize)
+{
+ // Limit the maximum size and default the size to 500x500 if not specified
+ if (maxWidth && boundsSize.width > [maxWidth intValue]) {
+ // resize the height so that the aspect ratio remains the same
+ boundsSize.height = (boundsSize.height / boundsSize.width) * [maxWidth intValue];
+ boundsSize.width = [maxWidth intValue];
+ }
+ else if (boundsSize.width <= 0)
+ boundsSize.width = 500;
+
+ if (maxHeight && boundsSize.height > [maxHeight intValue]) {
+ // resize the width so that the aspect ratio reamins the same
+ boundsSize.width = (boundsSize.width / boundsSize.height) * [maxHeight intValue];
+ boundsSize.height = [maxHeight intValue];
+ }
+ else if (boundsSize.height <= 0)
+ boundsSize.height = 500;
+ return boundsSize;
+}
+
+static NSBitmapImageRep *getBitmapImageRepForSVGDocument(DrawDocument *document)
+{
+ [view setDocument:document];
+ [view sizeToFitViewBox];
+ NSSize boundsSize = constrainSizeToMaximum([view bounds].size);
+ [view setFrameSize:boundsSize];
+
+ // tell that view to render
+ NSBitmapImageRep *imageRep = [view bitmapImageRepForCachingDisplayInRect:[view bounds]];
+ [view cacheDisplayInRect:[view bounds] toBitmapImageRep:imageRep];
+
+ return imageRep;
+}
A810DEDA08A40F3100333D98 /* Development */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
};
name = Development;
};
A810DEDB08A40F3100333D98 /* Deployment */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
};
name = Deployment;
};
A810DEDC08A40F3100333D98 /* Default */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
};
name = Default;
};
#import <WebKit/WebPreferences.h>
#import <WebKit/WebView.h>
+#define COMMON_DIGEST_FOR_OPENSSL
+#import <CommonCrypto/CommonDigest.h>
#import <getopt.h>
@interface WaitUntilDoneDelegate : NSObject
@end
static void dumpRenderTree(const char *filename);
+NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap);
+
static volatile BOOL done;
static WebFrame *frame;
static int dumpPixels = NO;
static int dumpTree = YES;
static BOOL printSeparators;
+static NSString *currentTest = nil;
int main(int argc, const char *argv[])
{
struct option options[] = {
{"width", required_argument, NULL, 'w'},
{"height", required_argument, NULL, 'h'},
- {"bitmap", no_argument, &dumpPixels, YES},
- {"nobitmap", no_argument, &dumpPixels, NO},
+ {"pixel-tests", no_argument, &dumpPixels, YES},
{"tree", no_argument, &dumpTree, YES},
{"notree", no_argument, &dumpTree, NO},
{NULL, 0, NULL, 0}
[webView setUIDelegate:delegate];
frame = [webView mainFrame];
+ // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
+ // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
+ NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
+ [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
+
if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
char filenameBuffer[2048];
printSeparators = YES;
while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
char *newLineCharacter = strchr(filenameBuffer, '\n');
- if (newLineCharacter) {
+ if (newLineCharacter)
*newLineCharacter = '\0';
- }
+
+ if (strlen(filenameBuffer) == 0)
+ continue;
+
dumpRenderTree(filenameBuffer);
fflush(stdout);
}
} else {
- int i;
printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
- for (i = optind; i != argc; ++i) {
+ for (int i = optind; i != argc; ++i) {
dumpRenderTree(argv[i]);
}
}
if (dumpTree) {
if (dumpAsText) {
DOMDocument *document = [frame DOMDocument];
- if ([document isKindOfClass:[DOMHTMLDocument class]]) {
+ if ([document isKindOfClass:[DOMHTMLDocument class]])
result = [[[(DOMHTMLDocument *)document body] innerText] stringByAppendingString:@"\n"];
- }
- } else {
+ } else
result = [frame renderTreeAsExternalRepresentation];
- }
- if (!result) {
+
+ if (!result)
puts("error");
- } else {
+ else
fputs([result UTF8String], stdout);
- }
+
if (printSeparators)
puts("#EOF");
}
if (dumpPixels) {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- WebView *view = [frame webView];
- NSBitmapImageRep *imageRep = [view bitmapImageRepForCachingDisplayInRect:[view frame]];
- NSData *imageData;
- [view cacheDisplayInRect:[view frame] toBitmapImageRep:imageRep];
- imageData = [imageRep representationUsingType:NSPNGFileType properties:nil];
- if (printSeparators)
- printf("%d\n", [imageData length]);
- fwrite([imageData bytes], 1, [imageData length], stdout);
- [pool release];
+ if (!dumpAsText) {
+ NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
+ NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
+ NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
+
+ // grab a bitmap from the view
+ WebView *view = [frame webView];
+ NSBitmapImageRep *imageRep = [view bitmapImageRepForCachingDisplayInRect:[view frame]];
+ [view cacheDisplayInRect:[view frame] toBitmapImageRep:imageRep];
+
+ // has the actual hash to compare to the expected image's hash
+ NSString *actualHash = md5HashStringForBitmap(imageRep);
+ printf("\nActualHash: %s\n", [actualHash UTF8String]);
+ printf("BaselineHash: %s\n", [baselineHash UTF8String]);
+
+ // if the hashes don't match, send image back to stdout for diff comparision
+ if ([baselineHash isEqualToString:actualHash] == NO) {
+ NSData *imageData = [imageRep representationUsingType:NSPNGFileType properties:nil];
+ printf("Content-length: %d\n", [imageData length]);
+ fwrite([imageData bytes], 1, [imageData length], stdout);
+ }
+ }
+
+ printf("#EOF\n");
}
done = YES;
static void dumpRenderTree(const char *filename)
{
+
CFStringRef filenameString = CFStringCreateWithCString(NULL, filename, kCFStringEncodingUTF8);
if (filenameString == NULL) {
fprintf(stderr, "can't parse filename as UTF-8\n");
return;
}
+
CFURLRef URL = CFURLCreateWithFileSystemPath(NULL, filenameString, kCFURLPOSIXPathStyle, FALSE);
if (URL == NULL) {
fprintf(stderr, "can't turn %s into a CFURL\n", filename);
waitToDump = NO;
dumpAsText = NO;
dumpTitleChanges = NO;
+ currentTest = (NSString *) filenameString;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[frame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
}
[[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
}
+
+/* Hashes a bitmap and returns a text string for comparison and saving to a file */
+NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap)
+{
+ MD5_CTX md5Context;
+ unsigned char hash[16];
+
+ MD5_Init(&md5Context);
+ MD5_Update(&md5Context, [bitmap bitmapData], [bitmap bytesPerPlane]);
+ MD5_Final(hash, &md5Context);
+
+ char hex[33] = "";
+ for (int i = 0; i < 16; i++) {
+ snprintf(hex, 33, "%s%02x", hex, hash[i]);
+ }
+
+ return [NSString stringWithUTF8String:hex];
+}
+
objectVersion = 42;
objects = {
+/* Begin PBXAggregateTarget section */
+ A84F608D08B1370600E9745F /* All */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = A84F609208B1371400E9745F /* Build configuration list for PBXAggregateTarget "All" */;
+ buildPhases = (
+ );
+ buildSettings = {
+ OPTIMIZATION_CFLAGS = "";
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = All;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = (
+ "-Wmost",
+ "-Wno-four-char-constants",
+ "-Wno-unknown-pragmas",
+ );
+ };
+ dependencies = (
+ A84F609108B1370E00E9745F /* PBXTargetDependency */,
+ A84F608F08B1370E00E9745F /* PBXTargetDependency */,
+ );
+ name = All;
+ productName = All;
+ };
+/* End PBXAggregateTarget section */
+
/* Begin PBXBuildFile section */
9340994C08540CAE007F3BC8 /* DumpRenderTreePrefix.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A70AAB03705E1F00C91783 /* DumpRenderTreePrefix.h */; };
9340994E08540CAE007F3BC8 /* DumpRenderTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* DumpRenderTree.m */; settings = {ATTRIBUTES = (); }; };
- 9340995008540CAE007F3BC8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
9340995108540CAE007F3BC8 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9335435F03D75502008635CE /* WebKit.framework */; };
- 9340995208540CAE007F3BC8 /* WebCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9335436503D7553D008635CE /* WebCore.framework */; };
- 9340995308540CAE007F3BC8 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9335436903D75557008635CE /* JavaScriptCore.framework */; };
+ A817090008B163EF00CCB9FB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A84F608908B136DA00E9745F /* Cocoa.framework */; };
+ A817090208B1643800CCB9FB /* WebCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A817090108B1643800CCB9FB /* WebCore.framework */; };
+ A817090408B164D300CCB9FB /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A817090308B164D300CCB9FB /* JavaScriptCore.framework */; };
+ A84F608A08B136DA00E9745F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A84F608908B136DA00E9745F /* Cocoa.framework */; };
+ B5A752A008AF5CD400138E45 /* ImageDiff.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A7525808AF4A3600138E45 /* ImageDiff.m */; };
+ B5A752A208AF5D1F00138E45 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A752A108AF5D1F00138E45 /* QuartzCore.framework */; };
/* End PBXBuildFile section */
/* Begin PBXBuildStyle section */
};
/* End PBXBuildStyle section */
+/* Begin PBXContainerItemProxy section */
+ A84F608E08B1370E00E9745F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = B5A7525A08AF4A4A00138E45;
+ remoteInfo = ImageDiff;
+ };
+ A84F609008B1370E00E9745F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9340994A08540CAE007F3BC8;
+ remoteInfo = DumpRenderTree;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXFileReference section */
08FB7796FE84155DC02AAC07 /* DumpRenderTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DumpRenderTree.m; sourceTree = "<group>"; };
- 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32A70AAB03705E1F00C91783 /* DumpRenderTreePrefix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DumpRenderTreePrefix.h; sourceTree = "<group>"; };
9335435F03D75502008635CE /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WebKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 9335436503D7553D008635CE /* WebCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WebCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 9335436903D75557008635CE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = JavaScriptCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9340995408540CAF007F3BC8 /* DumpRenderTree */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = DumpRenderTree; sourceTree = BUILT_PRODUCTS_DIR; };
+ A817090108B1643800CCB9FB /* WebCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WebCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ A817090308B164D300CCB9FB /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = JavaScriptCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ A84F608908B136DA00E9745F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
+ B5A7525808AF4A3600138E45 /* ImageDiff.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ImageDiff.m; sourceTree = "<group>"; };
+ B5A7526708AF4A4A00138E45 /* ImageDiff */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ImageDiff; sourceTree = BUILT_PRODUCTS_DIR; };
+ B5A752A108AF5D1F00138E45 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9340995008540CAE007F3BC8 /* Foundation.framework in Frameworks */,
9340995108540CAE007F3BC8 /* WebKit.framework in Frameworks */,
- 9340995208540CAE007F3BC8 /* WebCore.framework in Frameworks */,
- 9340995308540CAE007F3BC8 /* JavaScriptCore.framework in Frameworks */,
+ A84F608A08B136DA00E9745F /* Cocoa.framework in Frameworks */,
+ A817090208B1643800CCB9FB /* WebCore.framework in Frameworks */,
+ A817090408B164D300CCB9FB /* JavaScriptCore.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ B5A7525F08AF4A4A00138E45 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ B5A752A208AF5D1F00138E45 /* QuartzCore.framework in Frameworks */,
+ A817090008B163EF00CCB9FB /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
08FB7794FE84155DC02AAC07 /* DumpRenderTree */ = {
isa = PBXGroup;
children = (
+ B5A7525808AF4A3600138E45 /* ImageDiff.m */,
08FB7796FE84155DC02AAC07 /* DumpRenderTree.m */,
32A70AAB03705E1F00C91783 /* DumpRenderTreePrefix.h */,
- 08FB779EFE84155DC02AAC07 /* Foundation.framework */,
- 9335436903D75557008635CE /* JavaScriptCore.framework */,
- 9335436503D7553D008635CE /* WebCore.framework */,
9335435F03D75502008635CE /* WebKit.framework */,
+ A817090308B164D300CCB9FB /* JavaScriptCore.framework */,
+ A817090108B1643800CCB9FB /* WebCore.framework */,
+ B5A752A108AF5D1F00138E45 /* QuartzCore.framework */,
+ A84F608908B136DA00E9745F /* Cocoa.framework */,
9340995508540CAF007F3BC8 /* Products */,
);
name = DumpRenderTree;
isa = PBXGroup;
children = (
9340995408540CAF007F3BC8 /* DumpRenderTree */,
+ B5A7526708AF4A4A00138E45 /* ImageDiff */,
);
name = Products;
sourceTree = "<group>";
);
runOnlyForDeploymentPostprocessing = 0;
};
+ B5A7525B08AF4A4A00138E45 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
productReference = 9340995408540CAF007F3BC8 /* DumpRenderTree */;
productType = "com.apple.product-type.tool";
};
+ B5A7525A08AF4A4A00138E45 /* ImageDiff */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = B5A7526408AF4A4A00138E45 /* Build configuration list for PBXNativeTarget "ImageDiff" */;
+ buildPhases = (
+ B5A7525B08AF4A4A00138E45 /* Headers */,
+ B5A7525D08AF4A4A00138E45 /* Sources */,
+ B5A7525F08AF4A4A00138E45 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ buildSettings = {
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = DumpRenderTreePrefix.h;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ PRODUCT_NAME = DumpRenderTree;
+ WARNING_CFLAGS = (
+ "-Wall",
+ "-W",
+ "-Wno-unused-parameter",
+ );
+ };
+ dependencies = (
+ );
+ name = ImageDiff;
+ productInstallPath = "$(HOME)/bin";
+ productName = DumpRenderTree;
+ productReference = B5A7526708AF4A4A00138E45 /* ImageDiff */;
+ productType = "com.apple.product-type.tool";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
productRefGroup = 9340995508540CAF007F3BC8 /* Products */;
projectDirPath = "";
targets = (
+ A84F608D08B1370600E9745F /* All */,
9340994A08540CAE007F3BC8 /* DumpRenderTree */,
+ B5A7525A08AF4A4A00138E45 /* ImageDiff */,
);
};
/* End PBXProject section */
);
runOnlyForDeploymentPostprocessing = 0;
};
+ B5A7525D08AF4A4A00138E45 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ B5A752A008AF5CD400138E45 /* ImageDiff.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ A84F608F08B1370E00E9745F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = B5A7525A08AF4A4A00138E45 /* ImageDiff */;
+ targetProxy = A84F608E08B1370E00E9745F /* PBXContainerItemProxy */;
+ };
+ A84F609108B1370E00E9745F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9340994A08540CAE007F3BC8 /* DumpRenderTree */;
+ targetProxy = A84F609008B1370E00E9745F /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin XCBuildConfiguration section */
149C29C008902C6D008A9EFC /* Development */ = {
isa = XCBuildConfiguration;
149C29C408902C6D008A9EFC /* Development */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
WARNING_CFLAGS = (
"-Wall",
149C29C508902C6D008A9EFC /* Deployment */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
WARNING_CFLAGS = (
"-Wall",
};
name = Deployment;
};
+ A84F609308B1371400E9745F /* Development */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+ OPTIMIZATION_CFLAGS = "-O0";
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = All;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = (
+ "-Wmost",
+ "-Wno-four-char-constants",
+ "-Wno-unknown-pragmas",
+ );
+ };
+ name = Development;
+ };
+ A84F609408B1371400E9745F /* Deployment */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = YES;
+ GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
+ OPTIMIZATION_CFLAGS = "-O0";
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = All;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = (
+ "-Wmost",
+ "-Wno-four-char-constants",
+ "-Wno-unknown-pragmas",
+ );
+ };
+ name = Deployment;
+ };
+ B5A7526508AF4A4A00138E45 /* Development */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_PREFIX_HEADER = "";
+ PRODUCT_NAME = ImageDiff;
+ };
+ name = Development;
+ };
+ B5A7526608AF4A4A00138E45 /* Deployment */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_PREFIX_HEADER = "";
+ PRODUCT_NAME = ImageDiff;
+ };
+ name = Deployment;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Deployment;
};
+ A84F609208B1371400E9745F /* Build configuration list for PBXAggregateTarget "All" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ A84F609308B1371400E9745F /* Development */,
+ A84F609408B1371400E9745F /* Deployment */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
+ B5A7526408AF4A4A00138E45 /* Build configuration list for PBXNativeTarget "ImageDiff" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ B5A7526508AF4A4A00138E45 /* Development */,
+ B5A7526608AF4A4A00138E45 /* Deployment */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Deployment;
+ };
/* End XCConfigurationList section */
};
rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
use strict;
use IPC::Open2;
+use Getopt::Long;
use FindBin;
use Cwd;
use lib $FindBin::Bin;
chdirWebKit();
# Argument handling
-my $testSVGs = 0;
-if (($#ARGV > -1) && ($ARGV[0] eq "--svg")) {
- $testSVGs = 1;
- shift @ARGV;
-}
+my $testSVGs = '';
+my $pixelTests = '';
+my $maxWidth = '';
+my $maxHeight = '';
+my $verbose = '';
+my $quiet = '';
+
+GetOptions('svg' => \$testSVGs,
+ 'pixel-tests' => \$pixelTests,
+ 'max-width' => \$maxWidth,
+ 'max-height' => \$maxHeight,
+ 'verbose' => \$verbose,
+ 'quiet' => \$quiet);
-my $toolbuildscript = "build-dumprendertree";
-if ($testSVGs) {
- $toolbuildscript = "build-dumpkcanvastree";
-}
+my $toolname = "DumpRenderTree";
-my $result = system "WebKitTools/Scripts/$toolbuildscript", @ARGV;
+my $result = system "WebKitTools/Scripts/build-dumprendertree", @ARGV;
exit $result if $result;
-
-my $toolname = "DumpRenderTree";
if ($testSVGs) {
+ my $result = system "WebKitTools/Scripts/build-dumpkcanvastree", @ARGV;
+ exit $result if $result;
$toolname = "DumpKCanvasTree";
}
my $tool = "$productDir/$toolname";
+my $imageDiffTool = "$productDir/ImageDiff";
die "can't find executable $toolname tool (looked in $productDir)\n" if !-x $tool;
+die "can't find executable $imageDiffTool tool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
if ($testSVGs) {
checkSVGFrameworks();
my %counts;
my %tests;
+my %imagesPresent;
my $count;
+my @toolArgs;
+
+if ($pixelTests) {
+ push @toolArgs, "--pixel-tests";
+ if ($maxWidth) {
+ push @toolArgs, ("--width", $maxWidth);
+ }
+
+ if ($maxWidth) {
+ push @toolArgs, ("--height", $maxWidth);
+ }
+}
-open2(\*IN, \*OUT, $tool, "-") or die;
+push @toolArgs, "-";
$| = 1;
+open2(\*IN, \*OUT, $tool, @toolArgs) or die;
+
+if ($pixelTests) {
+ open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, "") or die "unable to open $imageDiffTool\n";
+}
+
+my $column = 0;
+my $lastDirectory = "";
+
for my $test (@tests) {
next if $test eq 'results.html';
my $base = $test;
$base =~ s/\.(html|xml|xhtml|svg)$//;
- print "running $base test";
-
+ if ($verbose) {
+ print "running $base test";
+ } elsif (!$quiet) {
+ my $dir = $base;
+ $dir =~ s|/[^/]+$||;
+ if ($dir ne $lastDirectory) {
+ print "\n" unless $column == 0;
+ print $dir . " ";
+ $lastDirectory = $dir;
+ $column = 0;
+ }
+
+ print ".";
+ $column++;
+ }
+
my $result;
print OUT "$testDirectory/$test\n";
my $actual = "";
while (<IN>) {
- last if /#EOF$/;
+ last if /#EOF/;
$actual .= $_;
}
-
+
my $expected;
if (open EXPECTED, "<", "$testDirectory/$base-expected.txt") {
$expected = "";
}
close EXPECTED;
}
+
+ my $textDumpMatches = $expected && ($actual eq $expected);
+ my $actualHash = "";
+ my $expectedHash = "";
+ my $hashMatches = "";
+ my $actualPNG = "";
+ my $actualPNGSize = 0;
+ my $expectedPNG = "";
+ my $expectedPNGSize = 0;
+ my $diffPNG = "";
+ my $diffPercentage = "";
+ my $diffResult = "passed";
+
+ if ($pixelTests) {
+ while (<IN>) {
+ last if /#EOF/;
+ if (/ActualHash: ([a-f0-9]{32})/) {
+ $actualHash = $1;
+ } elsif (/BaselineHash: ([a-f0-9]{32})/) {
+ $expectedHash = $1;
+ } elsif (/Content-length: (\d+)\s*/) {
+ $actualPNGSize = $1;
+ read(IN, $actualPNG, $actualPNGSize);
+ }
+ }
+
+ if ($hashMatches = ($expectedHash eq $actualHash)) {
+ $diffResult = "passed";
+ }
+
+ if (!$hashMatches && -f "$testDirectory/$base-expected.png" && $textDumpMatches) {
+ $expectedPNGSize = getFilesize("$testDirectory/$base-expected.png");
+ open EXPECTEDPNG, "$testDirectory/$base-expected.png";
+ read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
+
+ print DIFFOUT "Content-length: $actualPNGSize\n";
+ print DIFFOUT $actualPNG;
+
+ print DIFFOUT "Content-length: $expectedPNGSize\n";
+ print DIFFOUT $expectedPNG;
+
+ while (<DIFFIN>) {
+ last if /^error/ || /^diff:/;
+ if (/Content-length: (\d+)\s*/) {
+ read(DIFFIN, $diffPNG, $1);
+ }
+ }
+
+ if (/^diff: (.+)% (passed|failed)/) {
+ $diffPercentage = $1;
+ $diffResult = $2;
+ }
+ }
+ }
+
+ if ($pixelTests) {
+ if ($actualPNGSize != 0 && ! -f "$testDirectory/$base-expected.png") {
+ open EXPECTED, ">", "$testDirectory/$base-expected.png" or die "could not create $testDirectory/$base-expected.png\n";
+ print EXPECTED $actualPNG;
+ close EXPECTED;
+ }
+
+ # update the expected hash if the image diff said that there was no difference
+ if ($actualHash ne "" && ! -f "$testDirectory/$base-expected.checksum") {
+ open EXPECTED, ">", "$testDirectory/$base-expected.checksum" or die "could not create $testDirectory/$base-expected.checksum\n";
+ print EXPECTED $actualHash;
+ close EXPECTED;
+ }
+ }
+
if (!defined $expected) {
- print " -> new test\n";
+ if ($verbose) {
+ print " -> new test\n";
+ }
$result = "new";
open EXPECTED, ">", "$testDirectory/$base-expected.txt" or die "could not create $testDirectory/$base-expected.txt\n";
print EXPECTED $actual;
close EXPECTED;
+
unlink "$testResultsDirectory/$base-actual.txt";
unlink "$testResultsDirectory/$base-diffs.txt";
- } elsif ($actual eq $expected) {
- print " -> succeeded\n";
+ } elsif ($textDumpMatches && (!$pixelTests || ($pixelTests && $diffResult eq "passed"))) {
+ if ($verbose) {
+ print " -> succeeded\n";
+ }
$result = "match";
unlink "$testResultsDirectory/$base-actual.txt";
unlink "$testResultsDirectory/$base-diffs.txt";
- } else {
- print " -> failed\n";
+ } elsif (!$textDumpMatches || ($pixelTests && $diffResult ne "passed")) {
+ print "\n" unless $column == 0;
+ print "$test -> failed\n";
+ $column = 0;
+
$result = "mismatch";
+
my $dir = "$testResultsDirectory/$base";
$dir =~ s|/[^/]+$||;
system "mkdir", "-p", $dir;
+
open ACTUAL, ">", "$testResultsDirectory/$base-actual.txt" or die;
print ACTUAL $actual;
close ACTUAL;
+
system "diff -u \"$testDirectory/$base-expected.txt\" \"$testResultsDirectory/$base-actual.txt\" > \"$testResultsDirectory/$base-diffs.txt\"";
+
+ if ($pixelTests && $diffPNG && $diffPNG ne "") {
+ $imagesPresent{$base} = 1;
+
+ open ACTUAL, ">", "$testResultsDirectory/$base-actual.png" or die;
+ print ACTUAL $actualPNG;
+ close ACTUAL;
+
+ open DIFF, ">", "$testResultsDirectory/$base-diffs.png" or die;
+ print DIFF $diffPNG;
+ close DIFF;
+
+ open DIFFHTML, ">$testResultsDirectory/$base-diffs.html" or die;
+ print DIFFHTML "<html>\n";
+ print DIFFHTML "<head>\n";
+ print DIFFHTML "<title>$base Image Compare</title>\n";
+ print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
+ print DIFFHTML "var actualImageVisible = true;\n";
+ print DIFFHTML "function animateImage() {\n";
+ print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
+ print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
+ print DIFFHTML " if (actualImageVisible) {\n";
+ print DIFFHTML " image.src=\"$testDirectory/$base-expected.png\";\n";
+ print DIFFHTML " imageText.innerHTML = \"Expected Image\";\n";
+ print DIFFHTML " actualImageVisible = false;\n";
+ print DIFFHTML " } else {\n";
+ print DIFFHTML " image.src=\"$testResultsDirectory/$base-actual.png\";\n";
+ print DIFFHTML " imageText.innerHTML = \"Actual Image\";\n";
+ print DIFFHTML " actualImageVisible = true;\n";
+ print DIFFHTML " }\n";
+ print DIFFHTML " setTimeout('animateImage()',2000);\n";
+ print DIFFHTML "}\n";
+ print DIFFHTML "</script>\n";
+ print DIFFHTML "</head>\n";
+ print DIFFHTML "<body onLoad=\"animateImage();\">\n";
+ print DIFFHTML "<table>\n";
+ if ($diffPercentage) {
+ print DIFFHTML "<tr>\n";
+ print DIFFHTML "<td>Difference between images: <a href=\"$testResultsDirectory/$base-diffs.png\">$diffPercentage%</a></td>\n";
+ print DIFFHTML "</tr>\n";
+ }
+ print DIFFHTML "<tr>\n";
+ print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
+ print DIFFHTML "</tr>\n";
+ print DIFFHTML "<tr>\n";
+ print DIFFHTML "<td><img src=\"$testResultsDirectory/$base-actual.png\" id=\"animatedImage\"></td>\n";
+ print DIFFHTML "</tr>\n";
+ print DIFFHTML "</table>\n";
+ print DIFFHTML "</body>\n";
+ print DIFFHTML "</html>\n";
+ }
+ } else {
+ $result = "fail";
+ print "\n" unless $column == 0;
+ print "$test -> crashed\n";
+ $column = 0;
+
+ close IN;
+ close OUT;
+ open2(\*IN, \*OUT, $tool, @toolArgs) or die;
}
$count += 1;
print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">expected</a></td>\n";
print HTML "<td><a href=\"$base-actual.txt\">actual</a></td>\n";
print HTML "<td><a href=\"$base-diffs.txt\">diffs</a></td>\n";
+ if ($pixelTests) {
+ print HTML "<td><a href=\"$testDirectory/$base-expected.png\">expected image</a></td>\n";
+ if ($imagesPresent{$base}) {
+ print HTML "<td><a href=\"$base-diffs.html\">image diffs</a></td>\n";
+ }
+ }
print HTML "</tr>\n";
}
print HTML "</table>\n";
print HTML "<tr>\n";
print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">results</a></td>\n";
+ if ($pixelTests) {
+ print HTML "<td><a href=\"$testDirectory/$base-expected.png\">image</a></td>\n";
+ }
print HTML "</tr>\n";
}
print HTML "</table>\n";
system "open", $testResults;
}
+
+sub getFilesize
+{
+ my $filename = shift;
+ my @fileStats;
+
+ @fileStats = stat($filename);
+
+ $fileStats[7];
+}