diff --git a/.travis.yml b/.travis.yml index 311d3630..585a8f9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,9 @@ jobs: env: TOXENV=py37 addons: postgresql: "9.4" + chrome: stable + services: + - xvfb - name: "python3.7-postgresql-10-bionic" dist: bionic language: python @@ -17,6 +20,7 @@ jobs: env: TOXENV=py37 addons: postgresql: '10' + chrome: stable apt: packages: - postgresql-10 @@ -24,6 +28,7 @@ jobs: - postgresql-server-dev-10 services: - postgresql + - xvfb - name: "python3.8-postgresql-12-focal" dist: focal language: python @@ -31,6 +36,7 @@ jobs: env: TOXENV=py38 addons: postgresql: '12' + chrome: stable apt: packages: - postgresql-12 @@ -38,6 +44,7 @@ jobs: - postgresql-server-dev-12 services: - postgresql + - xvfb cache: directories: @@ -53,6 +60,9 @@ env: # https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882 - BOTO_CONFIG=/doesnotexist +before_install: + - export CHROME_BIN=/usr/bin/google-chrome + before_script: - sudo systemctl stop postgresql # the port may have been auto-configured to use 5433 if it thought 5422 was already in use, @@ -64,8 +74,9 @@ before_script: - psql -c "create user lemur with password 'lemur;'" -U postgres - psql lemur -c "create extension IF NOT EXISTS pg_trgm;" -U postgres - npm config set registry https://registry.npmjs.org - - npm install -g bower + - npm install -g npm@latest bower - pip install --upgrade setuptools + - export DISPLAY=:99.0 install: - pip install coveralls @@ -74,6 +85,7 @@ install: script: - make test - bandit -r . -ll -ii -x lemur/tests/,docs + - make test-js after_success: - coveralls diff --git a/gulp/build.js b/gulp/build.js index d86ce425..667f28e8 100644 --- a/gulp/build.js +++ b/gulp/build.js @@ -1,257 +1,260 @@ 'use strict'; var gulp = require('gulp'), - minifycss = require('gulp-minify-css'), - concat = require('gulp-concat'), - less = require('gulp-less'), - gulpif = require('gulp-if'), - gutil = require('gulp-util'), - foreach = require('gulp-foreach'), - path =require('path'), - merge = require('merge-stream'), - del = require('del'), - size = require('gulp-size'), - plumber = require('gulp-plumber'), - autoprefixer = require('gulp-autoprefixer'), - jshint = require('gulp-jshint'), - inject = require('gulp-inject'), - cache = require('gulp-cache'), - ngAnnotate = require('gulp-ng-annotate'), - csso = require('gulp-csso'), - useref = require('gulp-useref'), - filter = require('gulp-filter'), - rev = require('gulp-rev'), - imagemin = require('gulp-imagemin'), - minifyHtml = require('gulp-minify-html'), - bowerFiles = require('main-bower-files'), - karma = require('karma'), - replace = require('gulp-replace'), - argv = require('yargs').argv; - - -gulp.task('clean', function (done) { - del(['.tmp', 'lemur/static/dist'], done); - done(); -}); - -gulp.task('default', gulp.series(['clean'], function () { - gulp.start('fonts', 'styles'); -})); - -gulp.task('test', function (done) { - // returning the promise - return new karma.Server({ - configFile: __dirname + '/karma.conf.js', - singleRun: true - }, function() { - done(); - }).start(); -}); - -gulp.task('dev:fonts', function () { - let fileList = [ - 'bower_components/bootstrap/dist/fonts/*', - 'bower_components/fontawesome/fonts/*' - ]; - - return gulp.src(fileList) - .pipe(gulp.dest('.tmp/fonts')); // returns a stream making it async -}); - -gulp.task('dev:styles', function () { - let baseContent = '@import "bower_components/bootstrap/less/bootstrap.less";@import "bower_components/bootswatch/$theme$/variables.less";@import "bower_components/bootswatch/$theme$/bootswatch.less";@import "bower_components/bootstrap/less/utilities.less";'; - let isBootswatchFile = function (file) { - - let suffix = 'bootswatch.less'; - return file.path.indexOf(suffix, file.path.length - suffix.length) !== -1; - }; - - let isBootstrapFile = function (file) { - let suffix = 'bootstrap-', - fileName = path.basename(file.path); - - return fileName.indexOf(suffix) === 0; - }; - - var fileList = [ - 'bower_components/bootswatch/sandstone/bootswatch.less', - 'bower_components/fontawesome/css/font-awesome.css', - 'bower_components/angular-chart.js/dist/angular-chart.css', - 'bower_components/angular-loading-bar/src/loading-bar.css', - 'bower_components/angular-ui-switch/angular-ui-switch.css', - 'bower_components/angular-wizard/dist/angular-wizard.css', - 'bower_components/ng-table/dist/ng-table.css', - 'bower_components/angularjs-toaster/toaster.css', - 'bower_components/angular-ui-select/dist/select.css', - 'lemur/static/app/styles/lemur.css' - ]; - - return gulp.src(fileList) - .pipe(gulpif(isBootswatchFile, foreach(function (stream, file) { - let themeName = path.basename(path.dirname(file.path)), - content = replaceAll(baseContent, '$theme$', themeName), - file2 = string_src('bootstrap-' + themeName + '.less', content); - - return file2; - }))) - .pipe(less()) - .pipe(gulpif(isBootstrapFile, foreach(function (stream, file) { - let fileName = path.basename(file.path), - themeName = fileName.substring(fileName.indexOf('-') + 1, fileName.indexOf('.')); - - // http://stackoverflow.com/questions/21719833/gulp-how-to-add-src-files-in-the-middle-of-a-pipe - // https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-multiple-sources-in-one-task.md - return merge(stream, gulp.src(['.tmp/styles/font-awesome.css', '.tmp/styles/lemur.css'], { allowEmpty: true })) - .pipe(concat('style-' + themeName + '.css')); - }))) - .pipe(plumber()) - .pipe(concat('styles.css')) - .pipe(minifycss()) - .pipe(autoprefixer('last 1 version')) - .pipe(gulp.dest('.tmp/styles')) - .pipe(size()); -}); + minifycss = require('gulp-minify-css'), + concat = require('gulp-concat'), + less = require('gulp-less'), + gulpif = require('gulp-if'), + gutil = require('gulp-util'), + foreach = require('gulp-foreach'), + path = require('path'), + merge = require('merge-stream'), + del = require('del'), + size = require('gulp-size'), + plumber = require('gulp-plumber'), + autoprefixer = require('gulp-autoprefixer'), + jshint = require('gulp-jshint'), + inject = require('gulp-inject'), + cache = require('gulp-cache'), + ngAnnotate = require('gulp-ng-annotate'), + csso = require('gulp-csso'), + useref = require('gulp-useref'), + filter = require('gulp-filter'), + rev = require('gulp-rev'), + imagemin = require('gulp-imagemin'), + minifyHtml = require('gulp-minify-html'), + bowerFiles = require('main-bower-files'), + karma = require('karma'), + replace = require('gulp-replace'), + argv = require('yargs').argv; // http://stackoverflow.com/questions/1144783/replacing-all-occurrences-of-a-string-in-javascript function escapeRegExp(string) { - return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } function replaceAll(string, find, replace) { - return string.replace(new RegExp(escapeRegExp(find), 'g'), replace); + return string.replace(new RegExp(escapeRegExp(find), 'g'), replace); } -function string_src(filename, string) { - let src = require('stream').Readable({ objectMode: true }); - src._read = function () { - this.push(new gutil.File({ cwd: '', base: '', path: filename, contents: new Buffer(string) })); - this.push(null); - }; - return src; +function stringSrc(filename, string) { + let src = require('stream').Readable({objectMode: true}); + src._read = function () { + this.push(new gutil.File({cwd: '', base: '', path: filename, contents: new Buffer(string)})); + this.push(null); + }; + return src; } +gulp.task('clean', function (done) { + del(['.tmp', 'lemur/static/dist'], done); + done(); +}); + +gulp.task('default', gulp.series(['clean'], function () { + gulp.start('fonts', 'styles'); +})); + +gulp.task('test', function (done) { + new karma.Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, function (err) { + if (err === 0) { + done(); + } else { + // if karma server failed to start raise error + done(new gutil.PluginError('karma', { + message: 'Karma Tests failed' + })); + } + }).start(); +}); + +gulp.task('dev:fonts', function () { + let fileList = [ + 'bower_components/bootstrap/dist/fonts/*', + 'bower_components/fontawesome/fonts/*' + ]; + + return gulp.src(fileList) + .pipe(gulp.dest('.tmp/fonts')); // returns a stream making it async +}); + +gulp.task('dev:styles', function () { + let baseContent = '@import "bower_components/bootstrap/less/bootstrap.less";@import "bower_components/bootswatch/$theme$/variables.less";@import "bower_components/bootswatch/$theme$/bootswatch.less";@import "bower_components/bootstrap/less/utilities.less";'; + let isBootswatchFile = function (file) { + + let suffix = 'bootswatch.less'; + return file.path.indexOf(suffix, file.path.length - suffix.length) !== -1; + }; + + let isBootstrapFile = function (file) { + let suffix = 'bootstrap-', + fileName = path.basename(file.path); + + return fileName.indexOf(suffix) === 0; + }; + + let fileList = [ + 'bower_components/bootswatch/sandstone/bootswatch.less', + 'bower_components/fontawesome/css/font-awesome.css', + 'bower_components/angular-chart.js/dist/angular-chart.css', + 'bower_components/angular-loading-bar/src/loading-bar.css', + 'bower_components/angular-ui-switch/angular-ui-switch.css', + 'bower_components/angular-wizard/dist/angular-wizard.css', + 'bower_components/ng-table/dist/ng-table.css', + 'bower_components/angularjs-toaster/toaster.css', + 'bower_components/angular-ui-select/dist/select.css', + 'lemur/static/app/styles/lemur.css' + ]; + + return gulp.src(fileList) + .pipe(gulpif(isBootswatchFile, foreach(function (stream, file) { + let themeName = path.basename(path.dirname(file.path)), + content = replaceAll(baseContent, '$theme$', themeName); + return stringSrc('bootstrap-' + themeName + '.less', content); + }))) + .pipe(less()) + .pipe(gulpif(isBootstrapFile, foreach(function (stream, file) { + let fileName = path.basename(file.path), + themeName = fileName.substring(fileName.indexOf('-') + 1, fileName.indexOf('.')); + + // http://stackoverflow.com/questions/21719833/gulp-how-to-add-src-files-in-the-middle-of-a-pipe + // https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-multiple-sources-in-one-task.md + return merge(stream, gulp.src(['.tmp/styles/font-awesome.css', '.tmp/styles/lemur.css'], {allowEmpty: true})) + .pipe(concat('style-' + themeName + '.css')); + }))) + .pipe(plumber()) + .pipe(concat('styles.css')) + .pipe(minifycss()) + .pipe(autoprefixer('last 1 version')) + .pipe(gulp.dest('.tmp/styles')) + .pipe(size()); +}); + gulp.task('dev:scripts', function () { - return gulp.src(['lemur/static/app/angular/**/*.js']) - .pipe(jshint()) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(size()); + return gulp.src(['lemur/static/app/angular/**/*.js']) + .pipe(jshint()) + .pipe(jshint.reporter('jshint-stylish')) + .pipe(size()); }); gulp.task('build:extras', function () { - return gulp.src(['lemur/static/app/*.*', '!lemur/static/app/*.html']) - .pipe(gulp.dest('lemur/static/dist')); + return gulp.src(['lemur/static/app/*.*', '!lemur/static/app/*.html']) + .pipe(gulp.dest('lemur/static/dist')); }); function injectHtml(isDev) { - return gulp.src('lemur/static/app/index.html') - .pipe( - inject(gulp.src(bowerFiles({ base: 'app' })), { - starttag: '', - addRootSlash: false, - ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null - }) - ) - .pipe(inject(gulp.src(['lemur/static/app/angular/**/*.js']), { - starttag: '', - addRootSlash: false, - ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null - })) - .pipe(inject(gulp.src(['.tmp/styles/**/*.css']), { - starttag: '', - addRootSlash: false, - ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null - })) - .pipe( - gulpif(!isDev, - inject(gulp.src('lemur/static/dist/ngviews/ngviews.min.js', { allowEmpty: true }), { - starttag: '', - addRootSlash: false - }) - ) - ).pipe(gulp.dest('.tmp/')); + return gulp.src('lemur/static/app/index.html') + .pipe( + inject(gulp.src(bowerFiles({base: 'app'})), { + starttag: '', + addRootSlash: false, + ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null + }) + ) + .pipe(inject(gulp.src(['lemur/static/app/angular/**/*.js']), { + starttag: '', + addRootSlash: false, + ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null + })) + .pipe(inject(gulp.src(['.tmp/styles/**/*.css']), { + starttag: '', + addRootSlash: false, + ignorePath: isDev ? ['lemur/static/app/', '.tmp/'] : null + })) + .pipe( + gulpif(!isDev, + inject(gulp.src('lemur/static/dist/ngviews/ngviews.min.js', {allowEmpty: true}), { + starttag: '', + addRootSlash: false + }) + ) + ).pipe(gulp.dest('.tmp/')); } gulp.task('dev:inject', gulp.series(['dev:styles', 'dev:scripts'], function () { - return injectHtml(true); + return injectHtml(true); })); gulp.task('build:ngviews', function () { - return gulp.src(['lemur/static/app/angular/**/*.html']) - .pipe(minifyHtml({ - empty: true, - spare: true, - quotes: true - })) - .pipe(gulp.dest('lemur/static/dist/angular')) - .pipe(size()); + return gulp.src(['lemur/static/app/angular/**/*.html']) + .pipe(minifyHtml({ + empty: true, + spare: true, + quotes: true + })) + .pipe(gulp.dest('lemur/static/dist/angular')) + .pipe(size()); }); gulp.task('build:inject', gulp.series(['dev:styles', 'dev:scripts', 'build:ngviews'], function () { - return injectHtml(false); + return injectHtml(false); })); gulp.task('build:html', gulp.series(['build:inject'], function () { - let jsFilter = filter(['**/*.js'], {'restore': true}); - let cssFilter = filter(['**/*.css'], {'restore': true}); + let jsFilter = filter(['**/*.js'], {'restore': true}); + let cssFilter = filter(['**/*.css'], {'restore': true}); - return gulp.src('.tmp/index.html') - .pipe(jsFilter) - .pipe(ngAnnotate()) - .pipe(jsFilter.restore) - .pipe(cssFilter) - .pipe(csso()) - .pipe(cssFilter.restore) - .pipe(useref()) - .pipe(gulp.dest('lemur/static/dist')) - .pipe(size()); + return gulp.src('.tmp/index.html') + .pipe(jsFilter) + .pipe(ngAnnotate()) + .pipe(jsFilter.restore) + .pipe(cssFilter) + .pipe(csso()) + .pipe(cssFilter.restore) + .pipe(useref()) + .pipe(gulp.dest('lemur/static/dist')) + .pipe(size()); })); gulp.task('build:fonts', gulp.series(['dev:fonts'], function () { - return gulp.src('.tmp/fonts/**/*') - .pipe(gulp.dest('lemur/static/dist/fonts')); + return gulp.src('.tmp/fonts/**/*') + .pipe(gulp.dest('lemur/static/dist/fonts')); })); gulp.task('build:images', function () { - return gulp.src('lemur/static/app/images/**/*') - .pipe(cache(imagemin({ - optimizationLevel: 3, - progressive: true, - interlaced: true - }))) - .pipe(gulp.dest('lemur/static/dist/images')) - .pipe(size()); + return gulp.src('lemur/static/app/images/**/*') + .pipe(cache(imagemin({ + optimizationLevel: 3, + progressive: true, + interlaced: true + }))) + .pipe(gulp.dest('lemur/static/dist/images')) + .pipe(size()); }); gulp.task('package:strip', function () { - return gulp.src(['lemur/static/dist/scripts/main*']) - .pipe(replace('http:\/\/localhost:3000', '')) - .pipe(replace('http:\/\/localhost:8000', '')) - .pipe(useref()) - .pipe(gulp.dest('lemur/static/dist/scripts')) - .pipe(size()); + return gulp.src(['lemur/static/dist/scripts/main*']) + .pipe(replace('http:\/\/localhost:3000', '')) + .pipe(replace('http:\/\/localhost:8000', '')) + .pipe(useref()) + .pipe(gulp.dest('lemur/static/dist/scripts')) + .pipe(size()); }); -gulp.task('addUrlContextPath:revision', function(){ - return gulp.src(['lemur/static/dist/**/*.css','lemur/static/dist/**/*.js']) - .pipe(rev()) - .pipe(gulp.dest('lemur/static/dist')) - .pipe(rev.manifest()) - .pipe(gulp.dest('lemur/static/dist')) +gulp.task('addUrlContextPath:revision', function () { + return gulp.src(['lemur/static/dist/**/*.css', 'lemur/static/dist/**/*.js']) + .pipe(rev()) + .pipe(gulp.dest('lemur/static/dist')) + .pipe(rev.manifest()) + .pipe(gulp.dest('lemur/static/dist')); }); -gulp.task('addUrlContextPath:revreplace', gulp.series(['addUrlContextPath:revision'], function(){ - return gulp.src( "lemur/static/dist/index.html") - .pipe(gulp.dest('lemur/static/dist')); +gulp.task('addUrlContextPath:revreplace', gulp.series(['addUrlContextPath:revision'], function () { + return gulp.src('lemur/static/dist/index.html') + .pipe(gulp.dest('lemur/static/dist')); })); -gulp.task('addUrlContextPath', gulp.series(['addUrlContextPath:revreplace'], function(){ - let urlContextPathExists = argv.urlContextPath ? true : false; - return gulp.src(['lemur/static/dist/scripts/main*.js', 'lemur/static/dist/angular/**/*.html']) - .pipe(gulpif(urlContextPathExists, replace('api/', argv.urlContextPath + '/api/'))) - .pipe(gulpif(urlContextPathExists, replace('/angular/', '/' + argv.urlContextPath + '/angular/'))) - .pipe(gulp.dest(function(file){ - return file.base; - })) +gulp.task('addUrlContextPath', gulp.series(['addUrlContextPath:revreplace'], function () { + let urlContextPathExists = !!argv.urlContextPath; + return gulp.src(['lemur/static/dist/scripts/main*.js', 'lemur/static/dist/angular/**/*.html']) + .pipe(gulpif(urlContextPathExists, replace('api/', argv.urlContextPath + '/api/'))) + .pipe(gulpif(urlContextPathExists, replace('/angular/', '/' + argv.urlContextPath + '/angular/'))) + .pipe(gulp.dest(function (file) { + return file.base; + })); })); gulp.task('build', gulp.series(['build:images', 'build:fonts', 'build:html', 'build:extras'])); diff --git a/gulp/karma.conf.js b/gulp/karma.conf.js index b9777c7a..4950db36 100644 --- a/gulp/karma.conf.js +++ b/gulp/karma.conf.js @@ -1,27 +1,37 @@ // Contents of: config/karma.conf.js +'use strict'; + module.exports = function (config) { - config.set({ - basePath : '../', + config.set({ + basePath: '../', - // Fix for "JASMINE is not supported anymore" warning - frameworks : ["jasmine"], + // Fix for "JASMINE is not supported anymore" warning + frameworks: ['jasmine'], - files : [ - 'app/lib/angular/angular.js', - 'app/lib/angular/angular-*.js', - 'test/lib/angular/angular-mocks.js', - 'app/js/**/*.js', - 'test/unit/**/*.js' - ], + files: [ + 'app/lib/angular/angular.js', + 'app/lib/angular/angular-*.js', + 'test/lib/angular/angular-mocks.js', + 'app/js/**/*.js', + 'test/unit/**/*.js' + ], - autoWatch : true, + autoWatch: true, - browsers : ['Chrome'], + browsers: [process.env.TRAVIS ? 'Chrome_travis_ci' : 'Chrome'], + customLaunchers: { + 'Chrome_travis_ci': { + base: 'Chrome', + flags: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu',], + }, + }, - junitReporter : { - outputFile : 'test_out/unit.xml', - suite : 'unit' - //... - } - }); + junitReporter: { + outputFile: 'test_out/unit.xml', + suite: 'unit' + //... + }, + + failOnEmptyTestSuite: false, + }); }; \ No newline at end of file diff --git a/package.json b/package.json index f2d87ab7..71421a64 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,10 @@ "gulp-size": "^2.1.0", "gulp-uglify": "^2.0.0", "gulp-useref": "^3.1.2", - "gulp-util": "^3.0.1", "http-proxy": ">=1.18.1", "jshint-stylish": "^2.2.1", - "karma": "^5.2.3", - "karma-jasmine": "^1.1.0", + "karma": "^6.0.3", + "karma-jasmine": "^4.0.1", "main-bower-files": "^2.13.1", "merge-stream": "^1.0.1", "require-dir": "~0.3.0", @@ -59,7 +58,8 @@ }, "devDependencies": { "gulp": "^4.0.2", + "gulp-util": "^3.0.8", "jshint": "^2.11.0", - "karma-chrome-launcher": "^2.0.0" + "karma-chrome-launcher": "^3.1.0" } }