From 67ee32fdea1ead3c153feb0746a97341c5053e7a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 6 Nov 2018 15:58:45 -0700 Subject: [PATCH 1/7] New rendering tests --- package.json | 9 +- rendering/.eslintrc | 11 ++ rendering/.gitignore | 1 + rendering/cases/single-layer/expected.png | Bin 0 -> 26049 bytes rendering/cases/single-layer/index.html | 22 +++ rendering/cases/single-layer/main.js | 19 +++ rendering/test.js | 193 ++++++++++++++++++++++ rendering/webpack.config.js | 36 ++++ 8 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 rendering/.eslintrc create mode 100644 rendering/.gitignore create mode 100644 rendering/cases/single-layer/expected.png create mode 100644 rendering/cases/single-layer/index.html create mode 100644 rendering/cases/single-layer/main.js create mode 100755 rendering/test.js create mode 100644 rendering/webpack.config.js diff --git a/package.json b/package.json index 0af8897237..4a46d031a1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "scripts": { "lint": "eslint tasks test src/ol examples config", "pretest": "npm run lint", - "test": "npm run karma -- --single-run --log-level error", + "test-rendering": "node rendering/test.js", + "test": "npm run karma -- --single-run --log-level error && npm run test-rendering", "karma": "karma start test/karma.config.js", "serve-examples": "webpack-dev-server --config examples/webpack/config.js --mode development --watch", "build-examples": "webpack --config examples/webpack/config.js --mode production", @@ -72,7 +73,9 @@ "mocha": "5.2.0", "mustache": "^3.0.0", "pixelmatch": "^4.0.2", + "pngjs": "^3.3.3", "proj4": "2.5.0", + "puppeteer": "^1.10.0", "rollup": "0.66.6", "sinon": "^6.0.0", "typescript": "^3.1.0-dev.20180905", @@ -81,7 +84,9 @@ "walk": "^2.3.9", "webpack": "4.25.1", "webpack-cli": "^3.0.8", - "webpack-dev-server": "^3.1.4" + "webpack-dev-middleware": "^3.4.0", + "webpack-dev-server": "^3.1.10", + "yargs": "^12.0.2" }, "eslintConfig": { "extends": "openlayers", diff --git a/rendering/.eslintrc b/rendering/.eslintrc new file mode 100644 index 0000000000..07e46d0c56 --- /dev/null +++ b/rendering/.eslintrc @@ -0,0 +1,11 @@ +{ + "env": { + "node": true + }, + "parserOptions": { + "ecmaVersion": 2017 + }, + "globals": { + "render": false + } +} diff --git a/rendering/.gitignore b/rendering/.gitignore new file mode 100644 index 0000000000..8b295d4969 --- /dev/null +++ b/rendering/.gitignore @@ -0,0 +1 @@ +actual.png \ No newline at end of file diff --git a/rendering/cases/single-layer/expected.png b/rendering/cases/single-layer/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c57b9f9067e1f3f0773ddfe3f052f91f9c3e649b GIT binary patch literal 26049 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 +Im~y%m;rQugnc}_^ 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 literal 0 HcmV?d00001 diff --git a/rendering/cases/single-layer/index.html b/rendering/cases/single-layer/index.html new file mode 100644 index 0000000000..8ee3c8d0a6 --- /dev/null +++ b/rendering/cases/single-layer/index.html @@ -0,0 +1,22 @@ + + + + + + +
+ + + + \ No newline at end of file diff --git a/rendering/cases/single-layer/main.js b/rendering/cases/single-layer/main.js new file mode 100644 index 0000000000..0fe6335004 --- /dev/null +++ b/rendering/cases/single-layer/main.js @@ -0,0 +1,19 @@ +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import TileLayer from '../../../src/ol/layer/Tile.js'; +import OSM from '../../../src/ol/source/OSM.js'; + +new Map({ + layers: [ + new TileLayer({ + source: new OSM() + }) + ], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 0 + }) +}); + +render(); diff --git a/rendering/test.js b/rendering/test.js new file mode 100755 index 0000000000..3e14aa8e5b --- /dev/null +++ b/rendering/test.js @@ -0,0 +1,193 @@ +#! /usr/bin/env node +const puppeteer = require('puppeteer'); +const webpack = require('webpack'); +const config = require('./webpack.config'); +const middleware = require('webpack-dev-middleware'); +const http = require('http'); +const path = require('path'); +const png = require('pngjs'); +const fs = require('fs'); +const fse = require('fs-extra'); +const pixelmatch = require('pixelmatch'); +const yargs = require('yargs'); + +const compiler = webpack(Object.assign({mode: 'development'}, config)); + +const handler = middleware(compiler, { + lazy: true, + logLevel: 'error' +}); + +function getHref(entry) { + return path.dirname(entry).slice(1) + '/'; +} + +function notFound(res) { + return () => { + const items = []; + for (const key in config.entry) { + const href = getHref(config.entry[key]); + items.push(`
  • ${href}
  • `); + } + const markup = `
      ${items.join('')}
    `; + + res.writeHead(404, { + 'Content-Type': 'text/html' + }); + res.end(markup); + }; +} + +function serve(port) { + return new Promise((resolve, reject) => { + const server = http.createServer((req, res) => { + handler(req, res, notFound(res)); + }); + + server.listen(port, err => { + if (err) { + return reject(err); + } + resolve(() => server.close()); + }); + }); +} + +function getActualScreenshotPath(entry) { + return path.join(__dirname, path.dirname(entry), 'actual.png'); +} + +function getExpectedScreenshotPath(entry) { + return path.join(__dirname, path.dirname(entry), 'expected.png'); +} + +function parsePNG(filepath) { + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(filepath); + stream.on('error', err => { + if (err.code === 'ENOENT') { + return reject(new Error(`File not found: ${filepath}`)); + } + reject(err); + }); + + const image = stream.pipe(new png.PNG()); + image.on('parsed', () => resolve(image)); + image.on('error', reject); + }); +} + +async function match(actual, expected) { + const actualImage = await parsePNG(actual); + const expectedImage = await parsePNG(expected); + const width = expectedImage.width; + const height = expectedImage.height; + if (actualImage.width != width) { + throw new Error(`Unexpected width for ${actual}: expected ${width}, got ${actualImage.width}`); + } + if (actualImage.height != height) { + throw new Error(`Unexpected height for ${actual}: expected ${height}, got ${actualImage.height}`); + } + const count = pixelmatch(actualImage.data, expectedImage.data, null, width, height); + return count / (width * height); +} + +async function assertScreenshotsMatch(entry) { + const actual = getActualScreenshotPath(entry); + const expected = getExpectedScreenshotPath(entry); + let mismatch, error; + try { + mismatch = await match(actual, expected); + } catch (err) { + error = err; + } + if (error) { + return error; + } + if (mismatch) { + return new Error(`${entry} mistmatch: ${mismatch}`); + } +} + +function exposeRender(page) { + return new Promise((resolve, reject) => { + const innerPromise = new Promise(innerResolve => { + page.exposeFunction('render', innerResolve).then(() => resolve(() => innerPromise), reject); + }); + }); +} + +async function renderPage(page, entry, options) { + const href = getHref(entry); + const renderCalled = await exposeRender(page); + await page.goto(`http://localhost:${options.port}${href}`, {waitUntil: 'networkidle2'}); + await renderCalled(); + await page.screenshot({path: getActualScreenshotPath(entry)}); +} + +async function copyActualToExpected(entry) { + const actual = getActualScreenshotPath(entry); + const expected = getExpectedScreenshotPath(entry); + await fse.copy(actual, expected); +} + +async function renderEach(page, entries, options) { + let fail = false; + for (const entry of entries) { + await renderPage(page, entry, options); + if (options.fix) { + await copyActualToExpected(entry); + continue; + } + const error = await assertScreenshotsMatch(entry); + if (error) { + process.stderr.write(`${error.message}\n`); + fail = true; + } + } + return fail; +} + +async function render(entries, options) { + const browser = await puppeteer.launch(); + let fail = false; + + try { + const page = await browser.newPage(); + await page.setViewport({width: 256, height: 256}); + fail = await renderEach(page, entries, options); + } finally { + browser.close(); + } + + if (fail) { + throw new Error('RENDERING TESTS FAILED'); + } +} + +async function main(entries, options) { + const done = await serve(options.port); + try { + await render(entries, options); + } finally { + done(); + } +} + +if (require.main === module) { + + const options = yargs. + option('fix', { + describe: 'Accept all screenshots as accepted', + default: false + }). + option('port', { + describe: 'The port for serving rendering cases', + default: 3000 + }). + parse(); + + const entries = Object.keys(config.entry).map(key => config.entry[key]); + + main(entries, options).catch(err => process.stderr.write(`${err.message}\n`, () => process.exit(1))); +} diff --git a/rendering/webpack.config.js b/rendering/webpack.config.js new file mode 100644 index 0000000000..2541c394f5 --- /dev/null +++ b/rendering/webpack.config.js @@ -0,0 +1,36 @@ +const CopyPlugin = require('copy-webpack-plugin'); +const fs = require('fs'); +const path = require('path'); + +const cases = path.join(__dirname, 'cases'); + +const caseDirs = fs.readdirSync(cases); + +const entry = {}; +caseDirs.forEach(c => { + entry[`cases/${c}/main`] = `./cases/${c}/main.js`; +}); + +module.exports = { + context: __dirname, + target: 'web', + entry: entry, + module: { + rules: [{ + use: { + loader: 'buble-loader' + }, + test: /\.js$/, + include: [ + path.join(__dirname, '..', 'src') + ] + }] + }, + plugins: [ + new CopyPlugin([ + {from: '../src/ol/ol.css', to: 'css'}, + {from: 'cases/**/*.html'} + ]) + ], + devtool: 'source-map' +}; From 12535580069e2d2933e14938ac35412056dcc746 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 9 Nov 2018 09:01:10 -0700 Subject: [PATCH 2/7] Register new handler for render with each page navigation --- rendering/cases/multiple-layers/expected.png | Bin 0 -> 5753 bytes rendering/cases/multiple-layers/index.html | 22 +++++++++++++ rendering/cases/multiple-layers/main.js | 33 +++++++++++++++++++ rendering/cases/single-layer/expected.png | Bin 26049 -> 26018 bytes rendering/test.js | 25 +++++++++----- 5 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 rendering/cases/multiple-layers/expected.png create mode 100644 rendering/cases/multiple-layers/index.html create mode 100644 rendering/cases/multiple-layers/main.js diff --git a/rendering/cases/multiple-layers/expected.png b/rendering/cases/multiple-layers/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..60ccc74409b6e049ea25b6c22c481136bc1add2d GIT binary patch literal 5753 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 + + + + + +
    + + + + diff --git a/rendering/cases/multiple-layers/main.js b/rendering/cases/multiple-layers/main.js new file mode 100644 index 0000000000..dcce3a0d06 --- /dev/null +++ b/rendering/cases/multiple-layers/main.js @@ -0,0 +1,33 @@ +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import {Vector as VectorLayer, Tile as TileLayer} from '../../../src/ol/layer.js'; +import {Vector as VectorSource, OSM} from '../../../src/ol/source.js'; +import Point from '../../../src/ol/geom/Point.js'; +import Feature from '../../../src/ol/Feature.js'; +import {fromLonLat} from '../../../src/ol/proj.js'; + +const center = fromLonLat([-111, 45.7]); + +new Map({ + layers: [ + new TileLayer({ + source: new OSM() + }), + new VectorLayer({ + source: new VectorSource({ + features: [ + new Feature( + new Point(center) + ) + ] + }) + }) + ], + target: 'map', + view: new View({ + center: center, + zoom: 4 + }) +}); + +render(); diff --git a/rendering/cases/single-layer/expected.png b/rendering/cases/single-layer/expected.png index c57b9f9067e1f3f0773ddfe3f052f91f9c3e649b..f3747f13a15246e3d804879c76ca8afdcc0b37c8 100644 GIT binary patch literal 26018 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 M)bv$blpWjC(NApigX literal 26049 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 +Im~y%m;rQugnc}_^ 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 diff --git a/rendering/test.js b/rendering/test.js index 3e14aa8e5b..da469b2d27 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -109,19 +109,25 @@ async function assertScreenshotsMatch(entry) { } } -function exposeRender(page) { - return new Promise((resolve, reject) => { - const innerPromise = new Promise(innerResolve => { - page.exposeFunction('render', innerResolve).then(() => resolve(() => innerPromise), reject); - }); +let handleRender; +async function exposeRender(page) { + await page.exposeFunction('render', () => { + if (!handleRender) { + throw new Error('No render handler set for current page'); + } + handleRender(); }); } async function renderPage(page, entry, options) { - const href = getHref(entry); - const renderCalled = await exposeRender(page); - await page.goto(`http://localhost:${options.port}${href}`, {waitUntil: 'networkidle2'}); - await renderCalled(); + const renderCalled = new Promise(resolve => { + handleRender = () => { + handleRender = null; + resolve(); + }; + }); + await page.goto(`http://localhost:${options.port}${getHref(entry)}`, {waitUntil: 'networkidle0'}); + await renderCalled; await page.screenshot({path: getActualScreenshotPath(entry)}); } @@ -154,6 +160,7 @@ async function render(entries, options) { try { const page = await browser.newPage(); + await exposeRender(page); await page.setViewport({width: 256, height: 256}); fail = await renderEach(page, entries, options); } finally { From 8c89e17618e1b0486e7297fb52e35e0eec74dc0e Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 9 Nov 2018 09:15:28 -0700 Subject: [PATCH 3/7] Avoid headless mode in CI --- .circleci/config.yml | 2 +- package.json | 1 + rendering/test.js | 69 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e627f3c1c..444dab6fce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/node:10-browsers + - image: circleci/node:latest-browsers working_directory: ~/repo diff --git a/package.json b/package.json index 4a46d031a1..90b1858aa4 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "karma-mocha": "1.3.0", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^4.0.0-rc.2", + "loglevelnext": "^3.0.0", "marked": "0.5.1", "mocha": "5.2.0", "mustache": "^3.0.0", diff --git a/rendering/test.js b/rendering/test.js index da469b2d27..e9f566e8ac 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -10,14 +10,10 @@ const fs = require('fs'); const fse = require('fs-extra'); const pixelmatch = require('pixelmatch'); const yargs = require('yargs'); +const log = require('loglevelnext'); const compiler = webpack(Object.assign({mode: 'development'}, config)); -const handler = middleware(compiler, { - lazy: true, - logLevel: 'error' -}); - function getHref(entry) { return path.dirname(entry).slice(1) + '/'; } @@ -38,16 +34,24 @@ function notFound(res) { }; } -function serve(port) { +function serve(options) { + const handler = middleware(compiler, { + lazy: true, + logger: options.log, + stats: 'minimal' + }); + return new Promise((resolve, reject) => { const server = http.createServer((req, res) => { handler(req, res, notFound(res)); }); - server.listen(port, err => { + server.listen(options.port, options.host, err => { if (err) { return reject(err); } + const address = server.address(); + options.log.info(`test server listening http://${address.address}:${address.port}/`); resolve(() => server.close()); }); }); @@ -126,7 +130,7 @@ async function renderPage(page, entry, options) { resolve(); }; }); - await page.goto(`http://localhost:${options.port}${getHref(entry)}`, {waitUntil: 'networkidle0'}); + await page.goto(`http://${options.host}:${options.port}${getHref(entry)}`, {waitUntil: 'networkidle0'}); await renderCalled; await page.screenshot({path: getActualScreenshotPath(entry)}); } @@ -155,11 +159,29 @@ async function renderEach(page, entries, options) { } async function render(entries, options) { - const browser = await puppeteer.launch(); + const browser = await puppeteer.launch({ + args: options.puppeteerArgs, + headless: !process.env.CI + }); + let fail = false; try { const page = await browser.newPage(); + page.on('error', err => { + options.log.error('page crash', err); + }); + page.on('pageerror', err => { + options.log.error('uncaught exception', err); + }); + page.on('console', message => { + const type = message.type(); + if (options.log[type]) { + options.log[type](message.text()); + } + }); + + page.setDefaultNavigationTimeout(options.timeout); await exposeRender(page); await page.setViewport({width: 256, height: 256}); fail = await renderEach(page, entries, options); @@ -173,7 +195,7 @@ async function render(entries, options) { } async function main(entries, options) { - const done = await serve(options.port); + const done = await serve(options); try { await render(entries, options); } finally { @@ -188,13 +210,38 @@ if (require.main === module) { describe: 'Accept all screenshots as accepted', default: false }). + option('host', { + describe: 'The host for serving rendering cases', + default: '127.0.0.1' + }). option('port', { describe: 'The port for serving rendering cases', + type: 'number', default: 3000 }). + option('timeout', { + describe: 'The timeout for loading pages (in milliseconds)', + type: 'number', + default: 60000 + }). + option('log-level', { + describe: 'The level for logging', + choices: ['trace', 'debug', 'info', 'warn', 'error', 'silent'], + default: 'error' + }). + option('puppeteer-args', { + describe: 'Args of for puppeteer.launch()', + type: 'array', + default: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [] + }). parse(); const entries = Object.keys(config.entry).map(key => config.entry[key]); - main(entries, options).catch(err => process.stderr.write(`${err.message}\n`, () => process.exit(1))); + options.log = log.create({name: 'rendering', level: options.logLevel}); + + main(entries, options).catch(err => { + options.log.error(err.message); + process.exit(1); + }); } From 4923fac359f1bf8dfe2f3107aab93b6dd984c839 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 9 Nov 2018 13:45:19 -0700 Subject: [PATCH 4/7] Avoid 404 for favicon.ico --- rendering/test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rendering/test.js b/rendering/test.js index e9f566e8ac..d536b3d45c 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -18,8 +18,14 @@ function getHref(entry) { return path.dirname(entry).slice(1) + '/'; } -function notFound(res) { +function notFound(req, res) { return () => { + if (req.url === '/favicon.ico') { + res.writeHead(204); + res.end(); + return; + } + const items = []; for (const key in config.entry) { const href = getHref(config.entry[key]); @@ -43,7 +49,7 @@ function serve(options) { return new Promise((resolve, reject) => { const server = http.createServer((req, res) => { - handler(req, res, notFound(res)); + handler(req, res, notFound(req, res)); }); server.listen(options.port, options.host, err => { From f444da29bced769d40bb1e0cb9410383e19da100 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Thu, 8 Nov 2018 09:36:20 +0100 Subject: [PATCH 5/7] Add linestring-style rendering test --- rendering/cases/linestring-style/expected.png | Bin 0 -> 8793 bytes rendering/cases/linestring-style/index.html | 22 ++++++ rendering/cases/linestring-style/main.js | 69 ++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 rendering/cases/linestring-style/expected.png create mode 100644 rendering/cases/linestring-style/index.html create mode 100644 rendering/cases/linestring-style/main.js diff --git a/rendering/cases/linestring-style/expected.png b/rendering/cases/linestring-style/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..68797650fc0684f8c9f70f8dd4207892b83eb404 GIT binary patch literal 8793 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 AS^xk5 literal 0 HcmV?d00001 diff --git a/rendering/cases/linestring-style/index.html b/rendering/cases/linestring-style/index.html new file mode 100644 index 0000000000..96cfb5a582 --- /dev/null +++ b/rendering/cases/linestring-style/index.html @@ -0,0 +1,22 @@ + + + + + + +
    + + + + diff --git a/rendering/cases/linestring-style/main.js b/rendering/cases/linestring-style/main.js new file mode 100644 index 0000000000..800659c76f --- /dev/null +++ b/rendering/cases/linestring-style/main.js @@ -0,0 +1,69 @@ +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import Feature from '../../../src/ol/Feature.js'; +import LineString from '../../../src/ol/geom/LineString.js'; +import VectorLayer from '../../../src/ol/layer/Vector.js'; +import VectorSource from '../../../src/ol/source/Vector.js'; +import Style from '../../../src/ol/style/Style.js'; +import Stroke from '../../../src/ol/style/Stroke.js'; + + +const vectorSource = new VectorSource(); +let feature; + +feature = new Feature({ + geometry: new LineString([[-60, 60], [45, 60]]) +}); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-60, -50], [30, 10]]) +}); +feature.setStyle(new Style({ + stroke: new Stroke({color: '#f00', width: 3}) +})); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-110, -100], [0, 100], [100, -90]]) +}); +feature.setStyle(new Style({ + stroke: new Stroke({ + color: 'rgba(55, 55, 55, 0.75)', + width: 5, + lineCap: 'square', + lineDash: [4, 8], + lineJoin: 'round' + }) +})); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-80, 80], [80, 80], [-40, -90]]) +}); +feature.setStyle([ + new Style({ + stroke: new Stroke({color: '#F2F211', width: 5}) + }), + new Style({ + stroke: new Stroke({color: '#292921', width: 1}) + }) +]); +vectorSource.addFeature(feature); + + +new Map({ + pixelRatio: 1, + layers: [ + new VectorLayer({ + source: vectorSource + }) + ], + target: 'map', + view: new View({ + center: [0, 0], + resolution: 1 + }) +}); + +render(); From 800776b6cb943a226d23fb42a79344de7ed7e682 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 9 Nov 2018 14:43:11 -0700 Subject: [PATCH 6/7] Only test outdated cases by default --- package.json | 3 ++- rendering/.gitignore | 3 ++- rendering/test.js | 62 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 90b1858aa4..1011c86629 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "eslint tasks test src/ol examples config", "pretest": "npm run lint", "test-rendering": "node rendering/test.js", - "test": "npm run karma -- --single-run --log-level error && npm run test-rendering", + "test": "npm run karma -- --single-run --log-level error && npm run test-rendering -- --force", "karma": "karma start test/karma.config.js", "serve-examples": "webpack-dev-server --config examples/webpack/config.js --mode development --watch", "build-examples": "webpack --config examples/webpack/config.js --mode production", @@ -57,6 +57,7 @@ "front-matter": "^3.0.0", "fs-extra": "^7.0.0", "glob": "^7.1.2", + "globby": "^8.0.1", "handlebars": "4.0.11", "istanbul": "0.4.5", "jquery": "3.3.1", diff --git a/rendering/.gitignore b/rendering/.gitignore index 8b295d4969..9afaa0563f 100644 --- a/rendering/.gitignore +++ b/rendering/.gitignore @@ -1 +1,2 @@ -actual.png \ No newline at end of file +actual.png +pass diff --git a/rendering/test.js b/rendering/test.js index d536b3d45c..150a7885e5 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -11,6 +11,7 @@ const fse = require('fs-extra'); const pixelmatch = require('pixelmatch'); const yargs = require('yargs'); const log = require('loglevelnext'); +const globby = require('globby'); const compiler = webpack(Object.assign({mode: 'development'}, config)); @@ -71,6 +72,10 @@ function getExpectedScreenshotPath(entry) { return path.join(__dirname, path.dirname(entry), 'expected.png'); } +function getPassFilePath(entry) { + return path.join(__dirname, path.dirname(entry), 'pass'); +} + function parsePNG(filepath) { return new Promise((resolve, reject) => { const stream = fs.createReadStream(filepath); @@ -141,10 +146,16 @@ async function renderPage(page, entry, options) { await page.screenshot({path: getActualScreenshotPath(entry)}); } +async function touch(filepath) { + const fd = await fse.open(filepath, 'w'); + await fse.close(fd); +} + async function copyActualToExpected(entry) { const actual = getActualScreenshotPath(entry); const expected = getExpectedScreenshotPath(entry); await fse.copy(actual, expected); + await touch(getPassFilePath(entry)); } async function renderEach(page, entries, options) { @@ -159,7 +170,10 @@ async function renderEach(page, entries, options) { if (error) { process.stderr.write(`${error.message}\n`); fail = true; + continue; } + + await touch(getPassFilePath(entry)); } return fail; } @@ -200,7 +214,50 @@ async function render(entries, options) { } } +async function getLatest(patterns) { + const stats = await globby(patterns, {stats: true}); + let latest = 0; + for (const stat of stats) { + if (stat.mtime > latest) { + latest = stat.mtime; + } + } + return latest; +} + +async function getOutdated(entries, options) { + const libTime = await getLatest(path.join(__dirname, '..', 'src', 'ol', '**', '*')); + options.log.debug('library time', libTime); + const outdated = []; + for (const entry of entries) { + const passPath = getPassFilePath(entry); + const passTime = await getLatest(passPath); + options.log.debug(entry, 'pass time', passTime); + if (passTime < libTime) { + outdated.push(entry); + continue; + } + + const caseTime = await getLatest(path.join(__dirname, path.dirname(entry), '**', '*')); + options.log.debug(entry, 'case time', caseTime); + if (passTime < caseTime) { + outdated.push(entry); + continue; + } + + options.log.info('skipping', entry); + } + return outdated; +} + async function main(entries, options) { + if (!options.force) { + entries = await getOutdated(entries, options); + } + if (entries.length === 0) { + return; + } + const done = await serve(options); try { await render(entries, options); @@ -230,6 +287,11 @@ if (require.main === module) { type: 'number', default: 60000 }). + option('force', { + describe: 'Run all tests (instead of just outdated tests)', + type: 'boolean', + default: false + }). option('log-level', { describe: 'The level for logging', choices: ['trace', 'debug', 'info', 'warn', 'error', 'silent'], From 6f238a8d094a5bef7cf709b3eed4fd0b74d5e8a9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 9 Nov 2018 15:06:27 -0700 Subject: [PATCH 7/7] Add stub support for interactive mode --- rendering/test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rendering/test.js b/rendering/test.js index 150a7885e5..4d2c915f43 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -262,7 +262,9 @@ async function main(entries, options) { try { await render(entries, options); } finally { - done(); + if (!options.interactive) { + done(); + } } } @@ -292,6 +294,11 @@ if (require.main === module) { type: 'boolean', default: false }). + option('interactive', { + describe: 'Run all tests and keep the test server running (this option will be reworked later)', + type: 'boolean', + default: false + }). option('log-level', { describe: 'The level for logging', choices: ['trace', 'debug', 'info', 'warn', 'error', 'silent'], @@ -306,6 +313,9 @@ if (require.main === module) { const entries = Object.keys(config.entry).map(key => config.entry[key]); + if (options.interactive) { + options.force = true; + } options.log = log.create({name: 'rendering', level: options.logLevel}); main(entries, options).catch(err => {