1
0
Fork 0

FlightPlans: fix save/load of restrictions, deleting legs from Nasal

- Fix Leg ownership to avoid segfaults when deleting them via Nasal
- Modify FP save/load code so restrictions and holds are correctly
  saved and restored

Extend the tests to cover these cases.
This commit is contained in:
James Turner 2020-03-10 14:00:40 +00:00
parent de172de5b3
commit 3f14d53286
10 changed files with 326 additions and 96 deletions

View file

@ -75,6 +75,9 @@ const string_list static_icaoFlightTypeCode = {
namespace flightgear {
// implemented in route.cxx
const char* restrictionToString(RouteRestriction aRestrict);
typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
static FPDelegateFactoryVec static_delegateFactories;
@ -109,11 +112,6 @@ FlightPlan::~FlightPlan()
delete d;
}
}
// delete legs
for (auto l : _legs) {
delete l;
}
}
FlightPlan* FlightPlan::clone(const string& newIdent) const
@ -160,7 +158,7 @@ string FlightPlan::ident() const
return _ident;
}
FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
FlightPlan::LegRef FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
{
if (!aWpt) {
return nullptr;
@ -189,9 +187,7 @@ void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
index = _legs.size();
}
LegVec::iterator it = _legs.begin();
it += index;
auto it = _legs.begin() + index;
int endIndex = index + wps.size() - 1;
if (_currentIndex >= endIndex) {
_currentIndex += wps.size();
@ -199,7 +195,7 @@ void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
LegVec newLegs;
for (WayptRef wp : wps) {
newLegs.push_back(new Leg(this, wp));
newLegs.push_back(LegRef{new Leg(this, wp)});
}
lockDelegates();
@ -223,12 +219,11 @@ void FlightPlan::deleteIndex(int aIndex)
lockDelegates();
_waypointsChanged = true;
LegVec::iterator it = _legs.begin();
it += index;
Leg* l = *it;
auto it = _legs.begin() + index;
LegRef l = *it;
_legs.erase(it);
delete l;
l->_parent = nullptr; // orphan the leg so it's clear from Nasal
if (_currentIndex == index) {
// current waypoint was removed
_currentWaypointChanged = true;
@ -255,43 +250,18 @@ void FlightPlan::clear()
_cruiseDataChanged = true;
_currentIndex = -1;
for (Leg* l : _legs) {
delete l;
}
_legs.clear();
notifyCleared();
unlockDelegates();
}
class RemoveWithFlag
{
public:
RemoveWithFlag(WayptFlag f) : flag(f), delCount(0) { }
int numDeleted() const { return delCount; }
bool operator()(FlightPlan::Leg* leg) const
{
if (leg->waypoint()->flag(flag)) {
delete leg;
++delCount;
return true;
}
return false;
}
private:
WayptFlag flag;
mutable int delCount;
};
int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
{
int count = 0;
// first pass, fix up currentIndex
for (int i=0; i<_currentIndex; ++i) {
Leg* l = _legs[i];
const auto& l = _legs.at(i);
if (l->waypoint()->flag(flag)) {
++count;
}
@ -311,13 +281,21 @@ int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
// and let the use re-activate.
// http://code.google.com/p/flightgear-bugs/issues/detail?id=1134
if (currentIsBeingCleared) {
SG_LOG(SG_GENERAL, SG_INFO, "currentIsBeingCleared:" << currentIsBeingCleared);
SG_LOG(SG_GENERAL, SG_INFO, "FlightPlan::clearWayptsWithFlag: currentIsBeingCleared:" << currentIsBeingCleared);
_currentIndex = -1;
}
// now delete and remove
RemoveWithFlag rf(flag);
auto it = std::remove_if(_legs.begin(), _legs.end(), rf);
int numDeleted = 0;
auto it = std::remove_if(_legs.begin(), _legs.end(),
[flag, &numDeleted](const LegRef& leg)
{
if (leg->waypoint()->flag(flag)) {
++numDeleted;
return true;
}
return false;
});
if (it == _legs.end()) {
return 0; // nothing was cleared, don't fire the delegate
}
@ -335,7 +313,7 @@ int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
}
unlockDelegates();
return rf.numDeleted();
return numDeleted;
}
bool FlightPlan::isActive() const
@ -407,14 +385,14 @@ int FlightPlan::findWayptIndex(const FGPositionedRef aPos) const
return -1;
}
FlightPlan::Leg* FlightPlan::currentLeg() const
FlightPlan::LegRef FlightPlan::currentLeg() const
{
if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
return nullptr;
return legAtIndex(_currentIndex);
}
FlightPlan::Leg* FlightPlan::previousLeg() const
FlightPlan::LegRef FlightPlan::previousLeg() const
{
if (_currentIndex <= 0) {
return nullptr;
@ -423,7 +401,7 @@ FlightPlan::Leg* FlightPlan::previousLeg() const
return legAtIndex(_currentIndex - 1);
}
FlightPlan::Leg* FlightPlan::nextLeg() const
FlightPlan::LegRef FlightPlan::nextLeg() const
{
if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
return nullptr;
@ -432,19 +410,19 @@ FlightPlan::Leg* FlightPlan::nextLeg() const
return legAtIndex(_currentIndex + 1);
}
FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
FlightPlan::LegRef FlightPlan::legAtIndex(int index) const
{
if ((index < 0) || (index >= numLegs())) {
throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
}
return _legs[index];
return _legs.at(index);
}
int FlightPlan::findLegIndex(const Leg *l) const
int FlightPlan::findLegIndex(const Leg* l) const
{
for (unsigned int i=0; i<_legs.size(); ++i) {
if (_legs[i] == l) {
if (_legs.at(i).get() == l) {
return i;
}
}
@ -778,8 +756,11 @@ void FlightPlan::saveToProperties(SGPropertyNode* d) const
// route nodes
SGPropertyNode* routeNode = d->getChild("route", 0, true);
for (unsigned int i=0; i<_legs.size(); ++i) {
Waypt* wpt = _legs[i]->waypoint();
wpt->saveAsNode(routeNode->getChild("wp", i, true));
auto leg = _legs.at(i);
Waypt* wpt = leg->waypoint();
auto legNode = routeNode->getChild("wp", i, true);
wpt->saveAsNode(legNode);
leg->writeToProperties(legNode);
} // of waypoint iteration
}
@ -1119,7 +1100,20 @@ void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
if (routeNode.valid()) {
for (auto wpNode : routeNode->getChildren("wp")) {
Leg* l = new Leg{this, Waypt::createFromProperties(this, wpNode)};
auto wp = Waypt::createFromProperties(this, wpNode);
LegRef l = new Leg{this, wp};
// sync leg restrictions with waypoint ones
if (wp->speedRestriction() != RESTRICT_NONE) {
l->setSpeed(wp->speedRestriction(), wp->speed());
}
if (wp->altitudeRestriction() != RESTRICT_NONE) {
l->setAltitude(wp->altitudeRestriction(), wp->altitudeFt());
}
if (wpNode->hasChild("hold-count")) {
l->setHoldCount(wpNode->getIntValue("hold-count"));
}
_legs.push_back(l);
} // of route iteration
}
@ -1135,7 +1129,7 @@ void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
for (int i=0; i<routeNode->nChildren(); ++i) {
SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
LegRef l = new Leg(this, parseVersion1XMLWaypt(wpNode));
_legs.push_back(l);
} // of route iteration
_waypointsChanged = true;
@ -1218,7 +1212,7 @@ bool FlightPlan::loadPlainTextFormat(const SGPath& path)
throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
}
_legs.push_back(new Leg(this, w));
_legs.push_back(LegRef{new Leg(this, w)});
} // of line iteration
} catch (sg_exception& e) {
SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path << "'. " << e.getMessage());
@ -1425,26 +1419,23 @@ void FlightPlan::activate()
_currentIndex = 0;
_currentWaypointChanged = true;
for (unsigned int i=0; i < _legs.size(); ) {
for (unsigned int i=1; i < _legs.size(); ) {
if (_legs[i]->waypoint()->type() == "via") {
WayptRef preceeding = _legs[i - 1]->waypoint();
Via* via = static_cast<Via*>(_legs[i]->waypoint());
WayptVec wps = via->expandToWaypoints(preceeding);
// delete the VIA leg
LegVec::iterator it = _legs.begin();
it += i;
Leg* l = *it;
auto it = _legs.begin() + i;
LegRef l = *it;
_legs.erase(it);
delete l;
// create new legs and insert
it = _legs.begin();
it += i;
it = _legs.begin() + i;
LegVec newLegs;
for (WayptRef wp : wps) {
newLegs.push_back(new Leg(this, wp));
newLegs.push_back(LegRef{new Leg(this, wp)});
}
_waypointsChanged = true;
@ -1630,6 +1621,28 @@ int FlightPlan::Leg::holdCount() const
return _holdCount;
}
void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const
{
if (_speedRestrict != RESTRICT_NONE) {
aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
if (_speedRestrict == SPEED_RESTRICT_MACH) {
aProp->setDoubleValue("speed", speedMach());
} else {
aProp->setDoubleValue("speed", _speed);
}
}
if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
aProp->setDoubleValue("altitude-ft", _altitudeFt);
}
if (_holdCount > 0) {
aProp->setDoubleValue("hold-count", _holdCount);
}
}
void FlightPlan::rebuildLegData()
{
_totalDistance = 0.0;

View file

@ -80,7 +80,7 @@ public:
/**
* flight-plan leg encapsulation
*/
class Leg
class Leg : public SGReferenced
{
public:
FlightPlan* owner() const
@ -138,6 +138,8 @@ public:
Leg* cloneFor(FlightPlan* owner) const;
void writeToProperties(SGPropertyNode* node) const;
const FlightPlan* _parent;
RouteRestriction _speedRestrict = RESTRICT_NONE,
_altRestrict = RESTRICT_NONE;
@ -157,6 +159,8 @@ public:
/// total distance of this leg from departure point
mutable double _distanceAlongPath = 11.0;
};
using LegRef = SGSharedPtr<Leg>;
class Delegate
{
@ -191,7 +195,7 @@ public:
bool _deleteWithPlan = false;
};
Leg* insertWayptAtIndex(Waypt* aWpt, int aIndex);
LegRef insertWayptAtIndex(Waypt* aWpt, int aIndex);
void insertWayptsAtIndex(const WayptVec& wps, int aIndex);
void deleteIndex(int index);
@ -211,15 +215,14 @@ public:
bool isActive() const;
Leg* currentLeg() const;
Leg* nextLeg() const;
Leg* previousLeg() const;
LegRef currentLeg() const;
LegRef nextLeg() const;
LegRef previousLeg() const;
int numLegs() const
{ return static_cast<int>(_legs.size()); }
Leg* legAtIndex(int index) const;
int findLegIndex(const Leg* l) const;
LegRef legAtIndex(int index) const;
int findWayptIndex(const SGGeod& aPos) const;
int findWayptIndex(const FGPositionedRef aPos) const;
@ -373,6 +376,8 @@ public:
private:
friend class Leg;
int findLegIndex(const Leg* l) const;
void lockDelegates();
void unlockDelegates();
@ -426,7 +431,7 @@ private:
double _totalDistance;
void rebuildLegData();
typedef std::vector<Leg*> LegVec;
using LegVec = std::vector<LegRef>;
LegVec _legs;
std::vector<Delegate*> _delegates;

View file

@ -175,7 +175,7 @@ static RouteRestriction restrictionFromString(const char* aStr)
"Route restrictFromString");
}
static const char* restrictionToString(RouteRestriction aRestrict)
const char* restrictionToString(RouteRestriction aRestrict)
{
switch (aRestrict) {
case RESTRICT_AT: return "at";

View file

@ -235,7 +235,9 @@ static void wayptGhostDestroy(void* g)
static void legGhostDestroy(void* g)
{
// nothing for now
FlightPlan::Leg* leg = (FlightPlan::Leg*) g;
if (!FlightPlan::Leg::put(leg)) // unref
delete leg;
}
@ -382,6 +384,7 @@ naRef ghostForLeg(naContext c, const FlightPlan::Leg* leg)
return naNil();
}
FlightPlan::Leg::get(leg); // take a ref
return naNewGhost2(c, &FPLegGhostType, (void*) leg);
}
@ -3143,7 +3146,7 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
static naRef f_leg_path(naContext c, naRef me, int argc, naRef* args)
{
FlightPlan::Leg* leg = fpLegGhost(me);
if (!leg) {
if (!leg || !leg->owner()) {
naRuntimeError(c, "leg.setAltitude called on non-flightplan-leg object");
}
@ -3165,7 +3168,7 @@ static naRef f_leg_path(naContext c, naRef me, int argc, naRef* args)
static naRef f_leg_courseAndDistanceFrom(naContext c, naRef me, int argc, naRef* args)
{
FlightPlan::Leg* leg = fpLegGhost(me);
if (!leg) {
if (!leg || !leg->owner()) {
naRuntimeError(c, "leg.courseAndDistanceFrom called on non-flightplan-leg object");
}

View file

@ -2,6 +2,7 @@ set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_flightplan.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_fpNasal.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_navaids2.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_aircraftPerformance.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_routeManager.cxx
@ -11,6 +12,7 @@ set(TESTSUITE_SOURCES
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_flightplan.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_fpNasal.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_aircraftPerformance.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_routeManager.hxx
PARENT_SCOPE

View file

@ -21,9 +21,11 @@
#include "test_navaids2.hxx"
#include "test_aircraftPerformance.hxx"
#include "test_routeManager.hxx"
#include "test_fpNasal.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(FlightplanTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(FPNasalTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NavaidsTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AircraftPerformanceTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(RouteManagerTests, "Unit tests");

View file

@ -25,12 +25,9 @@ void FlightplanTests::setUp()
FGTestApi::setUp::initTestGlobals("flightplan");
FGTestApi::setUp::initNavDataCache();
globals->get_subsystem_mgr()->init();
FGTestApi::setUp::initStandardNasal();
globals->get_subsystem_mgr()->postinit();
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
}
@ -61,7 +58,7 @@ void FlightplanTests::testBasic()
CPPUNIT_ASSERT(fp1->destinationAirport()->ident() == "EHAM");
CPPUNIT_ASSERT(fp1->destinationRunway()->ident() == "24");
CPPUNIT_ASSERT_EQUAL(fp1->numLegs(), 5);
CPPUNIT_ASSERT_EQUAL(5, fp1->numLegs());
CPPUNIT_ASSERT(fp1->legAtIndex(0)->waypoint()->source()->ident() == "23L");
@ -328,15 +325,72 @@ void FlightplanTests::testBug1814()
CPPUNIT_ASSERT_DOUBLES_EQUAL(101, f->legAtIndex(2)->distanceNm(), 0.5);
}
void FlightplanTests::testSegfaultWaypointGhost() {
// checking for a segfault here, no segfault indicates success. A runtime error in the log is acceptable here.
bool ok = FGTestApi::executeNasal(R"(
var fp = createFlightplan();
fp.departure = airportinfo("BIKF");
fp.destination = airportinfo("EGLL");
var wp = fp.getWP(1);
fp.deleteWP(1);
print(wp.wp_name);
)");
void FlightplanTests::testLoadSaveMachRestriction()
{
const std::string fpXML = R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">SAWG</airport>
<runway type="string">25</runway>
</departure>
<destination>
<airport type="string">SUMU</airport>
</destination>
<route>
<wp n="0">
<type type="string">navaid</type>
<ident type="string">PUGLI</ident>
<lon type="double">-60.552200</lon>
<lat type="double">-40.490000</lat>
</wp>
<wp n="1">
<type type="string">basic</type>
<alt-restrict type="string">at</alt-restrict>
<altitude-ft type="double">36000</altitude-ft>
<speed-restrict type="string">mach</speed-restrict>
<speed type="double">1.24</speed>
<ident type="string">SV002</ident>
<lon type="double">-115.50531</lon>
<lat type="double">37.89523</lat>
</wp>
<wp n="2">
<type type="string">navaid</type>
<ident type="string">SIGUL</ident>
<lon type="double">-60.552200</lon>
<lat type="double">-40.490000</lat>
</wp>
</route>
</PropertyList>
)";
std::istringstream stream(fpXML);
FlightPlanRef f = new FlightPlan;
bool ok = f->load(stream);
CPPUNIT_ASSERT(ok);
auto leg = f->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(SPEED_RESTRICT_MACH, leg->speedRestriction());
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.24, leg->speedMach(), 0.01);
auto firstLeg = f->legAtIndex(0);
firstLeg->setSpeed(SPEED_RESTRICT_MACH, 1.56);
// upgrade to a hold and set the count
f->legAtIndex(2)->setHoldCount(8);
// round trip through XML to check :)
std::ostringstream ss;
f->save(ss);
std::istringstream iss(ss.str());
FlightPlanRef f2 = new FlightPlan;
ok = f2->load(iss);
CPPUNIT_ASSERT(ok);
auto leg3 = f2->legAtIndex(0);
CPPUNIT_ASSERT_EQUAL(SPEED_RESTRICT_MACH, leg3->speedRestriction());
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.56, leg3->speedMach(), 0.01);
CPPUNIT_ASSERT_EQUAL(8, f2->legAtIndex(2)->holdCount());
}

View file

@ -38,10 +38,10 @@ class FlightplanTests : public CppUnit::TestFixture
CPPUNIT_TEST(testBasicAirways);
CPPUNIT_TEST(testAirwayNetworkRoute);
CPPUNIT_TEST(testBug1814);
CPPUNIT_TEST(testSegfaultWaypointGhost);
CPPUNIT_TEST(testRoutPathWpt0Midflight);
CPPUNIT_TEST(testRoutePathVec);
CPPUNIT_TEST(testLoadSaveMachRestriction);
// CPPUNIT_TEST(testParseICAORoute);
// CPPUNIT_TEST(testParseICANLowLevelRoute);
CPPUNIT_TEST_SUITE_END();
@ -63,9 +63,9 @@ public:
void testParseICAORoute();
void testParseICANLowLevelRoute();
void testBug1814();
void testSegfaultWaypointGhost();
void testRoutPathWpt0Midflight();
void testRoutePathVec();
void testLoadSaveMachRestriction();
};
#endif // FG_FLIGHTPLAN_UNIT_TESTS_HXX

