/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.test.io;
import android.test.InstrumentationTestCase;
import android.util.Log;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.common.DefaultProjectHandler;
import org.catrobat.catroid.content.LegoNXTSetting;
import org.catrobat.catroid.content.Project;
import org.catrobat.catroid.content.Scene;
import org.catrobat.catroid.content.Script;
import org.catrobat.catroid.content.Setting;
import org.catrobat.catroid.content.SingleSprite;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.content.StartScript;
import org.catrobat.catroid.content.bricks.Brick;
import org.catrobat.catroid.content.bricks.BrickBaseType;
import org.catrobat.catroid.content.bricks.ComeToFrontBrick;
import org.catrobat.catroid.content.bricks.FormulaBrick;
import org.catrobat.catroid.content.bricks.HideBrick;
import org.catrobat.catroid.content.bricks.LegoNxtMotorMoveBrick;
import org.catrobat.catroid.content.bricks.PlaceAtBrick;
import org.catrobat.catroid.content.bricks.SetSizeToBrick;
import org.catrobat.catroid.content.bricks.ShowBrick;
import org.catrobat.catroid.content.bricks.SpeakBrick;
import org.catrobat.catroid.devices.mindstorms.nxt.sensors.NXTSensor;
import org.catrobat.catroid.drone.DroneBrickFactory;
import org.catrobat.catroid.exceptions.CompatibilityProjectException;
import org.catrobat.catroid.exceptions.LoadingProjectException;
import org.catrobat.catroid.exceptions.OutdatedVersionProjectException;
import org.catrobat.catroid.formulaeditor.Formula;
import org.catrobat.catroid.formulaeditor.FormulaElement;
import org.catrobat.catroid.formulaeditor.InterpretationException;
import org.catrobat.catroid.formulaeditor.Sensors;
import org.catrobat.catroid.io.StorageHandler;
import org.catrobat.catroid.test.utils.Reflection;
import org.catrobat.catroid.test.utils.TestUtils;
import org.catrobat.catroid.ui.SettingsActivity;
import org.catrobat.catroid.ui.controller.BackPackListManager;
import org.catrobat.catroid.uitest.util.UiTestUtils;
import org.catrobat.catroid.utils.UtilFile;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static org.catrobat.catroid.common.Constants.PROJECTCODE_NAME;
import static org.catrobat.catroid.common.Constants.PROJECTCODE_NAME_TMP;
import static org.catrobat.catroid.common.Constants.PROJECTPERMISSIONS_NAME;
import static org.catrobat.catroid.utils.Utils.buildPath;
import static org.catrobat.catroid.utils.Utils.buildProjectPath;
public class StorageHandlerTest extends InstrumentationTestCase {
private final StorageHandler storageHandler;
private final String projectName = TestUtils.DEFAULT_TEST_PROJECT_NAME;
private final String backpackJsonValid = "backpack.json";
private final String backpackJsonInvalid = "backpack_invalid.json";
private final String backpackFilePath = buildPath(Constants.DEFAULT_ROOT, Constants.BACKPACK_DIRECTORY,
StorageHandler.BACKPACK_FILENAME);
private static final int SET_SPEED_INITIALLY = -70;
private static final int DEFAULT_MOVE_TIME_IN_MILLISECONDS = 2000;
private static final int DEFAULT_MOVE_POWER_IN_PERCENT = 20;
public StorageHandlerTest() throws IOException {
storageHandler = StorageHandler.getInstance();
}
@Override
public void setUp() throws Exception {
DefaultProjectHandler.createAndSaveDefaultProject(getInstrumentation().getTargetContext());
super.setUp();
// currentProject = ProjectManager.getInstance().getCurrentProject();
}
@Override
public void tearDown() throws Exception {
// ProjectManager.getInstance().setProject(currentProject);
TestUtils.deleteTestProjects();
super.tearDown();
}
public void testSerializeProject() {
final int xPosition = 457;
final int yPosition = 598;
final float size = 0.8f;
Project project = new Project(getInstrumentation().getTargetContext(), projectName);
Sprite firstSprite = new SingleSprite("first");
Sprite secondSprite = new SingleSprite("second");
Sprite thirdSprite = new SingleSprite("third");
Sprite fourthSprite = new SingleSprite("fourth");
Script testScript = new StartScript();
Script otherScript = new StartScript();
HideBrick hideBrick = new HideBrick();
ShowBrick showBrick = new ShowBrick();
SetSizeToBrick setSizeToBrick = new SetSizeToBrick(size);
ComeToFrontBrick comeToFrontBrick = new ComeToFrontBrick();
PlaceAtBrick placeAtBrick = new PlaceAtBrick(xPosition, yPosition);
testScript.addBrick(hideBrick);
testScript.addBrick(showBrick);
testScript.addBrick(setSizeToBrick);
testScript.addBrick(comeToFrontBrick);
otherScript.addBrick(placeAtBrick);
firstSprite.addScript(testScript);
secondSprite.addScript(otherScript);
project.getDefaultScene().addSprite(firstSprite);
project.getDefaultScene().addSprite(secondSprite);
project.getDefaultScene().addSprite(thirdSprite);
project.getDefaultScene().addSprite(fourthSprite);
storageHandler.saveProject(project);
Project loadedProject = storageHandler.loadProject(projectName, getInstrumentation().getContext());
Scene preScene = project.getDefaultScene();
Scene postScene = loadedProject.getDefaultScene();
ArrayList<Sprite> preSpriteList = (ArrayList<Sprite>) project.getDefaultScene().getSpriteList();
ArrayList<Sprite> postSpriteList = (ArrayList<Sprite>) loadedProject.getDefaultScene().getSpriteList();
//Test scene name:
assertEquals("Scene does not match after deserialization", preScene.getName(), postScene.getName());
// Test sprite names:
assertEquals("First sprite does not match after deserialization", preSpriteList.get(0).getName(),
postSpriteList.get(0).getName());
assertEquals("Second sprite does not match after deserialization", preSpriteList.get(1).getName(),
postSpriteList.get(1).getName());
assertEquals("Third sprite does not match after deserialization", preSpriteList.get(2).getName(),
postSpriteList.get(2).getName());
assertEquals("Fourth sprite does not match after deserialization", preSpriteList.get(3).getName(),
postSpriteList.get(3).getName());
assertEquals("Fifth sprite does not match after deserialization", preSpriteList.get(4).getName(),
postSpriteList.get(4).getName());
// Test project name:
assertEquals("Title missmatch after deserialization", project.getName(), loadedProject.getName());
// Test random brick values
Formula actualXPosition = ((FormulaBrick) postSpriteList.get(2).getScript(0).getBrickList().get(0))
.getFormulaWithBrickField(Brick.BrickField.X_POSITION);
Formula actualYPosition = ((FormulaBrick) postSpriteList.get(2).getScript(0).getBrickList().get(0))
.getFormulaWithBrickField(Brick.BrickField.Y_POSITION);
Formula actualSize = ((FormulaBrick) postSpriteList.get(1).getScript(0).getBrickList().get(2))
.getFormulaWithBrickField(Brick.BrickField.SIZE);
assertEquals("Size was not deserialized right", size, interpretFormula(actualSize, null));
assertEquals("XPosition was not deserialized right", xPosition,
interpretFormula(actualXPosition, null).intValue());
assertEquals("YPosition was not deserialized right", yPosition,
interpretFormula(actualYPosition, null).intValue());
// Test version codes and names
// final int preVersionCode = (Integer) TestUtils.getPrivateField("catroidVersionCode", project, false);
// final int postVersionCode = (Integer) TestUtils.getPrivateField("catroidVersionCode", loadedProject, false);
// assertEquals("Version codes are not equal", preVersionCode, postVersionCode);
//
// final String preVersionName = (String) TestUtils.getPrivateField("catroidVersionName", project, false);
// final String postVersionName = (String) TestUtils.getPrivateField("catroidVersionName", loadedProject, false);
// assertEquals("Version names are not equal", preVersionName, postVersionName);
}
public void testSanityCheck() throws IOException {
final int xPosition = 457;
final int yPosition = 598;
final float size = 0.8f;
final Project project = new Project(getInstrumentation().getTargetContext(), projectName);
Sprite firstSprite = new SingleSprite("first");
Sprite secondSprite = new SingleSprite("second");
Sprite thirdSprite = new SingleSprite("third");
Sprite fourthSprite = new SingleSprite("fourth");
Script testScript = new StartScript();
Script otherScript = new StartScript();
HideBrick hideBrick = new HideBrick();
ShowBrick showBrick = new ShowBrick();
SetSizeToBrick setSizeToBrick = new SetSizeToBrick(size);
ComeToFrontBrick comeToFrontBrick = new ComeToFrontBrick();
PlaceAtBrick placeAtBrick = new PlaceAtBrick(xPosition, yPosition);
testScript.addBrick(hideBrick);
testScript.addBrick(showBrick);
testScript.addBrick(setSizeToBrick);
testScript.addBrick(comeToFrontBrick);
otherScript.addBrick(placeAtBrick);
firstSprite.addScript(testScript);
secondSprite.addScript(otherScript);
project.getDefaultScene().addSprite(firstSprite);
project.getDefaultScene().addSprite(secondSprite);
project.getDefaultScene().addSprite(thirdSprite);
project.getDefaultScene().addSprite(fourthSprite);
File tmpCodeFile = new File(buildProjectPath(project.getName()), PROJECTCODE_NAME_TMP);
File currentCodeFile = new File(buildProjectPath(project.getName()), PROJECTCODE_NAME);
assertFalse(tmpCodeFile.getName() + " exists!", tmpCodeFile.exists());
assertFalse(currentCodeFile.getName() + " exists!", currentCodeFile.exists());
storageHandler.saveProject(project);
assertTrue(currentCodeFile.getName() + " was not created!", currentCodeFile.exists());
assertTrue(PROJECTCODE_NAME + " is empty!", currentCodeFile.length() > 0);
// simulate 1st Option: tmp_code.xml exists but code.xml doesn't exist --> saveProject process will restore from tmp_code.xml
if (!tmpCodeFile.createNewFile()) {
fail("Could not create tmp file");
}
UtilFile.copyFile(tmpCodeFile, currentCodeFile);
String currentCodeFileXml = Files.toString(currentCodeFile, Charsets.UTF_8);
assertTrue("Could not delete " + currentCodeFile.getName(), currentCodeFile.delete());
storageHandler.saveProject(project);
assertTrue(currentCodeFile.getName() + " was not created!", currentCodeFile.exists());
assertTrue(PROJECTCODE_NAME + " is empty!", currentCodeFile.length() > 0);
assertTrue("Sanity Check Failed. New Code File is not equal with tmp file.", currentCodeFileXml.equals(Files.toString(currentCodeFile, Charsets.UTF_8)));
// simulate 2nd Option: tmp_code.xml and code.xml exist --> saveProject process will discard tmp_code.xml and use code.xml
if (!tmpCodeFile.createNewFile()) {
fail("Could not create tmp file");
}
storageHandler.saveProject(project);
assertFalse("Sanity Check Failed. tmp file was not discarded.", tmpCodeFile.exists());
}
private Float interpretFormula(Formula formula, Sprite sprite) {
try {
return formula.interpretFloat(sprite);
} catch (InterpretationException interpretationException) {
Log.d(getClass().getSimpleName(), "Formula interpretation for Formula failed.", interpretationException);
}
return Float.NaN;
}
public void testGetRequiredResources() {
int resources = generateMultiplePermissionsProject().getRequiredResources();
assertEquals("Sum over required resources not matching", Brick.ARDRONE_SUPPORT
| Brick.FACE_DETECTION
| Brick.BLUETOOTH_LEGO_NXT
| Brick.TEXT_TO_SPEECH, resources);
}
public void testWritePermissionFile() throws IOException {
Project project = generateMultiplePermissionsProject();
ProjectManager.getInstance().setProject(project);
StorageHandler.getInstance().saveProject(project);
File permissionsFile = new File(buildProjectPath(project.getName()), PROJECTPERMISSIONS_NAME);
assertTrue("File containing the permissions could not be written", permissionsFile.exists());
//only for assertions. Add future permission; Vibration and LED not activated
Set<String> permissions = new HashSet<String>();
permissions.add(Constants.ARDRONE_SUPPORT);
permissions.add(Constants.BLUETOOTH_LEGO_NXT);
permissions.add(Constants.TEXT_TO_SPEECH);
permissions.add(Constants.FACE_DETECTION);
BufferedReader reader = new BufferedReader(new FileReader(permissionsFile));
String line;
while ((line = reader.readLine()) != null) {
assertTrue("Wrong permission in File found", permissions.contains(line));
}
}
public void testSerializeSettings() throws CompatibilityProjectException, OutdatedVersionProjectException, LoadingProjectException {
NXTSensor.Sensor[] sensorMapping = new NXTSensor.Sensor[] {
NXTSensor.Sensor.TOUCH, NXTSensor.Sensor.SOUND,
NXTSensor.Sensor.LIGHT_INACTIVE, NXTSensor.Sensor.ULTRASONIC
};
Reflection.setPrivateField(ProjectManager.getInstance(), "asynchronousTask", false);
Project project = generateMultiplePermissionsProject();
ProjectManager.getInstance().setProject(project);
String projectName = project.getName();
SettingsActivity.setLegoMindstormsNXTSensorMapping(getInstrumentation().getTargetContext(), sensorMapping);
ProjectManager.getInstance().saveProject(getInstrumentation().getTargetContext());
Setting setting = project.getSettings().get(0);
assertTrue("Wrong setting type, LegoNXT setting expected", setting instanceof LegoNXTSetting);
LegoNXTSetting nxtSetting = (LegoNXTSetting) setting;
NXTSensor.Sensor[] actualSensorMapping = nxtSetting.getSensorMapping();
assertEquals("Wrong numer of sensors", 4, actualSensorMapping.length);
assertEquals("Wrong sensor mapping for touch sensor", sensorMapping[0], actualSensorMapping[0]);
assertEquals("Wrong sensor mapping for sound sensor", sensorMapping[1], actualSensorMapping[1]);
assertEquals("Wrong sensor mapping for light sensor", sensorMapping[2], actualSensorMapping[2]);
assertEquals("Wrong sensor mapping for ultrasonic sensor", sensorMapping[3], actualSensorMapping[3]);
NXTSensor.Sensor[] changedSensorMapping = sensorMapping.clone();
changedSensorMapping[0] = NXTSensor.Sensor.LIGHT_ACTIVE;
SettingsActivity.setLegoMindstormsNXTSensorMapping(getInstrumentation().getTargetContext(), changedSensorMapping);
ProjectManager.getInstance().setProject(null);
ProjectManager.getInstance().loadProject(projectName, getInstrumentation().getTargetContext());
actualSensorMapping = SettingsActivity.getLegoMindstormsNXTSensorMapping(getInstrumentation().getTargetContext());
assertEquals("Wrong numer of sensors", 4, actualSensorMapping.length);
assertEquals("Wrong sensor mapping for touch sensor, settings not correctly loaded from project",
sensorMapping[0], actualSensorMapping[0]);
assertEquals("Wrong sensor mapping for sound sensor", sensorMapping[1], actualSensorMapping[1]);
assertEquals("Wrong sensor mapping for light sensor", sensorMapping[2], actualSensorMapping[2]);
assertEquals("Wrong sensor mapping for ultrasonic sensor", sensorMapping[3], actualSensorMapping[3]);
project = ProjectManager.getInstance().getCurrentProject();
setting = project.getSettings().get(0);
nxtSetting = (LegoNXTSetting) setting;
assertTrue("Wrong setting type, LegoNXT setting expected", setting instanceof LegoNXTSetting);
actualSensorMapping = nxtSetting.getSensorMapping();
assertEquals("Wrong numer of sensors", 4, actualSensorMapping.length);
assertEquals("Wrong sensor mapping for touch sensor", sensorMapping[0], actualSensorMapping[0]);
assertEquals("Wrong sensor mapping for sound sensor", sensorMapping[1], actualSensorMapping[1]);
assertEquals("Wrong sensor mapping for light sensor", sensorMapping[2], actualSensorMapping[2]);
assertEquals("Wrong sensor mapping for ultrasonic sensor", sensorMapping[3], actualSensorMapping[3]);
}
public void testDeserializeInvalidBackpackFile() throws IOException {
File backPackFile = loadBackpackFile(backpackJsonInvalid);
BackPackListManager.getInstance().loadBackpack();
TestUtils.sleep(1000);
assertTrue("Backpacked items loaded despite file is invalid!", BackPackListManager.getInstance().getBackpack()
.backpackedScripts.isEmpty());
assertFalse("Backpack.json should be deleted!", backPackFile.exists());
}
public void testDeserializeValidBackpackFile() throws IOException {
File backPackFile = loadBackpackFile(backpackJsonValid);
BackPackListManager.getInstance().loadBackpack();
TestUtils.sleep(1000);
assertFalse("Backpacked sprites not loaded!", BackPackListManager.getInstance().getBackpack().backpackedSprites.isEmpty());
assertFalse("Backpacked scripts not loaded!", BackPackListManager.getInstance().getBackpack().hiddenBackpackedScripts.isEmpty());
assertFalse("Backpacked looks not loaded!", BackPackListManager.getInstance().getBackpack().hiddenBackpackedLooks.isEmpty());
assertFalse("Backpacked sounds not loaded!", BackPackListManager.getInstance().getBackpack().hiddenBackpackedSounds.isEmpty());
assertTrue("Backpack.json should not be deleted!", backPackFile.exists());
}
private File loadBackpackFile(String jsonName) throws IOException {
UiTestUtils.clearBackPack(true);
InputStream inputStream = getInstrumentation().getContext().getResources().getAssets().open(jsonName);
File backPackFile = new File(backpackFilePath);
assertFalse("Backpack.json should not exist!", backPackFile.exists());
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
File targetFile = new File(backpackFilePath);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);
assertTrue("Backpack.json should exist!", backPackFile.exists());
assertTrue("Backpacked items not deleted!", BackPackListManager.getInstance().getBackpack()
.backpackedScripts.isEmpty());
return backPackFile;
}
private Project generateMultiplePermissionsProject() {
final Project project = new Project(getInstrumentation().getTargetContext(), projectName);
Sprite firstSprite = new SingleSprite("first");
Sprite secondSprite = new SingleSprite("second");
Script testScript = new StartScript();
Script otherScript = new StartScript();
HideBrick hideBrick = new HideBrick();
ShowBrick showBrick = new ShowBrick();
SpeakBrick speakBrick = new SpeakBrick("");
LegoNxtMotorMoveBrick motorBrick = new LegoNxtMotorMoveBrick(LegoNxtMotorMoveBrick.Motor.MOTOR_A, SET_SPEED_INITIALLY);
SetSizeToBrick setSizeToBrick = new SetSizeToBrick(new Formula(new FormulaElement(FormulaElement.ElementType.SENSOR,
Sensors.FACE_SIZE.name(), null)));
BrickBaseType moveBrick = DroneBrickFactory.getInstanceOfDroneBrick(DroneBrickFactory.DroneBricks.DRONE_MOVE_FORWARD_BRICK,
DEFAULT_MOVE_TIME_IN_MILLISECONDS, DEFAULT_MOVE_POWER_IN_PERCENT);
testScript.addBrick(hideBrick);
testScript.addBrick(showBrick);
testScript.addBrick(speakBrick);
testScript.addBrick(motorBrick);
otherScript.addBrick(setSizeToBrick);
otherScript.addBrick(moveBrick);
firstSprite.addScript(testScript);
secondSprite.addScript(otherScript);
project.getDefaultScene().addSprite(firstSprite);
project.getDefaultScene().addSprite(secondSprite);
return project;
}
}