openrocket/swing/test/net/sf/openrocket/IntegrationTest.java
2018-02-25 12:43:49 -05:00

377 lines
12 KiB
Java

package net.sf.openrocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.Action;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.util.Modules;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.database.ComponentPresetDao;
import net.sf.openrocket.database.ComponentPresetDatabase;
import net.sf.openrocket.database.motor.MotorDatabase;
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.file.GeneralRocketLoader;
import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.file.motor.GeneralMotorLoader;
import net.sf.openrocket.gui.main.UndoRedoAction;
import net.sf.openrocket.l10n.DebugTranslator;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.RigidBody;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.plugin.PluginModule;
import net.sf.openrocket.rocketcomponent.EngineBlock;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.MassComponent;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.utils.CoreServicesModule;
/**
* This class contains various integration tests that simulate user actions that
* might be performed.
*/
@RunWith(JMock.class)
public class IntegrationTest {
Mockery context = new JUnit4Mockery();
private OpenRocketDocument document;
private Action undoAction, redoAction;
private AerodynamicCalculator aeroCalc = new BarrowmanCalculator();
private FlightConfiguration config;
private FlightConditions conditions;
private String massComponentID = null;
@BeforeClass
public static void setUp() throws Exception {
Module applicationModule = new CoreServicesModule();
Module pluginModule = new PluginModule();
Module debugTranslator = new AbstractModule() {
@Override
protected void configure() {
bind(Translator.class).toInstance(new DebugTranslator(null));
}
};
Module dbOverrides = new AbstractModule() {
@Override
protected void configure() {
bind(ComponentPresetDao.class).toProvider(new EmptyComponentDbProvider());
bind(MotorDatabase.class).toProvider(new MotorDbProvider());
}
};
Injector injector = Guice.createInjector(Modules.override(applicationModule).with(debugTranslator), pluginModule, dbOverrides);
Application.setInjector(injector);
}
/**
* Tests loading a simple rocket design, modifying it, simulating
* it and the undo/redo mechanism in various combinations.
*/
@Test
public void testSimpleRocket() throws SimulationException {
System.setProperty("openrocket.unittest", "true");
document = loadRocket("simplerocket.ork");
undoAction = UndoRedoAction.newUndoAction(document);
redoAction = UndoRedoAction.newRedoAction(document);
FlightConfigurationId fcid = document.getSimulation(0).getFlightConfigurationId();
config = document.getRocket().getFlightConfiguration(fcid);
conditions = new FlightConditions(config);
// Test undo state
checkUndoState(null, null);
InnerTube mmt = (InnerTube)config.getRocket().getChild(0).getChild(1).getChild(2);
// Compute cg+cp + altitude
// double cgx, double mass, double cpx, double cna)
checkCgCp(0.248, 0.0645, 0.320, 12.0);
checkAlt(48.2);
// Mass modification
document.addUndoPosition("Modify mass");
checkUndoState(null, null);
massComponent().setComponentMass(0.01);
checkUndoState("Modify mass", null);
// Check cg+cp + altitude
checkCgCp(0.230, 0.0745, 0.320, 12.0);
checkAlt(37.2);
// Non-change
document.addUndoPosition("No change");
checkUndoState("Modify mass", null);
// Non-funcitonal change
document.addUndoPosition("Name change");
checkUndoState("Modify mass", null);
massComponent().setName("Foobar component");
checkUndoState("Name change", null);
// Check cg+cp
checkCgCp(0.230, 0.0745, 0.320, 12.0);
// Aerodynamic modification
document.addUndoPosition("Remove component");
checkUndoState("Name change", null);
document.getRocket().getChild(0).removeChild(0);
checkUndoState("Remove component", null);
// Check cg+cp + altitude
checkCgCp(0.163, 0.0613, 0.275, 9.95);
checkAlt(45.0);
// Undo "Remove component" change
undoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertTrue(document.getRocket().getChild(0).getChild(0) instanceof NoseCone);
checkUndoState("Name change", "Remove component");
// Check cg+cp + altitude
checkCgCp(0.230, 0.0745, 0.320, 12.0);
checkAlt(37.2);
// Undo "Name change" change
undoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertEquals("Extra mass", massComponent().getName());
checkUndoState("Modify mass", "Name change");
// Check cg+cp
checkCgCp(0.230, 0.0745, 0.320, 12.0);
// Undo "Modify mass" change
undoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertEquals(0, massComponent().getComponentMass(), 0);
checkUndoState(null, "Modify mass");
// Check cg+cp + altitude
checkCgCp(0.248, 0.0645, 0.320, 12.0);
checkAlt(48.2);
// Redo "Modify mass" change
redoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertEquals(0.010, massComponent().getComponentMass(), 0.00001);
checkUndoState("Modify mass", "Name change");
// Check cg+cp + altitude
checkCgCp(0.230, 0.0745, 0.320, 12.0);
checkAlt(37.2);
// Mass modification
document.addUndoPosition("Modify mass2");
checkUndoState("Modify mass", "Name change");
massComponent().setComponentMass(0.015);
checkUndoState("Modify mass2", null);
// Check cg+cp + altitude
checkCgCp(0.223, 0.0795, 0.320, 12.0);
checkAlt(32.7);
// Perform component movement
document.startUndo("Move component");
document.getRocket().freeze();
RocketComponent bodytube = document.getRocket().getChild(0).getChild(1);
RocketComponent innertube = bodytube.getChild(2);
RocketComponent engineblock = innertube.getChild(0);
assertTrue(innertube.removeChild(engineblock));
bodytube.addChild(engineblock, 0);
checkUndoState("Modify mass2", null);
document.getRocket().thaw();
checkUndoState("Move component", null);
document.stopUndo();
// Check cg+cp + altitude
checkCgCp(0.221, 0.0797, 0.320, 12.0);
checkAlt(32.7);
// Modify mass without setting undo description
massComponent().setComponentMass(0.020);
checkUndoState("Modify mass2", null);
// Check cg+cp + altitude
checkCgCp(0.215, 0.0847, 0.320, 12.0);
checkAlt(29.0);
// Undo "Modify mass2" change
undoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertEquals(0.015, massComponent().getComponentMass(), 0.0000001);
checkUndoState("Move component", "Modify mass2");
// Check cg+cp + altitude
checkCgCp(0.221, 0.0797, 0.320, 12.0);
checkAlt(32.7);
// Undo "Move component" change
undoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertTrue(document.getRocket().getChild(0).getChild(1).getChild(2).getChild(0) instanceof EngineBlock);
checkUndoState("Modify mass2", "Move component");
// Check cg+cp + altitude
checkCgCp(0.223, 0.0795, 0.320, 12.0);
checkAlt(32.7);
// Redo "Move component" change
redoAction.actionPerformed(new ActionEvent(this, 0, "foo"));
assertTrue(document.getRocket().getChild(0).getChild(1).getChild(0) instanceof EngineBlock);
checkUndoState("Move component", "Modify mass2");
// Check cg+cp + altitude
checkCgCp(0.221, 0.0797, 0.320, 12.0);
checkAlt(32.7);
}
/* *******************
* * Utility Methods *
* *******************
*/
private static ThrustCurveMotor readMotor() {
GeneralMotorLoader loader = new GeneralMotorLoader();
InputStream is = IntegrationTest.class.getResourceAsStream("Estes_A8.rse");
assertNotNull("Problem in unit test, cannot find Estes_A8.rse", is);
try {
for (ThrustCurveMotor.Builder m : loader.load(is, "Estes_A8.rse")) {
return m.build();
}
is.close();
} catch (IOException e) {
e.printStackTrace();
fail("IOException: " + e);
}
throw new RuntimeException("Could not load motor");
}
private static class EmptyComponentDbProvider implements Provider<ComponentPresetDao> {
final ComponentPresetDao db = new ComponentPresetDatabase();
@Override
public ComponentPresetDao get() {
return db;
}
}
private static class MotorDbProvider implements Provider<ThrustCurveMotorSetDatabase> {
final ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase();
public MotorDbProvider() {
db.addMotor(readMotor());
assertEquals(1, db.getMotorSets().size());
}
@Override
public ThrustCurveMotorSetDatabase get() {
return db;
}
}
private MassComponent massComponent() {
if (massComponentID == null) {
massComponentID = document.getRocket().getChild(0).getChild(1).getChild(0).getID();
}
return (MassComponent) document.getRocket().findComponent(massComponentID);
}
private void checkUndoState(String undoDesc, String redoDesc) {
if (undoDesc == null) {
assertEquals("[UndoRedoAction.OpenRocketDocument.Undo]", undoAction.getValue(Action.NAME));
assertFalse(undoAction.isEnabled());
} else {
assertEquals("[UndoRedoAction.OpenRocketDocument.Undo] (" + undoDesc + ")", undoAction.getValue(Action.NAME));
assertTrue(undoAction.isEnabled());
}
if (redoDesc == null) {
assertEquals("[UndoRedoAction.OpenRocketDocument.Redo]", redoAction.getValue(Action.NAME));
assertFalse(redoAction.isEnabled());
} else {
assertEquals("[UndoRedoAction.OpenRocketDocument.Redo] (" + redoDesc + ")", redoAction.getValue(Action.NAME));
assertTrue(redoAction.isEnabled());
}
}
private void checkCgCp(double cgx, double mass, double cpx, double cna) {
final RigidBody launchData = MassCalculator.calculateLaunch(config);
final Coordinate cg = launchData.getCenterOfMass();
assertEquals(cgx, cg.x, 0.001);
assertEquals(mass, cg.weight, 0.0005);
final Coordinate cp = aeroCalc.getWorstCP(config, conditions, null);
assertEquals(cpx, cp.x, 0.001);
assertEquals(cna, cp.weight, 0.1);
}
private void checkAlt(double expected) throws SimulationException {
Simulation simulation = document.getSimulation(0);
double actual;
// Simulate + check altitude
simulation.simulate();
actual = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
assertEquals(expected, actual, 0.5);
}
private OpenRocketDocument loadRocket(String fileName) {
GeneralRocketLoader loader = new GeneralRocketLoader(new File(fileName));
InputStream is = this.getClass().getResourceAsStream(fileName);
String failMsg = String.format("Problem in unit test, cannot find %s", fileName);
assertNotNull(failMsg, is);
OpenRocketDocument rocketDoc = null;
try {
rocketDoc = loader.load(is);
} catch (RocketLoadException e) {
fail("RocketLoadException while loading file " + fileName + " : " + e.getMessage());
}
try {
is.close();
} catch (IOException e) {
fail("Unable to close input stream for file " + fileName + ": " + e.getMessage());
}
return rocketDoc;
}
}