From 12fd3478115bf131047d34984498663446bfaa1f Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 2 Oct 2024 21:06:50 -0700 Subject: [PATCH] Handle non-serializable expected and actual values in parallel mode Fixes #212 --- lib/parallel_worker.js | 39 ++++++++++++++++++++++++++++++-- spec/parallel_worker_spec.js | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/lib/parallel_worker.js b/lib/parallel_worker.js index ab47dec..cdfa2fd 100644 --- a/lib/parallel_worker.js +++ b/lib/parallel_worker.js @@ -148,7 +148,7 @@ function forwardingReporter(clusterWorker) { for (const eventName of eventNames) { reporter[eventName] = function (payload) { - sendIfConnected(clusterWorker, { + const msg = { type: 'reporterEvent', eventName, payload: { @@ -157,7 +157,18 @@ function forwardingReporter(clusterWorker) { // Make them globally unique by prepending the worker ID. id: `${clusterWorker.id}-${payload.id}` } - }); + }; + + try { + sendIfConnected(clusterWorker, msg); + } catch (e) { + if (e instanceof TypeError) { + msg.payload = fixNonSerializableReporterEvent(payload); + sendIfConnected(clusterWorker, msg); + } else { + throw e; + } + } }; } @@ -181,5 +192,29 @@ function sendIfConnected(clusterWorker, msg) { return false; } +function fixNonSerializableReporterEvent(payload) { + payload = {...payload}; + + for (const ak of ['passedExpectations', 'failedExpectations']) { + if (payload[ak]) { + payload[ak] = payload[ak].map(function(expectation) { + expectation = {...expectation}; + + for (const vk of ['expected', 'actual']) { + try { + JSON.stringify(expectation[vk]); + } catch (ex) { + expectation[vk] = ''; + } + } + + return expectation; + }); + } + } + + return payload; +} + module.exports = ParallelWorker; diff --git a/spec/parallel_worker_spec.js b/spec/parallel_worker_spec.js index 4052afb..cc42879 100644 --- a/spec/parallel_worker_spec.js +++ b/spec/parallel_worker_spec.js @@ -620,6 +620,50 @@ describe('ParallelWorker', function() { expect(this.clusterWorker.send).toHaveBeenCalledOnceWith({type: 'booted'}); }); } + + for (const eventName of ['specDone', 'suiteDone']) { + it(`handles non-serializable expected values in ${eventName}`, async function() { + const env = jasmine.createSpyObj( + 'env', ['execute', 'parallelReset', 'addReporter'] + ); + const loader = jasmine.createSpyObj('loader', ['load']); + loader.load.withArgs('jasmine-core') + .and.returnValue(Promise.resolve(dummyCore(env))); + const jasmineWorker = new ParallelWorker({ + loader, + clusterWorker: this.clusterWorker, + process: stubProcess() + }); + + this.clusterWorker.emit('message', { + type: 'configure', + configuration: { + helpers: [], + } + }); + await jasmineWorker.envPromise_; + + this.clusterWorker.send.calls.reset(); + this.clusterWorker.send.and.callFake(function(msg) { + // Throw if msg is not serializable + JSON.stringify(msg); + }); + const notSerializable = BigInt(0); + dispatchRepoterEvent(env, eventName, { + failedExpectations: [{expected: 'ok', actual: notSerializable}], + passedExpectations: [{expected: notSerializable, actual: 'ok'}], + }); + + expect(this.clusterWorker.send).toHaveBeenCalledWith({ + type: 'reporterEvent', + eventName, + payload: jasmine.objectContaining({ + failedExpectations: [{expected: 'ok', actual: ''}], + passedExpectations: [{expected: '', actual: 'ok'}], + }) + }); + }); + } }); it('reports unhandled exceptions that occur between batches', async function() {