diff --git a/.travis.yml b/.travis.yml index bf7e51452250..eb4e5f6d548f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,13 @@ env: - SAUCE_USERNAME=angular-ci - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - SAUCE_CONNECT_READY_FILE=/tmp/sauce-connect-ready + - LOGS_DIR=/tmp/angular-build/logs + +before_script: + - mkdir -p $LOGS_DIR script: - ./travis_build.sh + +after_script: + - ./travis_print_logs.sh diff --git a/Gruntfile.js b/Gruntfile.js index daf5f24edc04..625ab7a31837 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -27,16 +27,9 @@ module.exports = function(grunt) { parallel: { travis: { - options: { - stream: true - }, tasks: [ - {grunt: true, args: ['test:docgen']}, - util.parallelTask('tests:docs'), - util.parallelTask('tests:modules'), - util.parallelTask('tests:jquery'), - util.parallelTask('tests:jqlite'), - util.parallelTask('test:e2e') + util.parallelTask(['test:unit', 'test:docgen', 'tests:docs'], {stream: true}), + util.parallelTask(['test:e2e']) ] } }, diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js index 6dcde906e690..d84bd5bdf57d 100644 --- a/docs/component-spec/annotationsSpec.js +++ b/docs/component-spec/annotationsSpec.js @@ -8,6 +8,10 @@ describe('Docs Annotations', function() { body.html(''); }); + var normalizeHtml = function(html) { + return html.toLowerCase().replace(/\s*$/, ''); + }; + describe('popover directive', function() { var $scope, element; @@ -57,7 +61,7 @@ describe('Docs Annotations', function() { $scope.$apply(); element.triggerHandler('click'); expect(popoverElement.title()).toBe('#title_text'); - expect(popoverElement.content()).toBe('

heading

\n'); + expect(normalizeHtml(popoverElement.content())).toMatch('

heading

'); })); }); @@ -65,6 +69,9 @@ describe('Docs Annotations', function() { describe('foldout directive', function() { + // Do not run this suite on Internet Explorer. + if (msie < 10) return; + var $scope, parent, element, url; beforeEach(function() { module(function($provide, $animateProvider) { diff --git a/docs/component-spec/mocks.js b/docs/component-spec/mocks.js index 143a1f393311..f916c0edee7a 100644 --- a/docs/component-spec/mocks.js +++ b/docs/component-spec/mocks.js @@ -1,3 +1,6 @@ +// Copy/pasted from src/Angular.js, so that we can disable specific tests on IE. +var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1], 10); + var createMockWindow = function() { var mockWindow = {}; var setTimeoutQueue = []; diff --git a/docs/component-spec/versionJumpSpec.js b/docs/component-spec/versionJumpSpec.js index 4db2d29d6e0a..bb43231a3a88 100644 --- a/docs/component-spec/versionJumpSpec.js +++ b/docs/component-spec/versionJumpSpec.js @@ -1,5 +1,8 @@ describe('DocsApp', function() { + // Do not run this suite on Internet Explorer. + if (msie < 10) return; + beforeEach(module('docsApp')); describe('DocsVersionsCtrl', function() { diff --git a/karma-docs.conf.js b/karma-docs.conf.js index e1790214e778..b0ef4e0f2492 100644 --- a/karma-docs.conf.js +++ b/karma-docs.conf.js @@ -1,7 +1,7 @@ var sharedConfig = require('./karma-shared.conf'); module.exports = function(config) { - sharedConfig(config); + sharedConfig(config, {testName: 'AngularJS: docs', logFile: 'karma-docs.log'}); config.set({ files: [ @@ -26,6 +26,7 @@ module.exports = function(config) { 'build/docs/js/docs.js', 'build/docs/docs-data.js', + 'docs/component-spec/mocks.js', 'docs/component-spec/*.js' ], @@ -34,6 +35,4 @@ module.exports = function(config) { suite: 'Docs' } }); - - config.sauceLabs.testName = 'AngularJS: docs'; }; diff --git a/karma-e2e.conf.js b/karma-e2e.conf.js index d9a92e99f3e3..3d839a33ae19 100644 --- a/karma-e2e.conf.js +++ b/karma-e2e.conf.js @@ -1,7 +1,7 @@ var sharedConfig = require('./karma-shared.conf'); module.exports = function(config) { - sharedConfig(config); + sharedConfig(config, {testName: 'AngularJS: e2e', logFile: 'karma-e2e.log'}); config.set({ frameworks: [], @@ -22,6 +22,4 @@ module.exports = function(config) { suite: 'E2E' } }); - - config.sauceLabs.testName = 'AngularJS: e2e'; }; diff --git a/karma-jqlite.conf.js b/karma-jqlite.conf.js index bf190f5eef8b..267e952f5918 100644 --- a/karma-jqlite.conf.js +++ b/karma-jqlite.conf.js @@ -2,7 +2,7 @@ var angularFiles = require('./angularFiles'); var sharedConfig = require('./karma-shared.conf'); module.exports = function(config) { - sharedConfig(config); + sharedConfig(config, {testName: 'AngularJS: jqLite', logFile: 'karma-jqlite.log'}); config.set({ files: angularFiles.mergeFilesFor('karma'), @@ -13,6 +13,4 @@ module.exports = function(config) { suite: 'jqLite' } }); - - config.sauceLabs.testName = 'AngularJS: jqLite'; }; diff --git a/karma-jquery.conf.js b/karma-jquery.conf.js index 126b3e1d297d..4af9508d49d8 100644 --- a/karma-jquery.conf.js +++ b/karma-jquery.conf.js @@ -2,7 +2,7 @@ var angularFiles = require('./angularFiles'); var sharedConfig = require('./karma-shared.conf'); module.exports = function(config) { - sharedConfig(config); + sharedConfig(config, {testName: 'AngularJS: jQuery', logFile: 'karma-jquery.log'}); config.set({ files: angularFiles.mergeFilesFor('karmaJquery'), @@ -13,6 +13,4 @@ module.exports = function(config) { suite: 'jQuery' } }); - - config.sauceLabs.testName = 'AngularJS: jQuery'; }; diff --git a/karma-modules.conf.js b/karma-modules.conf.js index 9bbdec673bb3..ecbaee212643 100644 --- a/karma-modules.conf.js +++ b/karma-modules.conf.js @@ -2,7 +2,7 @@ var angularFiles = require('./angularFiles'); var sharedConfig = require('./karma-shared.conf'); module.exports = function(config) { - sharedConfig(config); + sharedConfig(config, {testName: 'AngularJS: modules', logFile: 'karma-modules.log'}); config.set({ files: angularFiles.mergeFilesFor('karmaModules', 'angularSrcModules'), @@ -12,6 +12,4 @@ module.exports = function(config) { suite: 'modules' } }); - - config.sauceLabs.testName = 'AngularJS: modules'; }; diff --git a/karma-shared.conf.js b/karma-shared.conf.js index 814767088cf8..28978097a9b3 100644 --- a/karma-shared.conf.js +++ b/karma-shared.conf.js @@ -1,4 +1,4 @@ -module.exports = function(config) { +module.exports = function(config, specificOptions) { config.set({ frameworks: ['jasmine'], autoWatch: true, @@ -9,16 +9,60 @@ module.exports = function(config) { // config for Travis CI sauceLabs: { - testName: 'AngularJS', + testName: specificOptions.testName || 'AngularJS', startConnect: false, tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER }, + // For more browsers on Sauce Labs see: + // https://saucelabs.com/docs/platforms/webdriver customLaunchers: { 'SL_Chrome': { base: 'SauceLabs', browserName: 'chrome' + }, + 'SL_Firefox': { + base: 'SauceLabs', + browserName: 'firefox' + }, + 'SL_Safari': { + base: 'SauceLabs', + browserName: 'safari', + platform: 'Mac 10.8', + version: '6' + }, + 'SL_IE_8': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 7', + version: '8' + }, + 'SL_IE_9': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 2008', + version: '9' + }, + 'SL_IE_10': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 2012', + version: '10' } } }); + + + if (process.env.TRAVIS) { + // TODO(vojta): remove once SauceLabs supports websockets. + // This speeds up the capturing a bit, as browsers don't even try to use websocket. + config.transports = ['xhr-polling']; + + // Debug logging into a file, that we print out at the end of the build. + config.loggers.push({ + type: 'file', + filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log'), + level: config.LOG_DEBUG + }); + } }; diff --git a/lib/grunt/utils.js b/lib/grunt/utils.js index 97e7c416f91b..ed61a1815de6 100644 --- a/lib/grunt/utils.js +++ b/lib/grunt/utils.js @@ -246,10 +246,18 @@ module.exports = { }; }, - parallelTask: function(name) { - var args = [name, '--port=' + this.lastParallelTaskPort]; + parallelTask: function(args, options) { + var task = { + grunt: true, + args: args, + stream: options && options.stream + }; + + args.push('--port=' + this.lastParallelTaskPort); - if (grunt.option('browsers')) { + if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) { + args.push('--browsers=' + grunt.option('e2e-browsers')); + } else if (grunt.option('browsers')) { args.push('--browsers=' + grunt.option('browsers')); } @@ -259,8 +267,7 @@ module.exports = { this.lastParallelTaskPort++; - - return {grunt: true, args: args}; + return task; }, lastParallelTaskPort: 9876 diff --git a/lib/sauce/sauce_connect_setup.sh b/lib/sauce/sauce_connect_setup.sh index 7dfb6ba5a438..61ab9e2de835 100755 --- a/lib/sauce/sauce_connect_setup.sh +++ b/lib/sauce/sauce_connect_setup.sh @@ -15,13 +15,16 @@ set -e CONNECT_URL="http://saucelabs.com/downloads/Sauce-Connect-latest.zip" CONNECT_DIR="/tmp/sauce-connect-$RANDOM" CONNECT_DOWNLOAD="Sauce_Connect.zip" -CONNECT_LOG="$CONNECT_DIR/log" + +CONNECT_LOG="$LOGS_DIR/sauce-connect" +CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout" +CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr" # Get Connect and start it mkdir -p $CONNECT_DIR cd $CONNECT_DIR -curl $CONNECT_URL > $CONNECT_DOWNLOAD 2> /dev/null -unzip $CONNECT_DOWNLOAD +curl $CONNECT_URL -o $CONNECT_DOWNLOAD 2> /dev/null 1> /dev/null +unzip $CONNECT_DOWNLOAD > /dev/null rm $CONNECT_DOWNLOAD @@ -36,6 +39,10 @@ if [ ! -z "$SAUCE_CONNECT_READY_FILE" ]; then ARGS="$ARGS --readyfile $SAUCE_CONNECT_READY_FILE" fi -echo "Starting Sauce Connect in the background" -echo "Logging into $CONNECT_LOG" -java -jar Sauce-Connect.jar $ARGS $SAUCE_USERNAME $SAUCE_ACCESS_KEY > $CONNECT_LOG & + +echo "Starting Sauce Connect in the background, logging into:" +echo " $CONNECT_LOG" +echo " $CONNECT_STDOUT" +echo " $CONNECT_STDERR" +java -jar Sauce-Connect.jar $ARGS $SAUCE_USERNAME $SAUCE_ACCESS_KEY \ + --logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT & diff --git a/package.json b/package.json index f49fb738a6ef..4c98ec968b1e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "marked": "~0.2.9", "rewire": "1.1.3", "grunt-contrib-jasmine-node": "~0.1.1", - "grunt-parallel": "~0.3.0", + "grunt-parallel": "vojtajina/grunt-parallel#streaming-per-task", "grunt-ddescribe-iit": "~0.0.1", "grunt-merge-conflict": "~0.0.1" }, diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 415ff25dc718..344e2ead3c11 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -88,9 +88,9 @@ describe('angular', function() { expect(dst.a).not.toBe(src.a); }); - it("should deeply copy an object into an existing object", function() { + it("should deeply copy an object into a non-existing object", function() { var src = {a:{name:"value"}}; - var dst = copy(src, dst); + var dst = copy(src, undefined); expect(src).toEqual({a:{name:"value"}}); expect(dst).toEqual(src); expect(dst).not.toBe(src); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 844511a3bb3d..13604f6fa131 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -1488,16 +1488,6 @@ describe('$compile', function() { ); - it('should not allow more then one isolate scope creation per element', inject( - function($rootScope, $compile) { - expect(function(){ - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' + - '
'); - }) - ); - - it('should create new scope even at the root of the template', inject( function($rootScope, $compile, log) { element = $compile('
')($rootScope); @@ -1824,24 +1814,6 @@ describe('$compile', function() { }); }); - it('should allow setting of attributes', function() { - module(function() { - directive({ - setter: valueFn(function(scope, element, attr) { - attr.$set('name', 'abc'); - attr.$set('disabled', true); - expect(attr.name).toBe('abc'); - expect(attr.disabled).toBe(true); - }) - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.attr('name')).toEqual('abc'); - expect(element.attr('disabled')).toEqual('disabled'); - }); - }); - it('should create new instance of attr for each template stamping', function() { module(function($provide) { @@ -2055,32 +2027,15 @@ describe('$compile', function() { $rootScope.name = 'misko'; $rootScope.$apply(); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - })); - - - it('should update local when origin changes', inject(function() { - compile('
'); - expect(componentScope.ref).toBe(undefined); - expect(componentScope.refAlias).toBe(componentScope.ref); - componentScope.ref = 'misko'; - $rootScope.$apply(); expect($rootScope.name).toBe('misko'); expect(componentScope.ref).toBe('misko'); - expect($rootScope.name).toBe(componentScope.ref); - expect(componentScope.refAlias).toBe(componentScope.ref); + expect(componentScope.refAlias).toBe('misko'); - componentScope.name = {}; + $rootScope.name = {}; $rootScope.$apply(); - expect($rootScope.name).toBe(componentScope.ref); - expect(componentScope.refAlias).toBe(componentScope.ref); + expect(componentScope.ref).toBe($rootScope.name); + expect(componentScope.refAlias).toBe($rootScope.name); })); @@ -3379,7 +3334,7 @@ describe('$compile', function() { })); - it('should group on nested groups', inject(function($compile, $rootScope) { + it('should group on nested groups of same directive', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + @@ -3427,7 +3382,7 @@ describe('$compile', function() { }); - it('should throw error if unterminated', function () { + it('should throw error if unterminated (containing termination as a child)', function () { module(function($compileProvider) { $compileProvider.directive('foo', function() { return { diff --git a/test/ng/httpBackendSpec.js b/test/ng/httpBackendSpec.js index c65ab2e17c5b..d533d5f8bf7b 100644 --- a/test/ng/httpBackendSpec.js +++ b/test/ng/httpBackendSpec.js @@ -376,7 +376,7 @@ describe('$httpBackend', function() { }); - it('should convert 0 to 200 if content - relative url', function() { + it('should convert 0 to 404 if no content - relative url', function() { $backend = createHttpBackend($browser, MockXhr, null, null, null, 'file'); $backend('GET', '/whatever/index.html', null, callback); diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 7673066bf7b6..a82f736f2c56 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -717,17 +717,6 @@ describe('parser', function() { }); - it('should call the function once when it is not part of the context', function() { - var count = 0; - scope.fn = function() { - count++; - return function() { return 'lucas'; }; - }; - expect(scope.$eval('fn()()')).toBe('lucas'); - expect(count).toBe(1); - }); - - it('should call the function once when it is part of the context on assignments', function() { var count = 0; var element = {}; @@ -766,7 +755,7 @@ describe('parser', function() { }); - it('should call the function once when it is part of the context on array lookup function', function() { + it('should call the function once when it is part of the context on property lookup function', function() { var count = 0; var element = {name: {anotherFn: function() { return 'lucas';} } }; scope.fn = function() { diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index 13c51f4b0f9b..7c949602f092 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -1467,15 +1467,6 @@ describe('q', function() { }); - it('should still reject the promise, when exception is thrown in success handler, even if exceptionHandler rethrows', function() { - deferred.promise.then(function() { throw 'reject'; }).then(null, errorSpy); - deferred.resolve('resolve'); - mockNextTick.flush(); - expect(exceptionExceptionSpy).toHaveBeenCalled(); - expect(errorSpy).toHaveBeenCalled(); - }); - - it('should still reject the promise, when exception is thrown in success handler, even if exceptionHandler rethrows', function() { deferred.promise.then(null, function() { throw 'reject again'; }).then(null, errorSpy); deferred.reject('reject'); diff --git a/test/ng/sceSpecs.js b/test/ng/sceSpecs.js index 75c1fbaa156b..1eb382f6a887 100644 --- a/test/ng/sceSpecs.js +++ b/test/ng/sceSpecs.js @@ -139,10 +139,6 @@ describe('SCE', function() { expect($sce.trustAsHtml("")).toBe(""); })); - it('should unwrap null into null', inject(function($sce) { - expect($sce.getTrusted($sce.HTML, null)).toBe(null); - })); - it('should unwrap "" into ""', inject(function($sce) { expect($sce.getTrusted($sce.HTML, "")).toBe(""); })); diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index e18735357caa..13e08a681da9 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -168,7 +168,7 @@ describe('ngMock', function() { $log.reset(); })); - it("should skip debugging output if disabled", inject(function($log) { + it("should skip debugging output if disabled (" + debugEnabled + ")", inject(function($log) { $log.log('fake log'); $log.info('fake log'); $log.warn('fake log'); @@ -197,19 +197,19 @@ describe('ngMock', function() { $log.reset(); })); - it('should provide the debug method', function() { + it('should provide the log method', function() { expect(function() { $log.log(''); }).not.toThrow(); }); - it('should provide the debug method', function() { + it('should provide the info method', function() { expect(function() { $log.info(''); }).not.toThrow(); }); - it('should provide the debug method', function() { + it('should provide the warn method', function() { expect(function() { $log.warn(''); }).not.toThrow(); }); - it('should provide the debug method', function() { + it('should provide the error method', function() { expect(function() { $log.error(''); }).not.toThrow(); }); diff --git a/test/ngResource/resourceSpec.js b/test/ngResource/resourceSpec.js index d971e8e7db80..c49ac9e0acfe 100644 --- a/test/ngResource/resourceSpec.js +++ b/test/ngResource/resourceSpec.js @@ -797,7 +797,7 @@ describe("resource", function() { }); - it('should call the error callback if provided on non 2xx response', function() { + it('should call the error callback if provided on non 2xx response (without data)', function() { $httpBackend.expect('GET', '/CreditCard').respond(ERROR_CODE, ERROR_RESPONSE); CreditCard.get(callback, errorCB); diff --git a/travis_build.sh b/travis_build.sh index e2f28e204b83..627ece063273 100755 --- a/travis_build.sh +++ b/travis_build.sh @@ -7,4 +7,7 @@ export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev` npm install -g grunt-cli grunt ci-checks package ./lib/sauce/sauce_connect_block.sh -grunt parallel:travis --reporters dots --browsers SL_Chrome + +grunt parallel:travis --reporters dots \ + --browsers SL_Chrome,SL_Firefox,SL_Safari,SL_IE_8,SL_IE_9,SL_IE_10 \ + --e2e-browsers SL_Chrome diff --git a/travis_print_logs.sh b/travis_print_logs.sh new file mode 100755 index 000000000000..ec612ca177fc --- /dev/null +++ b/travis_print_logs.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +LOG_FILES=$LOGS_DIR/* + +for FILE in $LOG_FILES; do + echo -e "\n\n\n" + echo "================================================================================" + echo " $FILE" + echo "================================================================================" + cat $FILE +done