377 lines
12 KiB
Java
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;
|
|
}
|
|
|
|
}
|