View file

@ -0,0 +1,105 @@
#include "test_fpNasal.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <simgear/misc/strutils.hxx>
#include <Navaids/FlightPlan.hxx>
#include <Navaids/routePath.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/waypoint.hxx>
#include <Navaids/navlist.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/airways.hxx>
#include <Navaids/fix.hxx>
#include <Airports/airport.hxx>
#include <Autopilot/route_mgr.hxx>
using namespace flightgear;
// Set up function for each test.
void FPNasalTests::setUp()
{
FGTestApi::setUp::initTestGlobals("flightplan");
FGTestApi::setUp::initNavDataCache();
// flightplan() acces needs the route manager
globals->add_new_subsystem<FGRouteMgr>();
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
FGTestApi::setUp::initStandardNasal();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void FPNasalTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
static FlightPlanRef makeTestFP(const std::string& depICAO, const std::string& depRunway,
const std::string& destICAO, const std::string& destRunway,
const std::string& waypoints)
{
FlightPlanRef f = new FlightPlan;
FGTestApi::setUp::populateFPWithNasal(f, depICAO, depRunway, destICAO, destRunway, waypoints);
return f;
}
void FPNasalTests::testBasic()
{
FlightPlanRef fp1 = makeTestFP("EGCC", "23L", "EHAM", "24",
"TNT CLN");
fp1->setIdent("testplan");
// setup the FP on the route-manager, so flightplan() call works
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
rm->activate();
// modify leg data dfrom Nasal
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setAltitude(6000, 'AT');
)");
CPPUNIT_ASSERT(ok);
// check the value updated in the leg
CPPUNIT_ASSERT_EQUAL(RESTRICT_AT, fp1->legAtIndex(3)->altitudeRestriction());
CPPUNIT_ASSERT_EQUAL(6000, fp1->legAtIndex(3)->altitudeFt());
// insert some waypoints from Nasal
ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
var leg = fp.getWP(2);
var newWP = createWPFrom(navinfo(leg.lat, leg.lon, 'COA')[0]);
fp.insertWPAfter(newWP, 2);
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(string{"COSTA VOR-DME"}, fp1->legAtIndex(3)->waypoint()->source()->name());
}
void FPNasalTests::testSegfaultWaypointGhost()
{
// checking for a segfault here, no segfault indicates success. A runtime error in the log is acceptable here.
bool ok = FGTestApi::executeNasal(R"(
var fp = createFlightplan();
fp.departure = airportinfo("BIKF");
fp.destination = airportinfo("EGLL");
var wp = fp.getWP(1);
fp.deleteWP(1);
print(wp.wp_name);
)");
CPPUNIT_ASSERT(ok);
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2018 Edward d'Auvergne
*
* This file is part of the program FlightGear.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The flight plan unit tests.
class FPNasalTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(FPNasalTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testSegfaultWaypointGhost);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBasic();
void testSegfaultWaypointGhost();
};