Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use firefox and xvfb to test webgl and examples. #651

Merged
merged 15 commits into from
Dec 15, 2016
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
language: node_js
sudo: false
# At this time, the travis trusty sudo environment works, but the sudo: false
# environment doesn't (it might with a bunch of apt packages).
sudo: required
dist: trusty

node_js:
- '0.12'

addons:
firefox: latest
apt:
packages:
# I suspect that not all of these are necessary
- mesa-utils
- xvfb
- libosmesa6

- libgif-dev
- libpng-dev

- freeglut3-dev
- libxmu-dev
- libxi-dev
- libxxf86vm-dev
- libxrandr-dev

cache:
directories:
- node_modules
Expand All @@ -12,6 +34,10 @@ before_install:
- CACHE="${HOME}/cache" CMAKE_VERSION=3.5.0 CMAKE_SHORT_VERSION=3.5 source ./scripts/install_cmake.sh
- npm prune

before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

script:
- npm run build
- npm run docs
Expand Down
45 changes: 45 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enable_testing()

set(BUILD_TESTING ON CACHE BOOL "Enable geojs testing")
set(PHANTOMJS_TESTS ON CACHE BOOL "Generate phantomjs unit tests.")
set(FFHEADLESS_TESTS ON CACHE BOOL "Generate headless Firefox unit tests (requires xvfb to be running).")
set(ESLINT_TESTS ON CACHE BOOL "Generate eslint style tests for JS source files.")
set(SELENIUM_TESTS ON CACHE BOOL "Generate selenium unit tests.")

Expand Down Expand Up @@ -70,6 +71,13 @@ add_test(
)
set_property(TEST "notes-report" APPEND PROPERTY DEPENDS "notes-reset")

add_test(
NAME "total-coverage"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMAND npm run combine-coverage
)
set_property(TEST "notes-report" APPEND PROPERTY DEPENDS "notes-reset")

if(PHANTOMJS_TESTS)
find_program(NPM_EXECUTABLE npm)
add_test(
Expand All @@ -80,8 +88,45 @@ if(PHANTOMJS_TESTS)
set_property(TEST "phantomjs" APPEND PROPERTY ENVIRONMENT "CTEST_NOTES_PATH=${CMAKE_CURRENT_BINARY_DIR}/notes")
set_property(TEST "phantomjs" APPEND PROPERTY DEPENDS "notes-reset")
set_property(TEST "notes-report" APPEND PROPERTY DEPENDS "phantomjs")
set_property(TEST "total-coverage" APPEND PROPERTY DEPENDS "phantomjs")
endif()

add_custom_target(
data_files
ALL
DEPENDS ${Girder_DOWNLOAD_FILES}
)
add_test(NAME get_data_files COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target data_files)

if(FFHEADLESS_TESTS)
find_program(NPM_EXECUTABLE npm)
add_test(
NAME "ffheadless"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMAND npm run ffci
)
set_property(TEST "ffheadless" APPEND PROPERTY ENVIRONMENT "CTEST_IMAGE_PATH=${CMAKE_CURRENT_BINARY_DIR}/images")
set_property(TEST "total-coverage" APPEND PROPERTY DEPENDS "ffheadless")
set_property(TEST "ffheadless" APPEND PROPERTY DEPENDS "get_data_files")
endif()

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/base-images.tgz"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
# Make sure we have the data files.
COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target data_files
# Run the ffheadless test, asking to save all images
COMMAND TEST_SAVE_IMAGE=all npm run ffci
# Make a tarball of all of the images
COMMAND tar -zcvf "${CMAKE_CURRENT_BINARY_DIR}/base-images.tgz" --exclude=*-test.png --exclude=*-diff.png -C "${CMAKE_CURRENT_BINARY_DIR}/images" .
COMMENT "Create baseline images, then tar them into a single file"
VERBATIM
)

