diff options
Diffstat (limited to 'platform/node/test')
-rw-r--r-- | platform/node/test/compare.js | 27 | ||||
-rw-r--r-- | platform/node/test/expected/gzip/success.png | bin | 0 -> 42195 bytes | |||
-rw-r--r-- | platform/node/test/expected/gzip/unhandled.png | bin | 0 -> 5636 bytes | |||
-rw-r--r-- | platform/node/test/expected/map/image.png | bin | 0 -> 42195 bytes | |||
-rw-r--r-- | platform/node/test/fixtures/style.json | 31 | ||||
-rw-r--r-- | platform/node/test/fixtures/tiles/0-0-0.vector.pbf | bin | 0 -> 9660 bytes | |||
-rw-r--r-- | platform/node/test/js/consecutive.test.js | 82 | ||||
-rw-r--r-- | platform/node/test/js/gzip.test.js | 125 | ||||
-rw-r--r-- | platform/node/test/js/map.test.js | 280 | ||||
-rw-r--r-- | platform/node/test/render.test.js | 175 |
10 files changed, 720 insertions, 0 deletions
diff --git a/platform/node/test/compare.js b/platform/node/test/compare.js new file mode 100644 index 0000000000..3e5221de55 --- /dev/null +++ b/platform/node/test/compare.js @@ -0,0 +1,27 @@ +var spawn = require('child_process').spawn; + +module.exports = function compare(actual, expected, diff, t, callback) { + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; + + compare.stderr.on('data', function(data) { + error += data.toString(); + }); + + compare.on('error', function(err) { + t.error(err); + }); + + compare.on('exit', function(code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + callback(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + callback(match ? '' : error, difference); + } + }); + + compare.stdin.end(); +}; diff --git a/platform/node/test/expected/gzip/success.png b/platform/node/test/expected/gzip/success.png Binary files differnew file mode 100644 index 0000000000..de41e0fe2b --- /dev/null +++ b/platform/node/test/expected/gzip/success.png diff --git a/platform/node/test/expected/gzip/unhandled.png b/platform/node/test/expected/gzip/unhandled.png Binary files differnew file mode 100644 index 0000000000..ddb90d8b8f --- /dev/null +++ b/platform/node/test/expected/gzip/unhandled.png diff --git a/platform/node/test/expected/map/image.png b/platform/node/test/expected/map/image.png Binary files differnew file mode 100644 index 0000000000..de41e0fe2b --- /dev/null +++ b/platform/node/test/expected/map/image.png diff --git a/platform/node/test/fixtures/style.json b/platform/node/test/fixtures/style.json new file mode 100644 index 0000000000..222ac82bf8 --- /dev/null +++ b/platform/node/test/fixtures/style.json @@ -0,0 +1,31 @@ +{ + "version": 8, + "name": "Empty", + "sources": { + "mapbox": { + "type": "vector", + "maxzoom": 15, + "tiles": [ + "./fixtures/tiles/{z}-{x}-{y}.vector.pbf" + ] + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + } + ] +} diff --git a/platform/node/test/fixtures/tiles/0-0-0.vector.pbf b/platform/node/test/fixtures/tiles/0-0-0.vector.pbf Binary files differnew file mode 100644 index 0000000000..87628e06cf --- /dev/null +++ b/platform/node/test/fixtures/tiles/0-0-0.vector.pbf diff --git a/platform/node/test/js/consecutive.test.js b/platform/node/test/js/consecutive.test.js new file mode 100644 index 0000000000..928ca1d837 --- /dev/null +++ b/platform/node/test/js/consecutive.test.js @@ -0,0 +1,82 @@ +'use strict'; + +/* jshint node:true */ + +var test = require('tape'); +var mbgl = require('../../../..'); +var fs = require('fs'); +var path = require('path'); + +var suitePath = path.join(__dirname, '../../../../test/suite'); + +function renderTest(style, info, dir, key) { + return function (t) { + var completed = 0; + var remaining = 10; + var start = +new Date; + + var options = {}; + options.request = function(req) { + fs.readFile(path.join(suitePath, decodeURIComponent(req.url)), function(err, data) { + req.respond(err, { data: data }); + t.error(err); + }); + }; + options.cancel = function() {}; + options.ratio = 1.0; + + var map = new mbgl.Map(options); + map.load(style); + + function render() { + map.render(info[key], function(err, image) { + t.error(err); + + t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms'); + if (++completed === remaining) { + map.release(); + t.end(); + } else { + render(); + } + }); + } + + render(); + }; +} + +function rewriteLocalSchema(uri) { + return uri.replace(/^local:\/\//, ''); +} + +test('Consecutive', function(t) { + var dir = 'line-join'; + var k = 'round'; + + var style = require(path.join(suitePath, 'tests', dir, 'style.json')), + info = require(path.join(suitePath, 'tests', dir, 'info.json')); + + for (var k in style.sources) { + var source = style.sources[k]; + + if (source.tiles) { + source.tiles = source.tiles.map(rewriteLocalSchema); + } + + if (source.url) { + source.url = rewriteLocalSchema(source.url); + } + } + + if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite); + if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs); + + style = JSON.stringify(style); + + for (k in info) { + t.test(dir + ' ' + k, renderTest(style, info, dir, k)); + } + + t.end(); +}); diff --git a/platform/node/test/js/gzip.test.js b/platform/node/test/js/gzip.test.js new file mode 100644 index 0000000000..4545a18cf5 --- /dev/null +++ b/platform/node/test/js/gzip.test.js @@ -0,0 +1,125 @@ +'use strict'; + +/* jshint node: true */ + +var test = require('tape').test; +var mbgl = require('../../../..'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); +var http = require('http'); +var request = require('request'); +var st = require('st'); +var style = require('../fixtures/style.json'); +var PNG = require('pngjs').PNG; +var compare = require('../compare.js'); + +var server = http.createServer(st({ path: path.join(__dirname, '..') })); +server.listen(0); + +function filePath(name) { + return ['expected', 'actual', 'diff'].reduce(function(prev, key) { + var dir = path.join(__dirname, '..', key, 'gzip'); + mkdirp.sync(dir); + prev[key] = path.join(dir, name); + return prev; + }, {}); +} + +function setup(options, callback) { + callback(new mbgl.Map(options)); +} + +function getOptions(gzip, t) { + return { + request: function(req) { + var parts = req.url.split('.'); + var filetype = parts[parts.length - 1]; + + request({ + url: 'http://localhost:' + server.address().port + path.join('/', req.url), + encoding: null, + gzip: filetype === 'pbf' ? gzip : true, + headers: { + 'Accept-Encoding': 'gzip' + } + }, function (err, res, body) { + t.error(err); + var response = {}; + response.data = res.body; + req.respond(null, response); + }); + }, + ratio: 1.0 + }; +} + +test('gzip', function(t) { + t.test('success', function(t) { + mbgl.on('message', function(msg) { + if (msg.severity == 'ERROR') t.error(msg); + }); + + setup(getOptions(true, t), function(map) { + map.load(style); + map.render({}, function(err, data) { + mbgl.removeAllListeners('message'); + map.release(); + + t.error(err); + + var filename = filePath('success.png'); + + var png = new PNG({ + width: data.width, + height: data.height + }); + + png.data = data.pixels; + + if (process.env.UPDATE) { + png.pack() + .pipe(fs.createWriteStream(filename.expected)) + .on('finish', t.end); + } else { + png.pack() + .pipe(fs.createWriteStream(filename.actual)) + .on('finish', function() { + compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) { + t.error(err); + t.ok(diff <= 0.01, 'actual matches expected'); + t.end(); + }); + }); + } + }); + }); + }); + + t.test('unhandled', function(t) { + mbgl.once('message', function(msg) { + if (msg.severity == 'ERROR') { + t.ok(msg, 'emits error'); + t.equal(msg.class, 'Style'); + t.equal(msg.severity, 'ERROR'); + t.ok(msg.text.match(/pbf unknown field type exception/), 'error text matches'); + } + }); + + setup(getOptions(false, t), function(map) { + map.load(style); + map.render({}, function(err, data) { + map.release(); + + t.ok(err, 'returns error'); + t.ok(err.message.match(/Failed to parse/), 'error text matches'); + + t.end(); + }); + }); + }); + + t.test('teardown', function(t) { + server.close(t.end); + }); +}); diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js new file mode 100644 index 0000000000..09870abaaa --- /dev/null +++ b/platform/node/test/js/map.test.js @@ -0,0 +1,280 @@ +'use strict'; + +/* jshint node: true */ + +var test = require('tape'); +var mbgl = require('../../../..'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); +var style = require('../fixtures/style.json'); +var PNG = require('pngjs').PNG; +var compare = require('../compare.js'); + +function filePath(name) { + return ['expected', 'actual', 'diff'].reduce(function(prev, key) { + var dir = path.join(__dirname, '..', key, 'map'); + mkdirp.sync(dir); + prev[key] = path.join(dir, name); + return prev; + }, {}); +} + +test('Map', function(t) { + t.test('must be constructed with new', function(t) { + t.throws(function() { + mbgl.Map(); + }, /Use the new operator to create new Map objects/); + + t.end(); + }); + + t.test('must be constructed with options object', function(t) { + t.throws(function() { + new mbgl.Map(); + }, /Requires an options object as first argument/); + + t.throws(function() { + new mbgl.Map('options'); + }, /Requires an options object as first argument/); + + t.end(); + }); + + t.test('requires request and ratio options', function(t) { + var options = {}; + + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); + + options.request = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); + + options.request = function() {}; + options.cancel = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object 'cancel' property must be a function/); + + options.cancel = function() {}; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); + + options.ratio = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); + + options.ratio = 1.0; + t.doesNotThrow(function() { + new mbgl.Map(options); + }); + + t.end(); + }); + + t.test('.load', function(t) { + var options = { + request: function() {}, + ratio: 1 + }; + + t.test('requires a map style as first argument', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + + map.release(); + t.end(); + }); + + t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + mbgl.once('message', function(msg) { + t.equal(msg.severity, 'ERROR'); + t.equal(msg.class, 'ParseStyle'); + t.ok(msg.text.match(/Expect either an object or array at root/)); + + map.release(); + t.end(); + }); + + map.load('invalid'); + }); + + t.test('accepts an empty stylesheet string', function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load('{}'); + }); + + map.release(); + t.end(); + }); + + t.test('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load(style); + }); + + map.release(); + t.end(); + }); + + t.test('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load(JSON.stringify(style)); + }); + + map.release(); + t.end(); + }); + + t.test('does not immediately trigger any tile loads', function(t) { + var map = new mbgl.Map({ + request: function(req) { + t.fail('unexpected request ' + req.url); + }, + ratio: 1 + }); + + map.load(style); + + setTimeout(function() { + map.release(); + t.end(); + }, 100); + }); + }); + + t.test('.render', function(t) { + var options = { + request: function(req) { + fs.readFile(path.join(__dirname, '..', req.url), function(err, data) { + req.respond(err, { data: data }); + }); + }, + ratio: 1 + }; + + t.test('requires an object as the first parameter', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render(); + }, /First argument must be an options object/); + + t.throws(function() { + map.render('invalid'); + }, /First argument must be an options object/); + + map.release(); + t.end(); + }); + + t.test('requires a callback as the second parameter', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render({}); + }, /Second argument must be a callback function/); + + t.throws(function() { + map.render({}, 'invalid'); + }, /Second argument must be a callback function/); + + map.release(); + t.end(); + }); + + t.test('requires a style to be set', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render({}, function() {}); + }, /Style is not loaded/); + + map.release(); + t.end(); + }); + + t.test('returns an error', function(t) { + mbgl.on('message', function(msg) { + t.ok(msg, 'emits error'); + t.equal(msg.class, 'Style'); + t.equal(msg.severity, 'ERROR'); + t.ok(msg.text.match(/Failed to load/), 'error text matches'); + }); + + var map = new mbgl.Map(options); + map.load(style); + map.render({ zoom: 1 }, function(err, data) { + mbgl.removeAllListeners('message'); + map.release(); + + t.ok(err, 'returns error'); + t.ok(err.message.match(/Failed to load/), 'error text matches'); + + t.end(); + }); + }); + + t.test('double release', function(t) { + var map = new mbgl.Map(options); + map.release(); + + t.throws(function() { + map.release(); + }, /Map resources have already been released/); + + t.end(); + }); + + t.test('returns an image', function(t) { + var map = new mbgl.Map(options); + map.load(style); + map.render({}, function(err, data) { + t.error(err); + + map.release(); + + var filename = filePath('image.png'); + + var png = new PNG({ + width: data.width, + height: data.height + }); + + png.data = data.pixels; + + if (process.env.UPDATE) { + png.pack() + .pipe(fs.createWriteStream(filename.expected)) + .on('finish', t.end); + } else { + png.pack() + .pipe(fs.createWriteStream(filename.actual)) + .on('finish', function() { + compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) { + t.error(err); + t.ok(diff <= 0.01, 'actual matches expected'); + t.end(); + }); + }); + } + }); + }); + }); +}); diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js new file mode 100644 index 0000000000..10a7afcb52 --- /dev/null +++ b/platform/node/test/render.test.js @@ -0,0 +1,175 @@ +'use strict'; + +/* jshint node:true */ + +var test = require('tape'); +var mbgl = require('../../..'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); +var PNG = require('pngjs').PNG; +var compare = require('./compare.js'); +var suitePath = path.join(__dirname, '../../../test/suite'); + +function template(name) { + return fs.readFileSync(path.join(suitePath, 'templates', name + '.html.tmpl')).toString(); +} + +var results = ''; +var resultTemplate = template('result'); + +function format(tmpl, kwargs) { + return tmpl.replace(/\{\{|\}\}|\{([^}]+)\}/g, function(match, key) { + if (match === '{{') return '{'; + if (match === '}}') return '}'; + return kwargs[key]; + }); +} + +function renderTest(style, info, base, key) { + var dir = path.join(suitePath, 'tests', base, key); + mkdirp.sync(dir); + + return function(t) { + var watchdog = setTimeout(function() { + t.fail('timed out after 20 seconds'); + }, 20000); + + t.once('end', function() { + clearTimeout(watchdog); + + if (map) { + map.release(); + map = null; + } + }); + + var options = {}; + options.request = function(req) { + var url = decodeURIComponent(req.url); + fs.readFile(path.join(suitePath, url), function(err, data) { + req.respond(err, { data: data }); + }); + }; + options.ratio = info[key].pixelRatio || 1; + + var map = new mbgl.Map(options); + map.load(style); + + map.render(info[key], function(err, data) { + if (err) { + t.error(err); + return t.end(); + } + + var expected = path.join(dir, 'expected.png'); + var actual = path.join(dir, 'actual.png'); + var diff = path.join(dir, 'diff.png'); + + var png = new PNG({ + width: data.width, + height: data.height + }); + + png.data = data.pixels; + + if (process.env.UPDATE) { + png.pack() + .pipe(fs.createWriteStream(expected)) + .on('finish', t.end); + } else { + png.pack() + .pipe(fs.createWriteStream(actual)) + .on('finish', function() { + compare(actual, expected, diff, t, function(err, diff) { + t.error(err); + + var allowed = 0.001; + + if ('diff' in info[key]) { + if (typeof info[key].diff === 'number') { + allowed = info[key].diff; + } else if ('native' in info[key].diff) { + allowed = info[key].diff.native; + } + } + + results += format(resultTemplate, { + name: base, + key: key, + color: diff <= allowed ? 'green' : 'red', + error: err ? '<p>' + err + '</p>' : '', + difference: diff, + zoom: info.zoom || 0, + center: info.center || [0, 0], + bearing: info.bearing || 0, + width: info.width || 512, + height: info.height || 512 + }); + + if (!info[key].ignored || !('native' in info[key].ignored)) { + t.ok(diff <= allowed, 'expected ' + diff + ' to be less than ' + allowed); + } + + t.end(); + }); + }); + } + }); + }; +} + +function rewriteLocalSchema(url) { + var regex = /^local:\/\//; + if (url instanceof Array) { + return url.map(function(str) { + return str.replace(regex, ''); + }); + } else if (typeof url === "string") { + return url.replace(regex, ''); + } +} + +var tests; + +if (process.argv[1] === __filename) { + tests = process.argv.slice(2); +} + +test('Render', function(t) { + fs.readdirSync(path.join(suitePath, 'tests')).forEach(function(dir) { + if (dir === 'index.html' || dir[0] === '.') return; + if (tests && tests.length && tests.indexOf(dir) < 0) return; + + var style = require(path.join(suitePath, 'tests', dir, 'style.json')), + info = require(path.join(suitePath, 'tests', dir, 'info.json')); + + for (var k in style.sources) { + var source = style.sources[k]; + + if (source.tiles) { + source.tiles = source.tiles.map(rewriteLocalSchema); + } + + if (source.url) { + source.url = rewriteLocalSchema(source.url); + } + } + + if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite); + if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs); + + style = JSON.stringify(style); + + for (k in info) { + (info[k].native === false ? t.skip : t.test)(dir + ' ' + k, renderTest(style, info, dir, k)); + } + }); + + t.test('results', function(t) { + var p = path.join(suitePath, 'tests', 'index.html'); + fs.writeFileSync(p, format(template('results'), {results: results})); + console.warn('Results at: ' + p); + t.end(); + }); +}); |