mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
840ffb5df0
Newer versions of `tap` run more asynchronously, so sometimes using `process.nextTick(process.exit)` to end a test would prevent the test from completing correctly. Removing all instances of `process.nextTick(process.exit)` put tests into three categories: * the test still worked correctly -- no fixup needed. * the test would hang because the VM's `_steppingInterval` was keeping Node alive. These tests call a new `quit()` method which ends the stepping interval. * the `load-extensions` test needed special attention because the "Video Sensing" extension starts its own loop using `setTimeout`. I added a `_stopLoop()` method on the extension and directly call that from the test. I'm not completely happy with this solution but anything more general would likely require a change to the extension spec, so I'm leaving that as a followup task.
105 lines
4 KiB
JavaScript
105 lines
4 KiB
JavaScript
/**
|
|
* This test mocks render breaking on loading a sprite with a
|
|
* corrupted vector 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 {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_svg.sprite3');
|
|
const sprite = readFileToBuffer(spriteUri);
|
|
|
|
const costumeFileName = 'a267f8b97ee9cf8aa9832aa0b4cfd9eb.svg';
|
|
const originalCostume = extractAsset(spriteUri, costumeFileName);
|
|
// We need to get the actual md5 because we hand modified the svg 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);
|
|
|
|
let vm;
|
|
let defaultVectorAssetId;
|
|
|
|
tap.beforeEach(() => {
|
|
const storage = makeTestStorage();
|
|
|
|
vm = new VirtualMachine();
|
|
vm.attachStorage(storage);
|
|
defaultVectorAssetId = vm.runtime.storage.defaultAssetId.ImageVector;
|
|
|
|
// Mock renderer breaking on loading a corrupt costume
|
|
FakeRenderer.prototype.createSVGSkin = function (svgString) {
|
|
// Look for text added to costume to make it a corrupt svg
|
|
if (svgString.includes('<here is some')) {
|
|
throw new Error('mock createSVGSkin broke');
|
|
}
|
|
return FakeRenderer.prototype._nextSkinId++;
|
|
};
|
|
|
|
vm.attachRenderer(new FakeRenderer());
|
|
|
|
return vm.loadProject(project).then(() => vm.addSprite(sprite));
|
|
});
|
|
|
|
const test = tap.test;
|
|
|
|
test('load sprite3 with corrupted vector costume file', t => {
|
|
t.equal(vm.runtime.targets.length, 3);
|
|
|
|
const stage = vm.runtime.targets[0];
|
|
t.ok(stage.isStage);
|
|
|
|
const blueGuySprite = vm.runtime.targets[2];
|
|
t.equal(blueGuySprite.getName(), 'Blue Square Guy');
|
|
t.equal(blueGuySprite.getCostumes().length, 1);
|
|
|
|
const corruptedCostume = blueGuySprite.getCostumes()[0];
|
|
t.equal(corruptedCostume.name, 'costume1');
|
|
t.equal(corruptedCostume.assetId, defaultVectorAssetId);
|
|
t.equal(corruptedCostume.dataFormat, 'svg');
|
|
// 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, 'Blue Square Guy');
|
|
t.equal(resavedSprite.costumes.length, 1);
|
|
|
|
const corruptedCostume = resavedSprite.costumes[0];
|
|
t.equal(corruptedCostume.name, 'costume1');
|
|
// Resaved project costume should have the metadata that corresponds to the original broken costume
|
|
t.equal(corruptedCostume.assetId, brokenCostumeMd5);
|
|
t.equal(corruptedCostume.dataFormat, 'svg');
|
|
// 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}.svg`);
|
|
t.equal(md5(costume.fileContent), brokenCostumeMd5);
|
|
t.end();
|
|
});
|