From 73d1db3bd2b89da858b1a08e57e4caabf310e5fb Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 4 Aug 2014 01:30:24 +0200 Subject: [PATCH] CDB-3686 Adds support for per mil tolerance when comparing images as in Mac OS X some results from ImageMagick are a bit odd --- test/acceptance/multilayer.js | 11 +++--- test/acceptance/server.js | 21 ++++++----- test/support/assert.js | 67 ++++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/test/acceptance/multilayer.js b/test/acceptance/multilayer.js index 31f91d7b..89e4c25d 100644 --- a/test/acceptance/multilayer.js +++ b/test/acceptance/multilayer.js @@ -14,6 +14,9 @@ var helper = require(__dirname + '/../support/test_helper'); var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures'; +var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20; +var IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL = 25; + var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft'); var ServerOptions = require(__dirname + '/../../lib/cartodb/server_options'); serverOptions = ServerOptions(); @@ -117,7 +120,7 @@ suite('multilayer', function() { + expectedQuery + '$windshaft$)::regclass[])'); - assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', 2, + assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL, function(err, similarity) { next(err); }); @@ -400,7 +403,7 @@ suite('multilayer', function() { + expectedQuery + '$windshaft$)::regclass[])'); - assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', 2, + assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { next(err); }); @@ -438,7 +441,7 @@ suite('multilayer', function() { + expectedQuery + '$windshaft$)::regclass[])'); - assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', 2, + assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { next(err); }); @@ -1067,7 +1070,7 @@ suite('multilayer', function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "image/png"); - assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', 2, + assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { next(err); }); diff --git a/test/acceptance/server.js b/test/acceptance/server.js index 5c688fcc..0a430dd1 100644 --- a/test/acceptance/server.js +++ b/test/acceptance/server.js @@ -11,6 +11,9 @@ var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js'); var helper = require(__dirname + '/../support/test_helper'); +var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20, + IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL = 0; + var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft'); var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')(); var server = new CartodbWindshaft(serverOptions); @@ -842,7 +845,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', 2, + assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { if (err) throw err; done(); @@ -873,7 +876,7 @@ suite('server', function() { assert.equal(ct, 'image/png'); assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', - 2, this); + IMAGE_EQUALS_TOLERANCE_PER_MIL, this); }, function checkImage(err, similarity) { if (err) throw err; @@ -910,7 +913,7 @@ suite('server', function() { assert.equal(ct, 'image/png'); assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', - 2, this); + IMAGE_EQUALS_TOLERANCE_PER_MIL, this); }, function checkImage(err, similarity) { if (err) throw err; @@ -934,7 +937,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', 2, + assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { if (err) throw err; done(); @@ -971,7 +974,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', 2, + assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { next(err); }); @@ -1011,7 +1014,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/blank.png', 0, + assert.imageEqualsFile(res.body, './test/fixtures/blank.png', IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL, function(err, similarity) { if (err) next(err); else next(); @@ -1031,7 +1034,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/blank.png', 0, + assert.imageEqualsFile(res.body, './test/fixtures/blank.png', IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL, function(err, similarity) { if (err) next(err); else next(); @@ -1068,7 +1071,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', 2, + assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { // NOTE: we expect them to be EQUAL here if (err) { next(err); return; } @@ -1105,7 +1108,7 @@ suite('server', function() { assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var ct = res.headers['content-type']; assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', 2, + assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err, similarity) { // NOTE: we expect them to be different here if (err) next(); diff --git a/test/support/assert.js b/test/support/assert.js index 26d3221f..2c7e174f 100644 --- a/test/support/assert.js +++ b/test/support/assert.js @@ -1,10 +1,11 @@ // Cribbed from the ever prolific Konstantin Kaefer // https://github.com/mapbox/tilelive-mapnik/blob/master/test/support/assert.js -var fs = require('fs'); -var http = require('http'); -var path = require('path'); -var exec = require('child_process').exec; +var exec = require('child_process').exec, + fs = require('fs'), + http = require('http'), + path = require('path'), + util = require('util'); var assert = module.exports = exports = require('assert'); @@ -66,35 +67,51 @@ assert.utfgridEqualsFile = function(buffer, file_b, tolerance, callback) { callback(err); }; -// -// @param tol tolerated color distance as a percent over max channel value -// by default this is zero. For meaningful values, see -// http://www.imagemagick.org/script/command-line-options.php#metric -// -assert.imageEqualsFile = function(buffer, file_b, tol, callback) { +/** + * Takes an image data as an input and an image path and compare them using ImageMagick fuzz algorithm, if case the + * similarity is not within the tolerance limit it will callback with an error. + * + * @param buffer The image data to compare from + * @param {string} referenceImageRelativeFilePath The relative file to compare against + * @param {number} tolerance tolerated mean color distance, as a per mil (‰) + * @param {function} callback Will call to home with null in case there is no error, otherwise with the error itself + * @see FUZZY in http://www.imagemagick.org/script/command-line-options.php#metric + */ +assert.imageEqualsFile = function(buffer, referenceImageRelativeFilePath, tolerance, callback) { if (!callback) callback = function(err) { if (err) throw err; }; - file_b = path.resolve(file_b); - var file_a = '/tmp/windshaft-test-image-test.png'; // + (Math.random() * 1e16); // TODO: make predictable - var err = fs.writeFileSync(file_a, buffer, 'binary'); + var referenceImageFilePath = path.resolve(referenceImageRelativeFilePath), + testImageFilePath = '/tmp/windshaft-test-image-' + (Math.random() * 1e16); // TODO: make predictable + var err = fs.writeFileSync(testImageFilePath, buffer, 'binary'); if (err) throw err; - var fuzz = tol + '%'; - exec('compare -fuzz ' + fuzz + ' -metric AE "' + file_a + '" "' + - file_b + '" /dev/null', function(err, stdout, stderr) { + var imageMagickCmd = util.format( + 'compare -metric fuzz "%s" "%s" /dev/null', + testImageFilePath, referenceImageFilePath + ); + + exec(imageMagickCmd, function(err, stdout, stderr) { if (err) { - fs.unlinkSync(file_a); + fs.unlinkSync(testImageFilePath); callback(err); } else { stderr = stderr.trim(); - var similarity = parseFloat(stderr); - if ( similarity > 0 ) { - var err = new Error('Images not equal(' + similarity + '): ' + - file_a + ' ' + file_b); - err.similarity = similarity; - callback(err); + var metrics = stderr.match(/([0-9]*) \((.*)\)/); + if ( ! metrics ) { + callback(new Error("No match for " + stderr)); + return; + } + var similarity = parseFloat(metrics[2]), + tolerancePerMil = (tolerance / 1000); + if (similarity > tolerancePerMil) { + err = new Error(util.format( + 'Images %s and %s are not equal (got %d similarity, expected %d)', + testImageFilePath, referenceImageFilePath, similarity, tolerancePerMil) + ); + err.similarity = similarity; + callback(err); } else { - fs.unlinkSync(file_a); - callback(null); + fs.unlinkSync(testImageFilePath); + callback(null); } } });