add_custom_target(baseline_images DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/base-images.tgz")

add_test(NAME baseline_images CONFIGURATIONS "baseline_images" COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target baseline_images)
set_property(TEST "baseline_images" APPEND PROPERTY ENVIRONMENT "CTEST_IMAGE_PATH=${CMAKE_CURRENT_BINARY_DIR}/images")

if(SELENIUM_TESTS)

find_package(PythonInterp REQUIRED)
Expand Down
3 changes: 2 additions & 1 deletion cmake/travis_build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include(${CTEST_SOURCE_DIRECTORY}/CTestConfig.cmake)
set(CTEST_SITE "Travis")
set(CTEST_BUILD_NAME "Linux-$ENV{TRAVIS_BRANCH}")
set(CTEST_CMAKE_GENERATOR "Unix Makefiles")
set(coverage_file "${CTEST_SOURCE_DIRECTORY}/dist/cobertura/phantomjs/coverage.xml")
set(coverage_file "${CTEST_SOURCE_DIRECTORY}/dist/cobertura/cobertura-coverage.xml")

ctest_start("Continuous")
ctest_configure(
Expand All @@ -15,6 +15,7 @@ ctest_build()
ctest_test(PARALLEL_LEVEL 1 RETURN_VALUE res)
if(EXISTS "${coverage_file}")
file(COPY "${coverage_file}" DESTINATION "${CTEST_BINARY_DIRECTORY}")
file(RENAME ${CTEST_BINARY_DIRECTORY}/cobertura-coverage.xml ${CTEST_BINARY_DIRECTORY}/coverage.xml)
ctest_coverage()
file(REMOVE ${CTEST_BINARY_DIRECTORY}/coverage.xml)
endif()
Expand Down
121 changes: 118 additions & 3 deletions karma-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,104 @@ var url = require('url');
var fs = require('fs');
var path = require('path');
var notes_path = process.env.CTEST_NOTES_PATH || path.resolve('notes');
var image_path = process.env.CTEST_IMAGE_PATH || path.resolve('images');
var test_case = process.env.GEOJS_TEST_CASE || 'tests/all.js';
var getRawBody = require('raw-body');

// Create the notes directory, if it doesn't exist.
if (!fs.existsSync(notes_path)) {
fs.mkdirSync(notes_path);
}
if (!fs.existsSync(image_path)) {
fs.mkdirSync(image_path);
}

/**
* Express style middleware to handle REST requests
* to `/notes` on the test server.
* This function returns true if (1) there is an environment variable set
* called "TEST_SAVE_IMAGE", and either (2a) the name of the test appears in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the method used to generate new baselines? Things like this could use an overview in readthedocs, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I figure I should write a script to explicitly generate new baselines, and probably also a replacement for upload_test_cases.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should give credit to @ronichoudhury, as some of this I lifted from the candela project.

* the value of the variable when treated as a comma separated list of strings,
* or (2b) the variable reads "all".
*/
function doSaveImage(name) {
var saveList = process.env.TEST_SAVE_IMAGE;
var result;
if (saveList === 'all' || !saveList) {
result = !!saveList;
} else {
result = saveList.split(',').indexOf(name) >= 0;
if (name.indexOf('-') >= 0) {
result = result || saveList.split(',').indexOf(name.split('-')[1]) >= 0;
}
}
return result;
}

/* Save an image. The image is expected to be a base64 encoded string, with or
* without a mime type specifier.
*
* @param {string} name: base name for the image.
* @param {string} image: a base64 encoded png image.
* @param {boolean} always: if true, always save the image regardless of
* environment settings.
*/
function saveImage(name, image, always) {
if (always || doSaveImage(name)) {
if (image.indexOf(',') >= 0) {
image = image.split(',')[1];
}
var dest = path.resolve(image_path, name + '.png');
fs.writeFileSync(dest, image, 'base64');
}
}

/* Compare an image to a base image. If it violates a threshold, save the
* image and a diff between it and the base image. Returns the resemble
* results.
*
* @param {string} name: base name for the image.
* @param {string} image: a base64 encoded png image.
* @param {number} threshold: allowed difference between this image and the
* base image.
* @param {function} callback: a function to call when complete.
*/
function compareImage(name, image, threshold, callback) {
/* Note, we could read the xvfb frame buffer using imageMagick, which would
* get the entire browser display, including it's window border, tabs, search
* bar, and non-canvas elements. It might be worth install a kiosk extension
* to FireFox (or use Chrome in Kiosk mode), and exclude the portions of the
* window that are used for Karma information.
var child_process = require('child_process');
var dest = path.resolve(image_path, name + '-xvfb.png');
child_process.execSync('import -window root \'' + dest.replace(/'/g, "'\\''") + '\'');
var xvfbImage = new Buffer(fs.readFileSync(dest)).toString('base64');
xvfbImage = 'data:image/png;base64,' + xvfbImage;
*/
var resemble = require('node-resemble');
var src = path.resolve('dist/data/base-images', name + '.png');
if (!fs.existsSync(src)) {
src = path.resolve(image_path, name + '.png');
}
var refImage = new Buffer(fs.readFileSync(src)).toString('base64');
refImage = 'data:image/png;base64,' + refImage;
resemble(image)
.compareTo(refImage)
.ignoreAntialiasing()
.onComplete(function (results) {
console.log('Image comparison: ' + name + ', delta: ' +
Number(results.misMatchPercentage) * 0.01);
var passed = (Number(results.misMatchPercentage) <= threshold * 100);
saveImage(name + '-test', image, !passed);
saveImage(name + '-diff', results.getImageDataUrl(), !passed);
results.passed = passed;
if (callback) {
callback(results);
}
});
}

/**
* Express style middleware to handle REST requests to `/notes` and
* `/testImage` on the test server.
*/
var notes_middleware = function (config) {
var notes = {};
Expand Down Expand Up @@ -46,6 +133,32 @@ var notes_middleware = function (config) {
response.writeHead(200);
return response.end(JSON.stringify(notes));
}
} else if (parsed.pathname === '/testImage') {
if (request.method === 'PUT') {
return getRawBody(request).then(function (body) {
var name = query.name;
var image = '' + body;
saveImage(name, image);
if (query.compare === 'true') {
compareImage(name, image, query.threshold, function (results) {
response.writeHead(200);
return response.end(JSON.stringify(results));
});
} else {
response.writeHead(200);
return response.end('{}');
}
}).catch(function (err) {
response.writeHead(500);
response.end(err.message);
});
} else if (request.method === 'GET') {
var src = path.resolve(image_path, query.name + '.png');
var img = new Buffer(fs.readFileSync(src)).toString('base64');
img = 'data:image/png;base64,' + img;
response.writeHead(200);
return response.end(img);
}
}
next();
};
Expand All @@ -56,14 +169,16 @@ module.exports = {
files: [
test_case,
{pattern: 'tests/data/**/*', included: false},
{pattern: 'tests/cases/**/*.js', included: false, served: false, watched: true}
{pattern: 'tests/cases/**/*.js', included: false, served: false, watched: true},
{pattern: 'tests/gl-cases/**/*.js', included: false, served: false, watched: true}
],
proxies: {
'/data/': '/base/tests/data/'
},
browsers: [
'PhantomJS'
],
browserNoActivityTimeout: 30000,
reporters: [
'progress',
'kjhtml'
Expand Down
1 change: 1 addition & 0 deletions karma-cov.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ karma_config.coverageReporter = {
reporters: [
{type: 'html', dir: 'dist/coverage/', subdir: browser},
{type: 'cobertura', dir: 'dist/cobertura/', file: 'coverage.xml', subdir: browser},
{type: 'json', dir: 'dist/coverage/json/', subdir: browser},
{type: 'lcovonly', dir: 'lcov', subdir: browser},
{type: 'text'}
]
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"gl-vec4": "^1.0.1",
"glob": "^7.0.3",
"imports-loader": "^0.6.5",
"istanbul-combine": "^0.3.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manthey is this the package that combines the xmls from different runs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, which is needed to have the xml file in the format we send to cdash. We send multiple lcov files to Codecov, which works without this, but cdash expects a single Cobertura file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! thanks.

"istanbul-instrumenter-loader": "^0.2.0",
"jade": "^1.11.0",
"jade-loader": "^0.8.0",
Expand All @@ -52,6 +53,7 @@
"json-loader": "^0.5.4",
"karma": "^0.13.22",
"karma-coverage": "^1.0.0",
"karma-firefox-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-jasmine-html-reporter": "^0.2.0",
"karma-phantomjs-launcher": "^1.0.0",
Expand All @@ -60,6 +62,7 @@
"karma-webpack": "^1.7.0",
"mousetrap": "^1.6.0",
"node-fs-extra": "^0.8.1",
"node-resemble": "^1.1.3",
"phantomjs-prebuilt": "^2.1.5",
"proj4": "^2.3.14",
"raw-body": "^2.1.6",
Expand All @@ -75,10 +78,13 @@
"build": "webpack --config webpack.config.js && webpack --config external.config.js",
"build-examples": "webpack --config webpack-examples.config.js",
"lint": "eslint --cache .",
"test": "karma start karma-cov.conf.js --single-run",
"test": "GEOJS_TEST_CASE=tests/test-unit.js karma start karma-cov.conf.js --single-run --browsers PhantomJS",
"start": "karma start karma.conf.js",
"ci": "karma start karma-cov.conf.js --single-run --browsers PhantomJS",
"ci": "GEOJS_TEST_CASE=tests/test-unit.js karma start karma-cov.conf.js --single-run --browsers PhantomJS",
"ffci": "GEOJS_TEST_CASE=tests/test-gl.js karma start karma-cov.conf.js --single-run --browsers Firefox",
"ffci-xvfb": "GEOJS_TEST_CASE=tests/test-gl.js xvfb-run -s '-ac -screen 0 1280x1024x24' karma start karma-cov.conf.js --single-run --browsers Firefox",
"codecov": "cat lcov/*/lcov.info | codecov",
"combine-coverage": "istanbul-combine -d dist/cobertura -r cobertura 'dist/coverage/json/**/coverage-final.json'",
"examples": "webpack-dev-server --config webpack-examples.config.js --host ${HOST-127.0.0.1} --port ${PORT-8082} --content-base dist/",
"start-test": "node examples/build.js; forever start ./testing/test-runners/server.js",
"stop-test": "forever stop ./testing/test-runners/server.js",
Expand Down
2 changes: 1 addition & 1 deletion src/choroplethFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var choroplethFeature = function (arg) {
var scalarId, scalarValue;

if (data === undefined) {
return m_this.choropleth.get('scalar')();
return m_this.choropleth.get('scalar')() || [];
} else {
scalarId = m_this.choropleth.get('accessors')().scalarId;
scalarValue = m_this.choropleth.get('accessors')().scalarValue;
Expand Down
11 changes: 11 additions & 0 deletions src/gl/vglRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ var vglRenderer = function (arg) {
canvas.attr('class', 'webgl-canvas');
canvas.css('display', 'block');
$(m_this.layer().node().get(0)).append(canvas);

if (window.contextPreserveDrawingBuffer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshots are actually the canvas drawing buffers, not actual screenshots. There are virtues to this approach: it is easy to ignore the window border, tabs, etc. and it doesn't matter if the div is aligned the same way. It ignores non-canvas data, which is both good and bad. It does require that you can ask the canvas for the drawing buffer, though, hence this flag,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see thanks.

var elem = canvas.get(0);
var getContext = elem.getContext;
elem.getContext = function (contextType, contextAttributes) {
contextAttributes = contextAttributes || {};
contextAttributes.preserveDrawingBuffer = true;
return getContext.call(elem, contextType, contextAttributes);
};
}

m_viewer = vgl.viewer(canvas.get(0), arg.options);
m_viewer.init();
m_contextRenderer = m_viewer.renderWindow().activeRenderer();
Expand Down
2 changes: 1 addition & 1 deletion src/graphFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var feature = require('./feature');
/**
* Create a new instance of class graphFeature
*
* @class geo.greaphFeature
* @class geo.graphFeature
* @extends geo.feature
* @returns {geo.graphFeature}
*/
Expand Down
1 change: 1 addition & 0 deletions testing/test-data/base-images.tgz.md5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9b076809dc9e876455a131748d670fc9
1 change: 1 addition & 0 deletions testing/test-data/base-images.tgz.url
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://data.kitware.com/api/v1/file/5849b21e8d777f5cdd828156/download
Loading