From 2ff83ce667428b32f3e22acadebf0ce2677a0478 Mon Sep 17 00:00:00 2001 From: Karishma Chadha <kchadha@scratch.mit.edu> Date: Mon, 23 May 2022 18:02:14 -0400 Subject: [PATCH] Add tests for missing and corrupt pngs in sb3 and sprite3 files --- test/fixtures/corrupt_png.sb3 | Bin 0 -> 16188 bytes test/fixtures/corrupt_png.sprite3 | Bin 0 -> 15084 bytes test/fixtures/missing_png.sb3 | Bin 0 -> 3150 bytes test/fixtures/missing_png.sprite3 | Bin 0 -> 2046 bytes test/integration/sb3_corrupted_png.js | 126 ++++++++++++++++++++++ test/integration/sb3_missing_png.js | 111 +++++++++++++++++++ test/integration/sprite3_corrupted_png.js | 125 +++++++++++++++++++++ test/integration/sprite3_missing_png.js | 107 ++++++++++++++++++ 8 files changed, 469 insertions(+) create mode 100644 test/fixtures/corrupt_png.sb3 create mode 100644 test/fixtures/corrupt_png.sprite3 create mode 100644 test/fixtures/missing_png.sb3 create mode 100644 test/fixtures/missing_png.sprite3 create mode 100644 test/integration/sb3_corrupted_png.js create mode 100644 test/integration/sb3_missing_png.js create mode 100644 test/integration/sprite3_corrupted_png.js create mode 100644 test/integration/sprite3_missing_png.js diff --git a/test/fixtures/corrupt_png.sb3 b/test/fixtures/corrupt_png.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..acc4822166ce8a3762caaebf28f203aed480aa51 GIT binary patch literal 16188 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 A3;+NC literal 0 HcmV?d00001 diff --git a/test/fixtures/corrupt_png.sprite3 b/test/fixtures/corrupt_png.sprite3 new file mode 100644 index 0000000000000000000000000000000000000000..fc1d61356273c436ab53b5e7a020a97861bbd633 GIT binary patch literal 15084 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/missing_png.sb3 b/test/fixtures/missing_png.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..40260a33bf4740eb43a426bd162919cb3185e3bb GIT binary patch literal 3150 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 WX9B!g*+2%d0pVO=VwlYd;sF4VwSgl5 literal 0 HcmV?d00001 diff --git a/test/fixtures/missing_png.sprite3 b/test/fixtures/missing_png.sprite3 new file mode 100644 index 0000000000000000000000000000000000000000..0702cdcd35c63e0b8d34e9bbe345d2a123711ae6 GIT binary patch literal 2046 zcmWIWW@Zs#-~dAYb=yN2pum8MfkA~qfx*H!(bC+)JjEnA$t=w{$=uA?D9I!xB`q~6 z&D_M;%v`TLu`D!%mw`RcvM1>>V^7j_ATF)oW?*Fb%E-U~G?{^c(SYgv-ylynHw~bn z;f`UhX}Ki|0w4(>6x3#5PzqyUU}6wpNJ%V7WC5zV%D~LLm%WJhy=btkjq+@jM+&RO zirGRx#@|Xf(z`ix<)jr)H@Y8neE6B^t=dI*uf*DX<pQq6b1tuBwLbHo%v%0;qH6EE z8J1gL+<n9|*UBZKploa9qKq4^>Ed2ba<*1Yy4RT9Rye=!gyNq^2DP#GE1%Sx7q0Z( zCbROn|Bjw%%$=DN{nvfH8N<V16A=GCUpzC+k4vTSQ{>U~#oH!V%>1z_<JMx%KC|p_ z&9p`Fd)&UtpZLUl>g(Ebi{w`u9r^g`fXHe~jlhp#CZ2xU%q%*$_w9PKf^X%;Jw12# zu-WJ}c#A};2gO>h5teyXcy#Sr&E>l`OJCUVZK+(9{fWR+f!X%8^3Q)>zSg);ep~3i zHCOk1)skp3{_5D`prg+t9R6wk)y;>$?k_n#=W!KNl|rXkoBbsVE0x1+lb%bTYunFp zK=gdz%WRH1RRt>!$E_CAl!MvyUxc04I`ryL)J4`;p6pYUuA5%4d1LCO9L4GWPWndq zY2MRm*YaO&WSS&ttW{yKO4~;E3rEW5{SS9s|8{N7eS!C988wAt<X0$r$gdV&&m8o* z;MuPGw;ve4Jo8P3?Y8hI887)3sgwLQj88t9KIeVd_OR+j`j>@FIs)-h0`fhwabmXI zCI6m(I`wMW^Olzn-v9h@g^7dPOJI`VO+Ir@Nv6u*bH7dda`)@?pYs3vSf+D`b9Hfq zvLyZs@Mh<j|FLzQDKI^N5@P_?WO@adiu8fWG(O(f(b+#ZLSHYQq!g>n)RP3lXel;1 zK{Po%At~X5uTR(q{;-aK2BrxT%<2Ntj31R0*gmc|5MY&Yc4}JSGoeq?A*Xp+i=d+3 ztU2>nPmc(gFnLvY#H@%ZGr}j$oPH``MSem;Vn%LS%EX+6j45ev=LAlfKRfH~lcdCy zoR>)pvtMOpuFQV)CS_4}=ChP#*%|LLUZy?CdYAPi;|$2p>>Pd4lOD7KeF^d@_Atl= zh7LC{42lbiGD}kRvWoNb;5kwr=r=4mvN!0Y-(drhKcZjGE$n)1a(k<gsJV;2!jXrS zo+%HHOnloiZPow2yRNRHLUaC@oj)U8&3`J_rc7v-*w)FMJU7}|u7#a?m}mP|p;2<J z)3T+_Y4sis+Rx7`Zf0XWp1qFuzs2!8U-ruA$=XFqZ<<<I*Ts@4V{t6;K`#G`IbZsf z47{B-Pf1_0ZhF)9)(I-AOVT#wT<%I|ZH_lJdRi?rf2ncRr&Bx^QaHaPX>R`~Kcl<w z1yhGZKmW0B67T1DU90G+SgjCuOFSbZ>P}d^v+45o?<{j>vK8(QaNVf4I_Sw9QQg#3 zN8@SS7rm}cv(!EH+Q~m{Pw2<a;*dtWFQ2u1oAdd82c8aHz{T8>^5yuV`a)f$xDYG$ z`AX`Y9h-}^_UN13^Lxmhvv8+6W2yLgErC_*PcI1w`!sofsgG`%tlau}`_CVp*?a$} z)`Dy08H;rbez1XJ=~vj-1HQoW4ip#IV+j<0VyGz<TZ9d@1j)!G!i=arkTWc(_JDyc zjUX0DITJ@+65x$$BXaft6$CJ_rExhhDyUp6pj(a{>!2J616vwbGc%BqAJGj)_7y16 fz`&NqKvooAk&uJ}yjj^m=CA<a8b$^N8!!(5>8#sH literal 0 HcmV?d00001 diff --git a/test/integration/sb3_corrupted_png.js b/test/integration/sb3_corrupted_png.js new file mode 100644 index 000000000..dd02ca4fd --- /dev/null +++ b/test/integration/sb3_corrupted_png.js @@ -0,0 +1,126 @@ +/** + * This test mocks render breaking on loading a corrupted bitmap costume. + * The VM should handle this safely by displaying a Gray Question Mark, + * but keeping track of the original costume data and serializing the + * original costume data back out. The saved project.json should not + * reflect that the costume is broken and should therefore re-attempt + * to load the costume if the saved project is re-loaded. + */ +const path = require('path'); +const tap = require('tap'); +const md5 = require('js-md5'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const FakeRenderer = require('../fixtures/fake-renderer'); +const FakeBitmapAdapter = require('../fixtures/fake-bitmap-adapter'); +const {extractAsset, readFileToBuffer} = require('../fixtures/readProjectFile'); +const VirtualMachine = require('../../src/index'); +const {serializeCostumes} = require('../../src/serialization/serialize-assets'); + +const projectUri = path.resolve(__dirname, '../fixtures/corrupt_png.sb3'); +const project = readFileToBuffer(projectUri); +const costumeFileName = 'e1320c21995dcf6de10119be7f08c26b.png'; +const originalCostume = extractAsset(projectUri, costumeFileName); +// We need to get the actual md5 because we hand modified the png to corrupt it +// after we downloaded the project from Scratch +// Loading the project back into the VM will correct the assetId and md5 +const brokenCostumeMd5 = md5(originalCostume); + +global.Image = function () { + const image = { + width: 1, + height: 1 + }; + setTimeout(() => { + const base64Image = image.src.split(',')[1]; + const decodedText = Buffer.from(base64Image, 'base64').toString(); + if (decodedText.includes('Here is some')) { + image.onerror(); + } else { + image.onload(); + } + }, 1000); + return image; +}; + +global.document = { + createElement: () => ({ + // Create mock canvas + getContext: () => ({ + drawImage: () => ({}) + }) + }) +}; + +let vm; +let defaultBitmapAssetId; + +tap.beforeEach(() => { + const storage = makeTestStorage(); + + vm = new VirtualMachine(); + vm.attachStorage(storage); + defaultBitmapAssetId = vm.runtime.storage.defaultAssetId.ImageBitmap; + + vm.attachRenderer(new FakeRenderer()); + vm.attachV2BitmapAdapter(new FakeBitmapAdapter()); + + return vm.loadProject(project); +}); + +const test = tap.test; + +test('load sb3 project with corrupted bitmap costume file', t => { + t.equal(vm.runtime.targets.length, 2); + + const stage = vm.runtime.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = vm.runtime.targets[1]; + t.equal(greenGuySprite.getName(), 'Green Guy'); + t.equal(greenGuySprite.getCostumes().length, 1); + + const corruptedCostume = greenGuySprite.getCostumes()[0]; + t.equal(corruptedCostume.name, 'Green Guy'); + t.equal(corruptedCostume.assetId, defaultBitmapAssetId); + t.equal(corruptedCostume.dataFormat, 'png'); + // Runtime should have info about broken asset + t.ok(corruptedCostume.broken); + t.equal(corruptedCostume.broken.assetId, brokenCostumeMd5); + // Verify that we saved the original asset data + t.equal(md5(corruptedCostume.broken.asset.data), brokenCostumeMd5); + + t.end(); +}); + +test('load and then save project with corrupted bitmap costume file', t => { + const resavedProject = JSON.parse(vm.toJSON()); + + t.equal(resavedProject.targets.length, 2); + + const stage = resavedProject.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = resavedProject.targets[1]; + t.equal(greenGuySprite.name, 'Green Guy'); + t.equal(greenGuySprite.costumes.length, 1); + + const corruptedCostume = greenGuySprite.costumes[0]; + t.equal(corruptedCostume.name, 'Green Guy'); + // Resaved project costume should have the metadata that corresponds to the original broken costume + t.equal(corruptedCostume.assetId, brokenCostumeMd5); + t.equal(corruptedCostume.dataFormat, 'png'); + // Test that we didn't save any data about the costume being broken + t.notOk(corruptedCostume.broken); + + t.end(); +}); + +test('serializeCostume saves orignal broken costume', t => { + const costumeDescs = serializeCostumes(vm.runtime, vm.runtime.targets[1].id); + t.equal(costumeDescs.length, 1); + const costume = costumeDescs[0]; + t.equal(costume.fileName, `${brokenCostumeMd5}.png`); + t.equal(md5(costume.fileContent), brokenCostumeMd5); + t.end(); + process.nextTick(process.exit); +}); diff --git a/test/integration/sb3_missing_png.js b/test/integration/sb3_missing_png.js new file mode 100644 index 000000000..a16e17a66 --- /dev/null +++ b/test/integration/sb3_missing_png.js @@ -0,0 +1,111 @@ +/** + * This test ensures that the VM gracefully handles an sb3 project with + * a missing bitmap costume. The VM should handle this safely by displaying + * a Gray Question Mark, but keeping track of the original costume data + * and serializing the original costume data back out. The saved project.json + * should not reflect that the costume is broken and should therefore re-attempt + * to load the costume if the saved project is re-loaded. + */ +const path = require('path'); +const tap = require('tap'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const FakeRenderer = require('../fixtures/fake-renderer'); +const FakeBitmapAdapter = require('../fixtures/fake-bitmap-adapter'); +const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; +const VirtualMachine = require('../../src/index'); +const {serializeCostumes} = require('../../src/serialization/serialize-assets'); + +const projectUri = path.resolve(__dirname, '../fixtures/missing_png.sb3'); +const project = readFileToBuffer(projectUri); + + +const missingCostumeAssetId = 'e1320c21995dcf6de10119be7f08c26b'; + +global.Image = function () { + const image = { + width: 1, + height: 1 + }; + setTimeout(() => image.onload(), 1000); + return image; +}; + +global.document = { + createElement: () => ({ + // Create mock canvas + getContext: () => ({ + drawImage: () => ({}) + }) + }) +}; + +let vm; + +tap.beforeEach(() => { + const storage = makeTestStorage(); + + vm = new VirtualMachine(); + vm.attachStorage(storage); + vm.attachRenderer(new FakeRenderer()); + vm.attachV2BitmapAdapter(new FakeBitmapAdapter()); + + return vm.loadProject(project); +}); + +const test = tap.test; + +test('loading sb3 project with missing bitmap costume file', t => { + t.equal(vm.runtime.targets.length, 2); + + const stage = vm.runtime.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = vm.runtime.targets[1]; + t.equal(greenGuySprite.getName(), 'Green Guy'); + t.equal(greenGuySprite.getCostumes().length, 1); + + const missingCostume = greenGuySprite.getCostumes()[0]; + t.equal(missingCostume.name, 'Green Guy'); + // Costume should have both default cosutme (e.g. Gray Question Mark) data and original data + const defaultVectorAssetId = vm.runtime.storage.defaultAssetId.ImageBitmap; + t.equal(missingCostume.assetId, defaultVectorAssetId); + t.equal(missingCostume.dataFormat, 'png'); + // Runtime should have info about broken asset + t.ok(missingCostume.broken); + t.equal(missingCostume.broken.assetId, missingCostumeAssetId); + + t.end(); +}); + +test('load and then save sb3 project with missing costume file', t => { + const resavedProject = JSON.parse(vm.toJSON()); + + t.equal(resavedProject.targets.length, 2); + + const stage = resavedProject.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = resavedProject.targets[1]; + t.equal(greenGuySprite.name, 'Green Guy'); + t.equal(greenGuySprite.costumes.length, 1); + + const missingCostume = greenGuySprite.costumes[0]; + t.equal(missingCostume.name, 'Green Guy'); + // Costume should have both default cosutme (e.g. Gray Question Mark) data and original data + t.equal(missingCostume.assetId, missingCostumeAssetId); + t.equal(missingCostume.dataFormat, 'png'); + // Test that we didn't save any data about the costume being broken + t.notOk(missingCostume.broken); + + t.end(); +}); + +test('serializeCostume does not save data for missing costume', t => { + const costumeDescs = serializeCostumes(vm.runtime); + + t.equal(costumeDescs.length, 1); // Should only have one costume, the backdrop + t.not(costumeDescs[0].fileName, `${missingCostumeAssetId}.png`); + + t.end(); + process.nextTick(process.exit); +}); diff --git a/test/integration/sprite3_corrupted_png.js b/test/integration/sprite3_corrupted_png.js new file mode 100644 index 000000000..2898fa82a --- /dev/null +++ b/test/integration/sprite3_corrupted_png.js @@ -0,0 +1,125 @@ +/** + * This test mocks render breaking on loading a sprite3 with a + * corrupted bitmap costume. + * The VM should handle this safely by displaying a Gray Question Mark, + * but keeping track of the original costume data and serializing the + * original costume data back out. The saved project.json should not + * reflect that the costume is broken and should therefore re-attempt + * to load the costume if the saved project is re-loaded. + */ +const path = require('path'); +const tap = require('tap'); +const md5 = require('js-md5'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const FakeRenderer = require('../fixtures/fake-renderer'); +const FakeBitmapAdapter = require('../fixtures/fake-bitmap-adapter'); +const {extractAsset, readFileToBuffer} = require('../fixtures/readProjectFile'); +const VirtualMachine = require('../../src/index'); +const {serializeCostumes} = require('../../src/serialization/serialize-assets'); + +const projectUri = path.resolve(__dirname, '../fixtures/default.sb3'); +const project = readFileToBuffer(projectUri); + +const spriteUri = path.resolve(__dirname, '../fixtures/corrupt_png.sprite3'); +const sprite = readFileToBuffer(spriteUri); + +const costumeFileName = 'e1320c21995dcf6de10119be7f08c26b.png'; +const originalCostume = extractAsset(spriteUri, costumeFileName); +// We need to get the actual md5 because we hand modified the png to corrupt it +// after we downloaded the project from Scratch +// Loading the project back into the VM will correct the assetId and md5 +const brokenCostumeMd5 = md5(originalCostume); + +global.Image = function () { + const image = { + width: 1, + height: 1 + }; + setTimeout(() => { + const base64Image = image.src.split(',')[1]; + const decodedText = Buffer.from(base64Image, 'base64').toString(); + if (decodedText.includes('Here is some')) { + image.onerror(); + } else { + image.onload(); + } + }, 1000); + return image; +}; + +global.document = { + createElement: () => ({ + // Create mock canvas + getContext: () => ({ + drawImage: () => ({}) + }) + }) +}; + +let vm; +let defaultBitmapAssetId; + +tap.beforeEach(() => { + const storage = makeTestStorage(); + + vm = new VirtualMachine(); + vm.attachStorage(storage); + defaultBitmapAssetId = vm.runtime.storage.defaultAssetId.ImageBitmap; + + vm.attachRenderer(new FakeRenderer()); + vm.attachV2BitmapAdapter(new FakeBitmapAdapter()); + + return vm.loadProject(project).then(() => vm.addSprite(sprite)); +}); + +const test = tap.test; + +test('load sprite3 with corrupted bitmap costume file', t => { + t.equal(vm.runtime.targets.length, 3); + + const stage = vm.runtime.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = vm.runtime.targets[2]; + t.equal(greenGuySprite.getName(), 'Green Guy'); + t.equal(greenGuySprite.getCostumes().length, 1); + + const corruptedCostume = greenGuySprite.getCostumes()[0]; + t.equal(corruptedCostume.name, 'Green Guy'); + t.equal(corruptedCostume.assetId, defaultBitmapAssetId); + t.equal(corruptedCostume.dataFormat, 'png'); + // Runtime should have info about broken asset + t.ok(corruptedCostume.broken); + t.equal(corruptedCostume.broken.assetId, brokenCostumeMd5); + // Verify that we saved the original asset data + t.equal(md5(corruptedCostume.broken.asset.data), brokenCostumeMd5); + + t.end(); +}); + +test('load and then save sprite with corrupted costume file', t => { + const resavedSprite = JSON.parse(vm.toJSON(vm.runtime.targets[2].id)); + + t.equal(resavedSprite.name, 'Green Guy'); + t.equal(resavedSprite.costumes.length, 1); + + const corruptedCostume = resavedSprite.costumes[0]; + t.equal(corruptedCostume.name, 'Green Guy'); + // Resaved project costume should have the metadata that corresponds to the original broken costume + t.equal(corruptedCostume.assetId, brokenCostumeMd5); + t.equal(corruptedCostume.dataFormat, 'png'); + // Test that we didn't save any data about the costume being broken + t.notOk(corruptedCostume.broken); + + t.end(); +}); + +test('serializeCostume saves orignal broken costume', t => { + const costumeDescs = serializeCostumes(vm.runtime, vm.runtime.targets[2].id); + t.equal(costumeDescs.length, 1); + const costume = costumeDescs[0]; + t.equal(costume.fileName, `${brokenCostumeMd5}.png`); + t.equal(md5(costume.fileContent), brokenCostumeMd5); + t.end(); + process.nextTick(process.exit); +}); diff --git a/test/integration/sprite3_missing_png.js b/test/integration/sprite3_missing_png.js new file mode 100644 index 000000000..bbc7a816c --- /dev/null +++ b/test/integration/sprite3_missing_png.js @@ -0,0 +1,107 @@ +/** + * This test ensures that the VM gracefully handles a sprite3 file with + * a missing bitmap costume. The VM should handle this safely by displaying + * a Gray Question Mark, but keeping track of the original costume data + * and serializing the original costume data back out. The saved project.json + * should not reflect that the costume is broken and should therefore re-attempt + * to load the costume if the saved project is re-loaded. + */ +const path = require('path'); +const tap = require('tap'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const FakeRenderer = require('../fixtures/fake-renderer'); +const FakeBitmapAdapter = require('../fixtures/fake-bitmap-adapter'); +const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; +const VirtualMachine = require('../../src/index'); +const {serializeCostumes} = require('../../src/serialization/serialize-assets'); + +// The particular project that we're loading doesn't matter for this test +const projectUri = path.resolve(__dirname, '../fixtures/default.sb3'); +const project = readFileToBuffer(projectUri); + +const spriteUri = path.resolve(__dirname, '../fixtures/missing_png.sprite3'); +const sprite = readFileToBuffer(spriteUri); + +const missingCostumeAssetId = 'e1320c21995dcf6de10119be7f08c26b'; + +global.Image = function () { + const image = { + width: 1, + height: 1 + }; + setTimeout(() => image.onload(), 1000); + return image; +}; + +global.document = { + createElement: () => ({ + // Create mock canvas + getContext: () => ({ + drawImage: () => ({}) + }) + }) +}; + +let vm; + +tap.beforeEach(() => { + const storage = makeTestStorage(); + + vm = new VirtualMachine(); + vm.attachStorage(storage); + vm.attachRenderer(new FakeRenderer()); + vm.attachV2BitmapAdapter(new FakeBitmapAdapter()); + + return vm.loadProject(project).then(() => vm.addSprite(sprite)); +}); + +const test = tap.test; + +test('loading sprite3 with missing bitmap costume file', t => { + t.equal(vm.runtime.targets.length, 3); + + const stage = vm.runtime.targets[0]; + t.ok(stage.isStage); + + const greenGuySprite = vm.runtime.targets[2]; + t.equal(greenGuySprite.getName(), 'Green Guy'); + t.equal(greenGuySprite.getCostumes().length, 1); + + const missingCostume = greenGuySprite.getCostumes()[0]; + t.equal(missingCostume.name, 'Green Guy'); + // Costume should have both default cosutme (e.g. Gray Question Mark) data and original data + const defaultBitmapAssetId = vm.runtime.storage.defaultAssetId.ImageBitmap; + t.equal(missingCostume.assetId, defaultBitmapAssetId); + t.equal(missingCostume.dataFormat, 'png'); + // Runtime should have info about broken asset + t.ok(missingCostume.broken); + t.equal(missingCostume.broken.assetId, missingCostumeAssetId); + + t.end(); +}); + +test('load and then save sprite3 with missing bitmap costume file', t => { + const resavedSprite = JSON.parse(vm.toJSON(vm.runtime.targets[2].id)); + + t.equal(resavedSprite.name, 'Green Guy'); + t.equal(resavedSprite.costumes.length, 1); + + const missingCostume = resavedSprite.costumes[0]; + t.equal(missingCostume.name, 'Green Guy'); + // Costume should have both default cosutme (e.g. Gray Question Mark) data and original data + t.equal(missingCostume.assetId, missingCostumeAssetId); + t.equal(missingCostume.dataFormat, 'png'); + // Test that we didn't save any data about the costume being broken + t.notOk(missingCostume.broken); + + t.end(); +}); + +test('serializeCostume does not save data for missing costume', t => { + const costumeDescs = serializeCostumes(vm.runtime, vm.runtime.targets[2].id); + + t.equal(costumeDescs.length, 0); + + t.end(); + process.nextTick(process.exit); +});