From 94743fda880835e102074e91b591967281904293 Mon Sep 17 00:00:00 2001 From: brandonocasey Date: Thu, 11 Mar 2021 16:22:59 -0500 Subject: [PATCH 1/4] chore: get test coverage working again --- .github/workflows/ci.yml | 2 +- package-lock.json | 6 +- package.json | 3 +- scripts/karma.conf.js | 4 +- scripts/rollup.config.js | 15 +- test/loader-common.js | 2 +- test/master-playlist-controller.test.js | 13 +- test/segment-loader.test.js | 499 ++++++++++++------------ test/test-helpers.js | 17 +- test/videojs-http-streaming.test.js | 64 +-- 10 files changed, 330 insertions(+), 295 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b1344dd5..14020cc6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-18.04] - test-type: [unit, playback, playback-min] + test-type: [unit, playback, playback-min, coverage] env: BROWSER_STACK_USERNAME: ${{secrets.BROWSER_STACK_USERNAME}} BROWSER_STACK_ACCESS_KEY: ${{secrets.BROWSER_STACK_ACCESS_KEY}} diff --git a/package-lock.json b/package-lock.json index ac41df4f2..705062e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7627,9 +7627,9 @@ } }, "rollup-plugin-worker-factory": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-worker-factory/-/rollup-plugin-worker-factory-0.5.1.tgz", - "integrity": "sha512-DyNB7XSNcq8USMU3fE1JW8eoadv0vGTa/BK041vHqhY2pLaUEHik2LXQ6BflmjWq8yiiBztJD6fOqpRtybVuFg==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-worker-factory/-/rollup-plugin-worker-factory-0.5.3.tgz", + "integrity": "sha512-az67NoTmWLKxnO8EZlJ63J3GzVIYGkZ9cV9I44h2iXIc5wDZDprx4zH5jDF+dFXENcopVYRxqOJQxJPnv5R73A==", "dev": true, "requires": { "rollup": "^2.34.2" diff --git a/package.json b/package.json index c7b517e3b..ff1f3cf14 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "start": "npm-run-all -p server watch", "server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch", "test": "npm-run-all lint build-test && karma start scripts/karma.conf.js", + "posttest": "[ \"$CI_TEST_TYPE\" != 'coverage' ] || shx cat test/dist/coverage/text.txt", "version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md", "update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s", "watch": "npm-run-all -p watch:*", @@ -76,7 +77,7 @@ "lodash-compat": "^3.10.0", "nomnoml": "^0.3.0", "rollup": "^2.36.1", - "rollup-plugin-worker-factory": "^0.5.1", + "rollup-plugin-worker-factory": "^0.5.3", "shelljs": "^0.8.4", "sinon": "^8.1.1", "url-toolkit": "^2.2.1", diff --git a/scripts/karma.conf.js b/scripts/karma.conf.js index 830f4801d..ea7027f67 100644 --- a/scripts/karma.conf.js +++ b/scripts/karma.conf.js @@ -1,11 +1,13 @@ const generate = require('videojs-generate-karma-config'); +const CI_TEST_TYPE = process.env.CI_TEST_TYPE || ''; module.exports = function(config) { // see https://github.com/videojs/videojs-generate-karma-config // for options const options = { - coverage: false, + coverage: CI_TEST_TYPE === 'coverage' ? true : false, + preferHeadless: false, browsers(aboutToRun) { return aboutToRun.filter(function(launcherName) { diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index 742c6c5ca..9a5e1c388 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -12,6 +12,13 @@ let syncWorker; const options = { input: 'src/videojs-http-streaming.js', distName: 'videojs-http-streaming', + checkWatch: false, + excludeCoverage(defaults) { + defaults.push(/^rollup-plugin-worker-factory/); + defaults.push(/^create-test-data!/); + + return defaults; + }, globals(defaults) { defaults.browser.xmldom = 'window'; defaults.test.xmldom = 'window'; @@ -34,7 +41,11 @@ const options = { defaults.browser.unshift('worker'); // change this to `syncWorker` for syncronous web worker // during unit tests - defaults.test.unshift('worker'); + if (CI_TEST_TYPE === 'coverage') { + defaults.test.unshift('syncWorker'); + } else { + defaults.test.unshift('worker'); + } defaults.test.unshift('createTestData'); if (CI_TEST_TYPE === 'playback-min') { @@ -42,7 +53,7 @@ const options = { } // istanbul is only in the list for regular builds and not watch - if (defaults.test.indexOf('istanbul') !== -1) { + if (CI_TEST_TYPE !== 'coverage' && defaults.test.indexOf('istanbul') !== -1) { defaults.test.splice(defaults.test.indexOf('istanbul'), 1); } defaults.module.unshift('replace'); diff --git a/test/loader-common.js b/test/loader-common.js index 94238dd52..874e9da3b 100644 --- a/test/loader-common.js +++ b/test/loader-common.js @@ -192,7 +192,7 @@ export const LoaderCommonFactory = ({ if (usesAsyncAppends) { return new Promise((resolve, reject) => { - loader.one('appending', loader.pause); + loader.one('appended', loader.pause); loader.one('appended', resolve); loader.one('error', reject); }); diff --git a/test/master-playlist-controller.test.js b/test/master-playlist-controller.test.js index f033eae79..54bc35677 100644 --- a/test/master-playlist-controller.test.js +++ b/test/master-playlist-controller.test.js @@ -832,9 +832,9 @@ QUnit.test('if buffered, will request second segment byte range', function(asser }; this.clock.tick(1); // segment - this.standardXHRResponse(this.requests[1], muxedSegment()); return new Promise((resolve, reject) => { this.masterPlaylistController.mainSegmentLoader_.on('appending', resolve); + this.standardXHRResponse(this.requests[1], muxedSegment()); }).then(() => { this.masterPlaylistController.mainSegmentLoader_.fetchAtBuffer_ = true; // source buffers are mocked, so must manually trigger update ends on audio and video @@ -1114,11 +1114,11 @@ QUnit.test('Segment loaders are unpaused when seeking after player has ended', f // media this.standardXHRResponse(this.requests.shift(), videoMedia); - // segment - this.standardXHRResponse(this.requests.shift(), muxedSegment()); - return new Promise((resolve, reject) => { this.masterPlaylistController.mainSegmentLoader_.one('appending', resolve); + + // segment + this.standardXHRResponse(this.requests.shift(), muxedSegment()); }).then(() => { assert.notOk( this.masterPlaylistController.mainSegmentLoader_.paused(), @@ -2125,11 +2125,12 @@ QUnit.test('updates the duration after switching playlists', function(assert) { // 1ms for request duration this.clock.tick(1); this.masterPlaylistController.mainSegmentLoader_.mediaIndex = 0; - // segment 0 - this.standardXHRResponse(this.requests[2], segment); return new Promise((resolve, reject) => { this.masterPlaylistController.mainSegmentLoader_.on('appending', resolve); + + // segment 0 + this.standardXHRResponse(this.requests[2], segment); }).then(() => { // source buffers are mocked, so must manually trigger update ends on audio and video // buffers diff --git a/test/segment-loader.test.js b/test/segment-loader.test.js index 746c7175f..e30edfd9d 100644 --- a/test/segment-loader.test.js +++ b/test/segment-loader.test.js @@ -883,32 +883,35 @@ QUnit.module('SegmentLoader', function(hooks) { loader.cacheEncryptionKeys_ = true; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20, { isEncrypted: true })); + return new Promise((resolve, reject) => { + loader.one('appended', resolve); + loader.one('error', reject); + loader.playlist(playlistWithDuration(20, { isEncrypted: true })); - // make the keys the same - loader.playlist_.segments[1].key = - videojs.mergeOptions({}, loader.playlist_.segments[0].key); - // give 2nd key an iv - loader.playlist_.segments[1].key.iv = new Uint32Array([0, 1, 2, 3]); + // make the keys the same + loader.playlist_.segments[1].key = + videojs.mergeOptions({}, loader.playlist_.segments[0].key); + // give 2nd key an iv + loader.playlist_.segments[1].key.iv = new Uint32Array([0, 1, 2, 3]); - loader.load(); - this.clock.tick(1); + loader.load(); + this.clock.tick(1); - assert.strictEqual(this.requests.length, 2, 'one request'); - assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request'); - assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request'); + assert.strictEqual(this.requests.length, 2, 'one request'); + assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request'); + assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request'); - // key response - standardXHRResponse(this.requests.shift(), encryptionKey()); - this.clock.tick(1); + // key response + standardXHRResponse(this.requests.shift(), encryptionKey()); + this.clock.tick(1); - // segment - standardXHRResponse(this.requests.shift(), encryptedSegment()); - this.clock.tick(1); + // segment + standardXHRResponse(this.requests.shift(), encryptedSegment()); + this.clock.tick(1); + + // decryption tick for syncWorker + this.clock.tick(1); - return new Promise((resolve, reject) => { - loader.one('appended', resolve); - loader.one('error', reject); }); }).then(() => { assert.deepEqual(loader.keyCache_['0-key.php'], { @@ -930,26 +933,29 @@ QUnit.module('SegmentLoader', function(hooks) { QUnit.test('new segment request keys every time', function(assert) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20, { isEncrypted: true })); + return new Promise((resolve, reject) => { + loader.one('appended', resolve); + loader.one('error', reject); + loader.playlist(playlistWithDuration(20, { isEncrypted: true })); - loader.load(); - this.clock.tick(1); + loader.load(); + this.clock.tick(1); - assert.strictEqual(this.requests.length, 2, 'one request'); - assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request'); - assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request'); + assert.strictEqual(this.requests.length, 2, 'one request'); + assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request'); + assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request'); - // key response - standardXHRResponse(this.requests.shift(), encryptionKey()); - this.clock.tick(1); + // key response + standardXHRResponse(this.requests.shift(), encryptionKey()); + this.clock.tick(1); - // segment - standardXHRResponse(this.requests.shift(), encryptedSegment()); - this.clock.tick(1); + // segment + standardXHRResponse(this.requests.shift(), encryptedSegment()); + this.clock.tick(1); + + // decryption tick for syncWorker + this.clock.tick(1); - return new Promise((resolve, reject) => { - loader.one('appended', resolve); - loader.one('error', reject); }); }).then(() => { this.clock.tick(1); @@ -1008,19 +1014,19 @@ QUnit.module('SegmentLoader', function(hooks) { const done = assert.async(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20)); - loader.load(); - this.clock.tick(1); - - standardXHRResponse(this.requests.shift(), muxedSegment()); - loader.one('appending', () => { loader.abort(); this.clock.tick(1); assert.equal(loader.state, 'APPENDING', 'still appending'); done(); - }); + + loader.playlist(playlistWithDuration(20)); + loader.load(); + this.clock.tick(1); + + standardXHRResponse(this.requests.shift(), muxedSegment()); + }); }); @@ -1046,11 +1052,6 @@ QUnit.module('SegmentLoader', function(hooks) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { let appendsdone = false; - loader.playlist(playlistWithDuration(20)); - loader.load(); - this.clock.tick(1); - - standardXHRResponse(this.requests.shift(), muxedSegment()); loader.one('appendsdone', () => { appendsdone = true; }); @@ -1073,6 +1074,13 @@ QUnit.module('SegmentLoader', function(hooks) { loader.sourceUpdater_.videoQueueCallback(finish); loader.sourceUpdater_.audioQueueCallback(finish); }); + + loader.playlist(playlistWithDuration(20)); + loader.load(); + this.clock.tick(1); + + standardXHRResponse(this.requests.shift(), muxedSegment()); + }); }); @@ -1092,15 +1100,15 @@ QUnit.module('SegmentLoader', function(hooks) { playlist.segments[1].timeline = 1; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // segment 0 - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // segment 0 + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { this.clock.tick(1); @@ -1158,15 +1166,16 @@ QUnit.module('SegmentLoader', function(hooks) { playlist.segments[1].timeline = 1; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // segment 0 - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // segment 0 + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { this.clock.tick(1); @@ -1197,15 +1206,15 @@ QUnit.module('SegmentLoader', function(hooks) { playlist.segments[1].timeline = 1; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // segment 0 - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // segment 0 + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { this.clock.tick(1); @@ -1238,15 +1247,15 @@ QUnit.module('SegmentLoader', function(hooks) { playlist.segments[1].timeline = 1; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // segment 0 - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // segment 0 + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { this.clock.tick(1); @@ -1524,29 +1533,30 @@ QUnit.module('SegmentLoader', function(hooks) { this.fakeMainTimelineChange(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20)); - loader.load(); - this.clock.tick(1); - - assert.deepEqual( - this.timelineChangeController.pendingTimelineChange({ type: 'audio' }), - { - type: 'audio', - from: -1, - to: 0 - }, - 'added pending timeline change for audio' - ); - assert.notOk( - this.timelineChangeController.lastTimelineChange({ type: 'audio' }), - 'no timeline change for audio yet' - ); - - // segment 0 - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + + loader.playlist(playlistWithDuration(20)); + loader.load(); + this.clock.tick(1); + + assert.deepEqual( + this.timelineChangeController.pendingTimelineChange({ type: 'audio' }), + { + type: 'audio', + from: -1, + to: 0 + }, + 'added pending timeline change for audio' + ); + assert.notOk( + this.timelineChangeController.lastTimelineChange({ type: 'audio' }), + 'no timeline change for audio yet' + ); + + // segment 0 + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { assert.deepEqual( @@ -1780,16 +1790,16 @@ QUnit.module('SegmentLoader', function(hooks) { const syncController = loader.syncController_; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20)); - loader.load(); - this.clock.tick(1); - standardXHRResponse(this.requests.shift(), audioSegment()); - - assert.notOk(syncController.mappingForTimeline(0), 'no mapping for timeline 0'); - return new Promise((resolve, reject) => { + loader.one('appended', resolve); loader.one('error', reject); + loader.playlist(playlistWithDuration(20)); + loader.load(); + this.clock.tick(1); + standardXHRResponse(this.requests.shift(), audioSegment()); + + assert.notOk(syncController.mappingForTimeline(0), 'no mapping for timeline 0'); }); }).then(() => { assert.notOk(syncController.mappingForTimeline(0), 'no mapping for timeline 0'); @@ -1910,25 +1920,6 @@ QUnit.module('SegmentLoader', function(hooks) { const addCueSpy = sinon.spy(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.inbandTextTracks_ = {}; - loader.playlist(playlistWithDuration(20)); - loader.load(); - // set the mediaSource duration as it is usually set by - // master playlist controller, which is not present here - loader.mediaSource_.duration = 20; - - this.clock.tick(1); - - // Mock text tracks and addRemoteTextTrack on the mock tech - sinon.stub(loader.vhs_.tech_, 'addRemoteTextTrack') - .returns({ - track: { - addCue: addCueSpy - } - }); - - standardXHRResponse(this.requests.shift(), muxedSegment()); - loader.on('appending', () => { // Simulate an id3Frame event happening that will call handleId3_ loader.handleId3_(loader.pendingSegment_, metadataCues, dispatchType); @@ -1949,6 +1940,26 @@ QUnit.module('SegmentLoader', function(hooks) { ); done(); }); + + loader.inbandTextTracks_ = {}; + loader.playlist(playlistWithDuration(20)); + loader.load(); + // set the mediaSource duration as it is usually set by + // master playlist controller, which is not present here + loader.mediaSource_.duration = 20; + + this.clock.tick(1); + + // Mock text tracks and addRemoteTextTrack on the mock tech + sinon.stub(loader.vhs_.tech_, 'addRemoteTextTrack') + .returns({ + track: { + addCue: addCueSpy + } + }); + + standardXHRResponse(this.requests.shift(), muxedSegment()); + }); }); @@ -1964,6 +1975,20 @@ QUnit.module('SegmentLoader', function(hooks) { const addCueSpy = sinon.spy(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { + loader.on('appending', () => { + // Simulate a caption event happening that will call handleCaptions_ + loader.handleCaptions_(loader.pendingSegment_, captions); + }); + + loader.on('appended', () => { + assert.ok( + Object.keys(loader.inbandTextTracks_.CC1), + 'created one text track with the caption stream as the id' + ); + assert.strictEqual(addCueSpy.callCount, 1, 'created one cue'); + done(); + }); + loader.playlist(playlistWithDuration(20)); loader.load(); @@ -1983,19 +2008,6 @@ QUnit.module('SegmentLoader', function(hooks) { standardXHRResponse(this.requests.shift(), muxedSegment()); - loader.on('appending', () => { - // Simulate a caption event happening that will call handleCaptions_ - loader.handleCaptions_(loader.pendingSegment_, captions); - }); - - loader.on('appended', () => { - assert.ok( - Object.keys(loader.inbandTextTracks_.CC1), - 'created one text track with the caption stream as the id' - ); - assert.strictEqual(addCueSpy.callCount, 1, 'created one cue'); - done(); - }); }); }); @@ -2012,25 +2024,6 @@ QUnit.module('SegmentLoader', function(hooks) { const addCueSpy = sinon.spy(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - loader.playlist(playlistWithDuration(20)); - loader.load(); - - this.clock.tick(1); - - // Mock text tracks on the mock tech and setup the inbandTextTracks - loader.inbandTextTracks_ = {}; - textTrackStub.returns({ - getTrackById: () => null - }); - sinon.stub(loader.vhs_.tech_, 'addRemoteTextTrack') - .returns({ - track: { - addCue: addCueSpy - } - }); - - standardXHRResponse(this.requests.shift(), audioSegment()); - loader.on('appending', () => { // Simulate a caption event happening that will call handleCaptions_ const dispatchType = 0x10; @@ -2055,6 +2048,24 @@ QUnit.module('SegmentLoader', function(hooks) { done(); }); + loader.playlist(playlistWithDuration(20)); + loader.load(); + + this.clock.tick(1); + + // Mock text tracks on the mock tech and setup the inbandTextTracks + loader.inbandTextTracks_ = {}; + textTrackStub.returns({ + getTrackById: () => null + }); + sinon.stub(loader.vhs_.tech_, 'addRemoteTextTrack') + .returns({ + track: { + addCue: addCueSpy + } + }); + + standardXHRResponse(this.requests.shift(), audioSegment()); }); }); @@ -2276,19 +2287,18 @@ QUnit.module('SegmentLoader', function(hooks) { const errors = []; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { + return new Promise((resolve, reject) => { + loader.one('appended', resolve); - const playlist = playlistWithDuration(40); + const playlist = playlistWithDuration(40); - loader.on('error', () => errors.push(loader.error())); + loader.on('error', () => errors.push(loader.error())); - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); - standardXHRResponse(this.requests.shift(), muxedSegment()); - return new Promise((resolve, reject) => { - loader.one('appended', resolve); - loader.one('error', reject); + standardXHRResponse(this.requests.shift(), muxedSegment()); }); }).then(() => { this.clock.tick(1); @@ -2313,20 +2323,21 @@ QUnit.module('SegmentLoader', function(hooks) { const errors = []; return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { + return new Promise((resolve, reject) => { - const playlist = playlistWithDuration(40); + loader.one('appended', resolve); + loader.one('error', reject); - loader.on('error', () => errors.push(loader.error())); + const playlist = playlistWithDuration(40); - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); + loader.on('error', () => errors.push(loader.error())); - standardXHRResponse(this.requests.shift(), audioSegment()); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + standardXHRResponse(this.requests.shift(), audioSegment()); - return new Promise((resolve, reject) => { - loader.one('appended', resolve); - loader.one('error', reject); }); }).then(() => { this.clock.tick(1); @@ -2597,24 +2608,25 @@ QUnit.module('SegmentLoader', function(hooks) { QUnit.test('triggers appenderror when append errors', function(assert) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - const playlist = playlistWithDuration(40); + return new Promise((resolve, reject) => { + loader.one('appenderror', resolve); + loader.one('error', reject); - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); + const playlist = playlistWithDuration(40); - // mocking in this case because it's hard to find a good append error that will - // 1) work across browsers - // 2) won't cause an error in the transmuxer first - loader.sourceUpdater_.appendBuffer = ({type, bytes}, callback) => { - callback({type: 'error'}); - }; + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); - standardXHRResponse(this.requests.shift(), muxedSegment()); + // mocking in this case because it's hard to find a good append error that will + // 1) work across browsers + // 2) won't cause an error in the transmuxer first + loader.sourceUpdater_.appendBuffer = ({type, bytes}, callback) => { + callback({type: 'error'}); + }; + + standardXHRResponse(this.requests.shift(), muxedSegment()); - return new Promise((resolve, reject) => { - loader.one('appenderror', resolve); - loader.one('error', reject); }); }).then(() => { assert.deepEqual( @@ -3133,54 +3145,55 @@ QUnit.module('SegmentLoader', function(hooks) { }; return setupMediaSource(loader.mediaSource_, sourceUpdater).then(() => { - const origAppendToSourceBuffer = loader.appendToSourceBuffer_.bind(loader); - const origAudioTimestampOffset = - sourceUpdater.audioTimestampOffset.bind(sourceUpdater); - const origVideoTimestampOffset = - sourceUpdater.videoTimestampOffset.bind(sourceUpdater); - const origTransmuxerPostMessage = - loader.transmuxer_.postMessage.bind(loader.transmuxer_); - - // Keep track of appends and changes in timestamp offset to verify the right - // number of each were set. - loader.appendToSourceBuffer_ = (config) => { - appends.push(config); - origAppendToSourceBuffer(config); - }; - sourceUpdater.audioTimestampOffset = (offset) => { - if (!offset) { - return audioTimestampOffsets.length ? - audioTimestampOffsets[audioTimestampOffsets.length - 1] : -1; - } - audioTimestampOffsets.push(offset); - origAudioTimestampOffset(offset); - }; - sourceUpdater.videoTimestampOffset = (offset) => { - if (!offset) { - return videoTimestampOffsets.length ? - videoTimestampOffsets[videoTimestampOffsets.length - 1] : -1; - } - videoTimestampOffsets.push(offset); - origVideoTimestampOffset(offset); - }; - loader.transmuxer_.postMessage = (message) => { - if (message.action === 'setTimestampOffset') { - transmuxerTimestampOffsets.push(message.timestampOffset); - } - origTransmuxerPostMessage(message); - }; - - // Load the playlist and the zero length segment. Note that the zero length - // segment is the first loaded segment, as it's an easy case for when a timestamp - // offset should be set, except in this case, when the first segment has no audio - // or video data. - loader.playlist(playlistWithDuration(20)); - loader.load(); - this.clock.tick(1); - standardXHRResponse(this.requests.shift(), zeroLengthSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + + const origAppendToSourceBuffer = loader.appendToSourceBuffer_.bind(loader); + const origAudioTimestampOffset = + sourceUpdater.audioTimestampOffset.bind(sourceUpdater); + const origVideoTimestampOffset = + sourceUpdater.videoTimestampOffset.bind(sourceUpdater); + const origTransmuxerPostMessage = + loader.transmuxer_.postMessage.bind(loader.transmuxer_); + + // Keep track of appends and changes in timestamp offset to verify the right + // number of each were set. + loader.appendToSourceBuffer_ = (config) => { + appends.push(config); + origAppendToSourceBuffer(config); + }; + sourceUpdater.audioTimestampOffset = (offset) => { + if (!offset) { + return audioTimestampOffsets.length ? + audioTimestampOffsets[audioTimestampOffsets.length - 1] : -1; + } + audioTimestampOffsets.push(offset); + origAudioTimestampOffset(offset); + }; + sourceUpdater.videoTimestampOffset = (offset) => { + if (!offset) { + return videoTimestampOffsets.length ? + videoTimestampOffsets[videoTimestampOffsets.length - 1] : -1; + } + videoTimestampOffsets.push(offset); + origVideoTimestampOffset(offset); + }; + loader.transmuxer_.postMessage = (message) => { + if (message.action === 'setTimestampOffset') { + transmuxerTimestampOffsets.push(message.timestampOffset); + } + origTransmuxerPostMessage(message); + }; + + // Load the playlist and the zero length segment. Note that the zero length + // segment is the first loaded segment, as it's an easy case for when a timestamp + // offset should be set, except in this case, when the first segment has no audio + // or video data. + loader.playlist(playlistWithDuration(20)); + loader.load(); + this.clock.tick(1); + standardXHRResponse(this.requests.shift(), zeroLengthSegment()); }); }).then(() => { assert.equal(appends.length, 0, 'zero appends'); @@ -3421,17 +3434,18 @@ QUnit.module('SegmentLoader', function(hooks) { QUnit.test('main buffered uses audio buffer when audio only', function(assert) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - const playlist = playlistWithDuration(40); - - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // need to load content to have starting media - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + + const playlist = playlistWithDuration(40); + + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // need to load content to have starting media + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { // mock the buffered values (easiest solution to test that segment-loader is @@ -3462,17 +3476,18 @@ QUnit.module('SegmentLoader', function(hooks) { this.fakeMainTimelineChange(); return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - const playlist = playlistWithDuration(40); - - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - // need to load content to have starting media - standardXHRResponse(this.requests.shift(), audioSegment()); return new Promise((resolve, reject) => { loader.one('appended', resolve); loader.one('error', reject); + + const playlist = playlistWithDuration(40); + + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); + + // need to load content to have starting media + standardXHRResponse(this.requests.shift(), audioSegment()); }); }).then(() => { // mock the buffered values (easiest solution to test that segment-loader is diff --git a/test/test-helpers.js b/test/test-helpers.js index a88e42b74..2c3c39c9e 100644 --- a/test/test-helpers.js +++ b/test/test-helpers.js @@ -7,7 +7,6 @@ import xhrFactory from '../src/xhr'; import window from 'global/window'; import { muxed as muxedSegment } from 'create-test-data!segments'; import {bytesToString, isTypedArray} from '@videojs/vhs-utils/es/byte-helpers'; -import {isLikelyFmp4MediaSegment} from '@videojs/vhs-utils/es/containers'; // return an absolute version of a page-relative URL export const absoluteUrl = function(relativeUrl) { @@ -531,7 +530,8 @@ export const requestAndAppendSegment = function({ requestDurationMillis, isOnlyAudio, isOnlyVideo, - tickClock + tickClock, + decryptionTicks }) { segment = segment || muxedSegment(); tickClock = typeof tickClock === 'undefined' ? true : tickClock; @@ -547,18 +547,19 @@ export const requestAndAppendSegment = function({ requestDurationMillis = requestDurationMillis || 1000; return new Promise((resolve, reject) => { + segmentLoader.one('appending', resolve); + segmentLoader.one('error', reject); + clock.tick(requestDurationMillis); if (initSegmentRequest) { standardXHRResponse(initSegmentRequest, initSegment); } standardXHRResponse(request, segment); - // fmp4 segments don't need to be transmuxed, therefore will execute synchronously - if (!isLikelyFmp4MediaSegment(segment)) { - segmentLoader.one('appending', resolve); - segmentLoader.one('error', reject); - } else { - resolve(); + // we need decryptionTicks for syncWorker, as decryption + // happens in a setTimeout on the main thread + if (decryptionTicks) { + clock.tick(2); } }).then(function() { if (throughput) { diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js index 53eff2e69..cccfab41e 100644 --- a/test/videojs-http-streaming.test.js +++ b/test/videojs-http-streaming.test.js @@ -531,8 +531,6 @@ QUnit.test('stats are reset on each new source', function(assert) { // media this.standardXHRResponse(this.requests.shift()); - // segment 0 - this.standardXHRResponse(this.requests.shift(), segment); this.player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.one('appending', () => { assert.equal( @@ -550,6 +548,9 @@ QUnit.test('stats are reset on each new source', function(assert) { assert.equal(this.player.tech_.vhs.stats.mediaBytesTransferred, 0, 'stat is reset'); done(); }); + + // segment 0 + this.standardXHRResponse(this.requests.shift(), segment); }); QUnit.test('XHR requests first byte range on play', function(assert) { @@ -715,9 +716,6 @@ QUnit.test('codecs are passed to the source buffer', function(assert) { // media this.standardXHRResponse(this.requests.shift()); - // segment 0 - this.standardXHRResponse(this.requests.shift(), muxedSegment()); - // source buffer won't be created until we have our first segment this.player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.one('appending', () => { // always create separate audio and video source buffers @@ -734,6 +732,10 @@ QUnit.test('codecs are passed to the source buffer', function(assert) { ); done(); }); + + // segment 0 + this.standardXHRResponse(this.requests.shift(), muxedSegment()); + }); QUnit.test('including HLS as a tech does not error', function(assert) { @@ -892,8 +894,6 @@ QUnit.test('starts downloading a segment on loadedmetadata', function(assert) { // media this.standardXHRResponse(this.requests[0]); - // segment 0 - this.standardXHRResponse(this.requests[1], segment); assert.strictEqual( this.requests[1].url, @@ -911,6 +911,9 @@ QUnit.test('starts downloading a segment on loadedmetadata', function(assert) { assert.equal(this.player.tech_.vhs.stats.mediaRequests, 1, '1 request'); done(); }); + + // segment 0 + this.standardXHRResponse(this.requests[1], segment); }); QUnit.test('re-initializes the handler for each source', function(assert) { @@ -1061,6 +1064,17 @@ QUnit.test('downloads media playlists after loading the master', function(assert assert.ok(segmentByteLength, 'the segment has some number of bytes'); + this.player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.one('appending', () => { + // verify stats + assert.equal( + this.player.tech_.vhs.stats.mediaBytesTransferred, + segmentByteLength, + 'transferred the segment byte length' + ); + assert.equal(this.player.tech_.vhs.stats.mediaRequests, 1, '1 request'); + done(); + }); + // segment 0 this.standardXHRResponse(this.requests[2], segment); @@ -1079,17 +1093,6 @@ QUnit.test('downloads media playlists after loading the master', function(assert absoluteUrl('manifest/media2-00001.ts'), 'first segment requested' ); - - this.player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.one('appending', () => { - // verify stats - assert.equal( - this.player.tech_.vhs.stats.mediaBytesTransferred, - segmentByteLength, - 'transferred the segment byte length' - ); - assert.equal(this.player.tech_.vhs.stats.mediaRequests, 1, '1 request'); - done(); - }); }); QUnit.test('setting bandwidth resets throughput', function(assert) { @@ -3411,11 +3414,6 @@ QUnit.test('calling play() at the end of a video replays', function(assert) { // copy the byte length since the segment bytes get cleared out const segmentByteLength = segment.byteLength; - assert.ok(segmentByteLength, 'the segment has some number of bytes'); - - // segment 0 - this.standardXHRResponse(this.requests.shift(), segment); - this.player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.one('appending', () => { this.player.tech_.ended = function() { return true; @@ -3434,6 +3432,11 @@ QUnit.test('calling play() at the end of a video replays', function(assert) { assert.equal(this.player.tech_.vhs.stats.mediaRequests, 1, '1 request'); done(); }); + + assert.ok(segmentByteLength, 'the segment has some number of bytes'); + + // segment 0 + this.standardXHRResponse(this.requests.shift(), segment); }); QUnit.test('keys are resolved relative to the master playlist', function(assert) { @@ -3548,7 +3551,8 @@ QUnit.test('keys are not requested when cached key available, cacheEncryptionKey mediaSource: mpc.mediaSource, segmentLoader: mpc.mainSegmentLoader_, clock: this.clock, - segment: encryptedSegment() + segment: encryptedSegment(), + decryptionTicks: true }).then(() => { assert.equal(this.requests.length, 1, 'requested a segment, not a key'); assert.equal( @@ -3603,7 +3607,8 @@ QUnit.test('keys are requested per segment, cacheEncryptionKeys:false', function mediaSource: mpc.mediaSource, segmentLoader: mpc.mainSegmentLoader_, clock: this.clock, - segment: encryptedSegment() + segment: encryptedSegment(), + decryptionTicks: true }).then(() => { assert.equal(this.requests.length, 2, 'requested a segment and a key'); assert.equal( @@ -5120,8 +5125,6 @@ QUnit.test( // media this.standardXHRResponse(this.requests.shift()); - // ts - this.standardXHRResponse(this.requests.shift(), muxedSegment()); this.player.tech(true).vhs.convertToProgramTime(3, (err, programTime) => { assert.deepEqual( @@ -5418,15 +5421,16 @@ QUnit.test('stats are reset on dispose', function(assert) { assert.ok(segmentByteLength, 'the segment has some number of bytes'); - // segment 0 - this.standardXHRResponse(this.requests.shift(), segment); - vhs.masterPlaylistController_.mainSegmentLoader_.on('appending', () => { assert.equal(vhs.stats.mediaBytesTransferred, segmentByteLength, 'stat is set'); vhs.dispose(); assert.equal(vhs.stats.mediaBytesTransferred, 0, 'stat is reset'); done(); }); + + // segment 0 + this.standardXHRResponse(this.requests.shift(), segment); + }); // mocking the fullscreenElement no longer works, find another way to mock From d37d3e38805ec60b9c7fbbbc96dc7145d3baf720 Mon Sep 17 00:00:00 2001 From: brandonocasey Date: Thu, 11 Mar 2021 16:52:11 -0500 Subject: [PATCH 2/4] do not run on bs and fix minified bundle --- package-lock.json | 6 +++--- package.json | 2 +- scripts/karma.conf.js | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 705062e65..ac77ffab8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7627,9 +7627,9 @@ } }, "rollup-plugin-worker-factory": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-worker-factory/-/rollup-plugin-worker-factory-0.5.3.tgz", - "integrity": "sha512-az67NoTmWLKxnO8EZlJ63J3GzVIYGkZ9cV9I44h2iXIc5wDZDprx4zH5jDF+dFXENcopVYRxqOJQxJPnv5R73A==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-worker-factory/-/rollup-plugin-worker-factory-0.5.4.tgz", + "integrity": "sha512-JuIwBZjiMzvqtcwZdY4gAFcS9O3+L+8hNJqWX763b95pqTY31O6T+gxWcYVWX0JuzsPOL9QKClkLN+RgBhgkQg==", "dev": true, "requires": { "rollup": "^2.34.2" diff --git a/package.json b/package.json index ff1f3cf14..a334ec3a7 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "lodash-compat": "^3.10.0", "nomnoml": "^0.3.0", "rollup": "^2.36.1", - "rollup-plugin-worker-factory": "^0.5.3", + "rollup-plugin-worker-factory": "0.5.4", "shelljs": "^0.8.4", "sinon": "^8.1.1", "url-toolkit": "^2.2.1", diff --git a/scripts/karma.conf.js b/scripts/karma.conf.js index ea7027f67..3e7adefbc 100644 --- a/scripts/karma.conf.js +++ b/scripts/karma.conf.js @@ -7,7 +7,6 @@ module.exports = function(config) { // for options const options = { coverage: CI_TEST_TYPE === 'coverage' ? true : false, - preferHeadless: false, browsers(aboutToRun) { return aboutToRun.filter(function(launcherName) { @@ -30,6 +29,11 @@ module.exports = function(config) { delete defaults.bsSafariMojave; delete defaults.bsSafariElCapitan; + // do not run on browserstack for coverage + if (CI_TEST_TYPE === 'coverage') { + defaults = {}; + } + return defaults; }, serverBrowsers() { From d2f46dacc50523fdd34e569a12d9884b1b8b8115 Mon Sep 17 00:00:00 2001 From: brandonocasey Date: Thu, 11 Mar 2021 16:56:28 -0500 Subject: [PATCH 3/4] fix coverage firefox --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14020cc6f..dcf33d7fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,15 +50,15 @@ jobs: - name: update apt cache on linux w/o browserstack run: sudo apt-get update - if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}} + if: ${{startsWith(matrix.os, 'ubuntu')}} - name: install ffmpeg/pulseaudio for firefox on linux w/o browserstack run: sudo apt-get install ffmpeg pulseaudio - if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}} + if: ${{startsWith(matrix.os, 'ubuntu')}} - name: start pulseaudio for firefox on linux w/o browserstack run: pulseaudio -D - if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}} + if: ${{startsWith(matrix.os, 'ubuntu')}} - name: setup node uses: actions/setup-node@v1 From 31eee42368d22dabe509c7086640826fcd25d1f1 Mon Sep 17 00:00:00 2001 From: brandonocasey Date: Thu, 11 Mar 2021 17:03:07 -0500 Subject: [PATCH 4/4] codecov, unit test only --- .github/workflows/ci.yml | 8 ++++++++ scripts/rollup.config.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcf33d7fc..20a0582da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,3 +77,11 @@ jobs: uses: GabrielBB/xvfb-action@v1 with: run: npm run test + + - name: coverage + uses: codecov/codecov-action@v1 + with: + token: ${{secrets.CODECOV_TOKEN}} + files: './test/dist/coverage/coverage-final.json' + fail_ci_if_error: true + if: ${{startsWith(env.CI_TEST_TYPE, 'coverage')}} diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index 9a5e1c388..e9b21bc83 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -103,7 +103,7 @@ const options = { if (CI_TEST_TYPE === 'playback' || CI_TEST_TYPE === 'playback-min') { options.testInput = 'test/playback.test.js'; -} else if (CI_TEST_TYPE === 'unit') { +} else if (CI_TEST_TYPE === 'unit' || CI_TEST_TYPE === 'coverage') { options.testInput = {include: ['test/**/*.test.js'], exclude: ['test/playback.test.js']}; }