mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-05-19 09:51:13 -04:00
Add tests for missing and corrupt pngs in sb3 and sprite3 files
This commit is contained in:
parent
3e193565ee
commit
2ff83ce667
8 changed files with 469 additions and 0 deletions
BIN
test/fixtures/corrupt_png.sb3
vendored
Normal file
BIN
test/fixtures/corrupt_png.sb3
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/corrupt_png.sprite3
vendored
Normal file
BIN
test/fixtures/corrupt_png.sprite3
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/missing_png.sb3
vendored
Normal file
BIN
test/fixtures/missing_png.sb3
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/missing_png.sprite3
vendored
Normal file
BIN
test/fixtures/missing_png.sprite3
vendored
Normal file
Binary file not shown.
126
test/integration/sb3_corrupted_png.js
Normal file
126
test/integration/sb3_corrupted_png.js
Normal file
|
@ -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);
|
||||
});
|
111
test/integration/sb3_missing_png.js
Normal file
111
test/integration/sb3_missing_png.js
Normal file
|
@ -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);
|
||||
});
|
125
test/integration/sprite3_corrupted_png.js
Normal file
125
test/integration/sprite3_corrupted_png.js
Normal file
|
@ -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);
|
||||
});
|
107
test/integration/sprite3_missing_png.js
Normal file
107
test/integration/sprite3_missing_png.js
Normal file
|
@ -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);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue