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