1
0
Fork 0

Fix FlightPlan procedure transition handling

Overhaul how transitions are stored in FlightPlan XML, and how
they’re exposed to Nasal. Simplify the Nasal access by making
‘sid_trans’ and ‘star_trans’ writeable.

Extend the unit-tests a lot to cover this, both from C++ and also
from Nasal

As part of this, overhaul the ownership of FlightPlan delegate
factories, to make it safer (use ref-counting of the factories,
and allow the factory to customise delegate clean-up behaviour)
This commit is contained in:
James Turner 2020-05-21 22:32:28 +01:00
parent 5f5a9d2a5e
commit fffcd14362
14 changed files with 846 additions and 148 deletions

View file

@ -998,10 +998,13 @@ flightgear::Transition* FGAirport::selectSIDByEnrouteTransition(FGPositioned* en
return nullptr;
}
Transition *FGAirport::selectSIDByTransition(const string &aIdent) const
Transition *FGAirport::selectSIDByTransition(const FGRunway* runway, const string &aIdent) const
{
loadProcedures();
for (auto sid : mSIDs) {
if (runway && !sid->isForRunway(runway))
continue;
auto trans = sid->findTransitionByName(aIdent);
if (trans) {
return trans;
@ -1022,10 +1025,13 @@ flightgear::Transition* FGAirport::selectSTARByEnrouteTransition(FGPositioned* e
return nullptr;
}
Transition *FGAirport::selectSTARByTransition(const string &aIdent) const
Transition *FGAirport::selectSTARByTransition(const FGRunway* runway, const string &aIdent) const
{
loadProcedures();
for (auto star : mSTARs) {
if (runway && !star->isForRunway(runway))
continue;
auto trans = star->findTransitionByName(aIdent);
if (trans) {
return trans;

View file

@ -249,7 +249,7 @@ class FGAirport : public FGPositioned
flightgear::SIDList getSIDs() const;
flightgear::Transition* selectSIDByEnrouteTransition(FGPositioned* enroute) const;
flightgear::Transition* selectSIDByTransition(const std::string& aIdent) const;
flightgear::Transition* selectSIDByTransition(const FGRunway* runway, const std::string& aIdent) const;
unsigned int numSTARs() const;
flightgear::STAR* getSTARByIndex(unsigned int aIndex) const;
@ -257,7 +257,7 @@ class FGAirport : public FGPositioned
flightgear::STARList getSTARs() const;
flightgear::Transition* selectSTARByEnrouteTransition(FGPositioned* enroute) const;
flightgear::Transition* selectSTARByTransition(const std::string& aIdent) const;
flightgear::Transition* selectSTARByTransition(const FGRunway* runway, const std::string& aIdent) const;
unsigned int numApproaches() const;
flightgear::Approach* getApproachByIndex(unsigned int aIndex) const;

View file

@ -78,7 +78,7 @@ namespace flightgear {
// implemented in route.cxx
const char* restrictionToString(RouteRestriction aRestrict);
typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
typedef std::vector<FlightPlan::DelegateFactoryRef> FPDelegateFactoryVec;
static FPDelegateFactoryVec static_delegateFactories;
FlightPlan::FlightPlan() :
@ -98,7 +98,7 @@ FlightPlan::FlightPlan() :
for (auto factory : static_delegateFactories) {
Delegate* d = factory->createFlightPlanDelegate(this);
if (d) { // factory might not always create a delegate
d->_deleteWithPlan = true;
d->_factory = factory; // record for clean-up purposes
addDelegate(d);
}
}
@ -106,10 +106,11 @@ FlightPlan::FlightPlan() :
FlightPlan::~FlightPlan()
{
// delete all delegates which we own.
// clean up delegates
for (auto d : _delegates) {
if (d->_deleteWithPlan) {
delete d;
if (d->_factory) {
auto f = d->_factory;
f->destroyFlightPlanDelegate(this, d);
}
}
}
@ -334,7 +335,6 @@ void FlightPlan::setCurrentIndex(int index)
lockDelegates();
_currentIndex = index;
_currentWaypointChanged = true;
_didLoadFP = true;
unlockDelegates();
}
@ -474,7 +474,7 @@ void FlightPlan::clearDeparture()
void FlightPlan::setSID(SID* sid, const std::string& transition)
{
if (sid == _sid) {
if ((sid == _sid) && (_sidTransition == transition)) {
return;
}
@ -574,7 +574,7 @@ void FlightPlan::setAlternate(FGAirportRef alt)
void FlightPlan::setSTAR(STAR* star, const std::string& transition)
{
if (_star == star) {
if ((_star == star) && (_starTransition == transition)) {
return;
}
@ -706,11 +706,9 @@ void FlightPlan::saveToProperties(SGPropertyNode* d) const
if (_departure) {
d->setStringValue("departure/airport", _departure->ident());
if (_sid) {
if (!_sidTransition.empty()) {
d->setStringValue("departure/sid", _sidTransition);
} else {
d->setStringValue("departure/sid", _sid->ident());
}
d->setStringValue("departure/sid", _sid->ident());
if (!_sidTransition.empty())
d->setStringValue("departure/sid_trans", _sidTransition);
}
if (_departureRunway) {
@ -721,11 +719,9 @@ void FlightPlan::saveToProperties(SGPropertyNode* d) const
if (_destination) {
d->setStringValue("destination/airport", _destination->ident());
if (_star) {
if (!_starTransition.empty()) {
d->setStringValue("destination/star", _starTransition);
} else {
d->setStringValue("destination/star", _star->ident());
}
d->setStringValue("destination/star", _star->ident());
if (!_starTransition.empty())
d->setStringValue("destination/star_trans", _starTransition);
}
if (_approach) {
@ -769,10 +765,12 @@ static bool anyWaypointsWithFlag(FlightPlan* plan, WayptFlag flag)
{
bool r = false;
plan->forEachLeg([&r, flag](FlightPlan::Leg* l) {
if (l->waypoint()->flags() && flag) {
if (l->waypoint()->flags() & flag) {
r = true;
}
});
return r;
}
bool FlightPlan::load(const SGPath& path)
@ -818,6 +816,8 @@ bool FlightPlan::load(const SGPath& path)
_cruiseDataChanged = true;
_waypointsChanged = true;
_didLoadFP = true;
unlockDelegates();
return Status;
@ -853,10 +853,22 @@ bool FlightPlan::load(std::istream &stream)
Status = false;
}
_arrivalChanged = false;
_departureChanged = false;
// we don't want to re-compute the arrival / departure after
// a load, since we assume the flight-plan had it specified already
// especially, the XML might have a SID/STAR embedded, which we don't
// want to lose
// however, we do want to run the normal delegate if no procedure was
// defined. We'll use the presence of waypoints tagged to decide
const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL);
const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE);
_arrivalChanged = !hasArrival;
_departureChanged = !hasDeparture;
_cruiseDataChanged = true;
_waypointsChanged = true;
_didLoadFP = true;
unlockDelegates();
return Status;
@ -1052,13 +1064,13 @@ void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
}
if (dep->hasChild("sid")) {
// previously, we would write a transition id for 'SID' if set,
// but this is ambigous. Starting with 2020.2, we only every try
// to parse this value as a SID, and look for a seperate sid_trans
// value
const string trans = dep->getStringValue("sid_trans");
const auto sid = dep->getStringValue("sid");
auto trans = _departure->selectSIDByTransition(sid);
if (trans) {
setSID(trans);
} else {
setSID(_departure->findSIDWithIdent(sid));
}
setSID(_departure->findSIDWithIdent(sid), trans);
}
}
}
@ -1074,14 +1086,12 @@ void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
}
if (dst->hasChild("star")) {
// prior to 2020.2 we would attempt to treat 'star' as a
// transiiton ID, but this is ambiguous. Look for a seperate value now
const auto star = dst->getStringValue("star");
auto trans = _destination->selectSTARByTransition(star);
if (trans) {
setSTAR(trans);
} else {
setSTAR(_destination->findSTARWithIdent(star));
}
}
const string trans = dst->getStringValue("star_trans");
setSTAR(_destination->findSTARWithIdent(star), trans);
} // of STAR processing
if (dst->hasChild("approach")) {
setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
@ -1758,7 +1768,7 @@ void FlightPlan::unlockDelegates()
--_delegateLock;
}
void FlightPlan::registerDelegateFactory(DelegateFactory* df)
void FlightPlan::registerDelegateFactory(DelegateFactoryRef df)
{
auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
if (it != static_delegateFactories.end()) {
@ -1768,7 +1778,7 @@ void FlightPlan::registerDelegateFactory(DelegateFactory* df)
static_delegateFactories.push_back(df);
}
void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
void FlightPlan::unregisterDelegateFactory(DelegateFactoryRef df)
{
auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
if (it == static_delegateFactories.end()) {
@ -2143,4 +2153,65 @@ void FlightPlan::forEachLeg(const LegVisitor& lv)
std::for_each(_legs.begin(), _legs.end(), lv);
}
int FlightPlan::indexOfFirstNonDepartureWaypoint() const
{
const auto numLegs = _legs.size();
for (int i = 0; i < numLegs; ++i) {
if (!(_legs.at(i)->waypoint()->flags() & WPT_DEPARTURE))
return i ;
}
// all waypoints are marked as departure
return -1;
}
int FlightPlan::indexOfFirstArrivalWaypoint() const
{
const auto numLegs = _legs.size();
for (int i = 0; i < numLegs; ++i) {
if (_legs.at(i)->waypoint()->flags() & WPT_ARRIVAL)
return i;
}
// no waypoints are marked as arrival
return -1;
}
int FlightPlan::indexOfFirstApproachWaypoint() const
{
const auto numLegs = _legs.size();
for (int i = 0; i < numLegs; ++i) {
if (_legs.at(i)->waypoint()->flags() & WPT_APPROACH)
return i;
}
// no waypoints are marked as arrival
return -1;
}
int FlightPlan::indexOfDestinationRunwayWaypoint() const
{
if (!_destinationRunway)
return -1;
// work backwards in case the departure and destination match
// this way we'll find the one we want
for (int i = numLegs() - 1; i >= 0; i--) {
if (_legs.at(i)->waypoint()->source() == _destinationRunway) {
return i;
}
}
return -1;
}
void FlightPlan::DelegateFactory::destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d)
{
// mimic old behaviour before destroyFlightPlanDelegate was added
SG_UNUSED(fp);
delete d;
}
} // of namespace flightgear

View file

@ -164,6 +164,9 @@ public:
using LegRef = SGSharedPtr<Leg>;
class DelegateFactory;
using DelegateFactoryRef = std::shared_ptr<DelegateFactory>;
class Delegate
{
public:
@ -195,8 +198,9 @@ public:
private:
friend class FlightPlan;
bool _deleteWithPlan = false;
// record the factory which created us, so we have the option to clean up
DelegateFactoryRef _factory;
};
LegRef insertWayptAtIndex(Waypt* aWpt, int aIndex);
@ -231,6 +235,11 @@ public:
int findWayptIndex(const SGGeod& aPos) const;
int findWayptIndex(const FGPositionedRef aPos) const;
int indexOfFirstNonDepartureWaypoint() const;
int indexOfFirstArrivalWaypoint() const;
int indexOfFirstApproachWaypoint() const;
int indexOfDestinationRunwayWaypoint() const;
bool load(const SGPath& p);
bool save(const SGPath& p) const;
@ -370,10 +379,11 @@ public:
{
public:
virtual Delegate* createFlightPlanDelegate(FlightPlan* fp) = 0;
virtual void destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d);
};
static void registerDelegateFactory(DelegateFactory* df);
static void unregisterDelegateFactory(DelegateFactory* df);
static void registerDelegateFactory(DelegateFactoryRef df);
static void unregisterDelegateFactory(DelegateFactoryRef df);
void addDelegate(Delegate* d);
void removeDelegate(Delegate* d);

View file

@ -916,12 +916,12 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
if (naIsString(value)) {
const std::string s(naStr_data(value));
FGAirport* apt = fp->departureAirport();
auto trans = apt->selectSIDByTransition(s);
if (trans) {
fp->setSID(trans);
} else {
fp->setSID(apt->findSIDWithIdent(s));
auto sid = apt->findSIDWithIdent(s);
if (!sid) {
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
}
fp->setSID(sid);
return;
}
@ -930,7 +930,43 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
return;
}
naRuntimeError(c, "bad argument type setting SID");
naRuntimeError(c, "bad argument type setting SID");
} else if (!strcmp(fieldName, "sid_trans")) {
Procedure* proc = procedureGhost(value);
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
fp->setSID((Transition*) proc);
return;
}
if (naIsString(value)) {
const std::string s(naStr_data(value));
Transition* trans = nullptr;
if (fp->sid()) {
trans = fp->sid()->findTransitionByName(s);
if (!trans) {
naRuntimeError(c, "No such transition %s for SID %s at %s",
s.c_str(),
fp->sid()->ident().c_str(),
fp->departureAirport()->ident().c_str());
}
} else {
trans = fp->departureAirport()->selectSIDByTransition(fp->departureRunway(), s);
if (!trans) {
naRuntimeError(c, "Couldn't find SID transition to %s at %s",
s.c_str(),
fp->departureAirport()->ident().c_str());
}
}
if (trans) {
fp->setSID(trans);
}
return;
}
naRuntimeError(c, "bad argument type setting sid_trans");
} else if (!strcmp(fieldName, "star")) {
Procedure* proc = procedureGhost(value);
if (proc && (proc->type() == PROCEDURE_STAR)) {
@ -946,12 +982,11 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
if (naIsString(value)) {
const std::string s(naStr_data(value));
FGAirport* apt = fp->destinationAirport();
auto trans = apt->selectSTARByTransition(s);
if (trans) {
fp->setSTAR(trans);
} else {
fp->setSTAR(apt->findSTARWithIdent(s));
}
auto star = apt->findSTARWithIdent(s);
if (!star) {
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
}
fp->setSTAR(star);
return;
}
@ -961,6 +996,42 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
}
naRuntimeError(c, "bad argument type setting STAR");
} else if (!strcmp(fieldName, "star_trans")) {
Procedure* proc = procedureGhost(value);
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
fp->setSTAR((Transition*) proc);
return;
}
if (naIsString(value)) {
const std::string s(naStr_data(value));
Transition* trans = nullptr;
if (fp->star()) {
trans = fp->star()->findTransitionByName(s);
if (!trans) {
naRuntimeError(c, "No such transition %s for STAR %s at %s",
s.c_str(),
fp->star()->ident().c_str(),
fp->destinationAirport()->ident().c_str());
}
} else {
trans = fp->destinationAirport()->selectSTARByTransition(fp->destinationRunway(), s);
if (!trans) {
naRuntimeError(c, "Couldn't find STAR transition to %s at %s",
s.c_str(),
fp->destinationAirport()->ident().c_str());
}
}
if (trans) {
fp->setSTAR(trans);
}
return;
}
naRuntimeError(c, "bad argument type setting star_trans");
} else if (!strcmp(fieldName, "approach")) {
Procedure* proc = procedureGhost(value);
if (proc && Approach::isApproach(proc->type())) {
@ -2487,6 +2558,11 @@ public:
return result;
}
void destroyFlightPlanDelegate(FlightPlan* fp, FlightPlan::Delegate* d) override
{
delete d;
}
const std::string& id() const
{ return _id; }
private:
@ -2496,16 +2572,12 @@ private:
int _gcSaveKey;
};
static std::vector<NasalFPDelegateFactory*> static_nasalDelegateFactories;
static std::vector<FlightPlan::DelegateFactoryRef> static_nasalDelegateFactories;
void shutdownNasalPositioned()
{
std::vector<NasalFPDelegateFactory*>::iterator it;
for (it = static_nasalDelegateFactories.begin();
it != static_nasalDelegateFactories.end(); ++it)
{
FlightPlan::unregisterDelegateFactory(*it);
delete (*it);
for (auto f : static_nasalDelegateFactories) {
FlightPlan::unregisterDelegateFactory(f);
}
static_nasalDelegateFactories.clear();
}
@ -2519,15 +2591,17 @@ static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
const std::string delegateId = (argc > 1) ? naStr_data(args[1]) : std::string{};
if (!delegateId.empty()) {
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
[delegateId](FlightPlan::DelegateFactoryRef factory)
{
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
return nfpd->id() == delegateId;
});
if (it != static_nasalDelegateFactories.end()) {
naRuntimeError(c, "duplicate delegate ID at registerFlightPlanDelegate: %s", delegateId.c_str());
}
}
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0], delegateId);
auto factory = std::make_shared<NasalFPDelegateFactory>(args[0], delegateId);
FlightPlan::registerDelegateFactory(factory);
static_nasalDelegateFactories.push_back(factory);
return naNil();
@ -2541,12 +2615,13 @@ static naRef f_unregisterFPDelegate(naContext c, naRef me, int argc, naRef* args
const std::string delegateId = naStr_data(args[0]);
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
[delegateId](FlightPlan::DelegateFactoryRef factory) {
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
return nfpd->id() == delegateId;
});
if (it == static_nasalDelegateFactories.end()) {
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no de;egate with ID:" << delegateId);
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no delegate with ID:" << delegateId);
return naNil();
}

View file

@ -180,9 +180,6 @@ naRef initNasalUnitTestInSim(naRef nasalGlobals, naContext c)
unitTest.set("assert_equal", f_assert_equal);
unitTest.set("assert_doubles_equal", f_assert_doubles_equal);
// http.set("save", f_http_save);
// http.set("load", f_http_load);
globals->get_commands()->addCommand("nasal-test", &command_executeNasalTest);
globals->get_commands()->addCommand("nasal-test-dir", &command_executeNasalTestDir);

View file

@ -30,76 +30,85 @@
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include <cppunit/TestAssert.h>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#if 0
typedef nasal::Ghost<simgear::HTTP::Request_ptr> NasalRequest;
typedef nasal::Ghost<simgear::HTTP::FileRequestRef> NasalFileRequest;
typedef nasal::Ghost<simgear::HTTP::MemoryRequestRef> NasalMemoryRequest;
FGHTTPClient& requireHTTPClient(const nasal::ContextWrapper& ctx)
static naRef f_assert(const nasal::CallContext& ctx )
{
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
if( !http )
ctx.runtimeError("Failed to get HTTP subsystem");
return *http;
}
/**
* http.save(url, filename)
*/
static naRef f_http_save(const nasal::CallContext& ctx)
{
const std::string url = ctx.requireArg<std::string>(0);
// Check for write access to target file
const std::string filename = ctx.requireArg<std::string>(1);
const SGPath validated_path = fgValidatePath(filename, true);
if( validated_path.isNull() )
ctx.runtimeError("Access denied: can not write to %s", filename.c_str());
return ctx.to_nasal
(
requireHTTPClient(ctx).client()->save(url, validated_path.utf8Str())
);
}
/**
* http.load(url)
*/
static naRef f_http_load(const nasal::CallContext& ctx)
{
const std::string url = ctx.requireArg<std::string>(0);
return ctx.to_nasal( requireHTTPClient(ctx).client()->load(url) );
}
static naRef f_request_abort( simgear::HTTP::Request&,
const nasal::CallContext& ctx )
{
// we need a request_ptr for cancel, not a reference. So extract
// the me object from the context directly.
simgear::HTTP::Request_ptr req = ctx.from_nasal<simgear::HTTP::Request_ptr>(ctx.me);
requireHTTPClient(ctx).client()->cancelRequest(req);
bool pass = ctx.requireArg<bool>(0);
auto msg = ctx.getArg<string>(1, "assert failed:");
CppUnit::Asserter::failIf(!pass, "assertion failed:" + msg,
CppUnit::SourceLine{"Nasal source line", 0});
if (!pass) {
ctx.runtimeError(msg.c_str());
}
return naNil();
}
#endif
static naRef f_fail(const nasal::CallContext& ctx )
{
auto msg = ctx.getArg<string>(0);
CppUnit::Asserter::fail("assertion failed:" + msg,
CppUnit::SourceLine{"Nasal source line", 0});
ctx.runtimeError("Test failed: %s", msg.c_str());
return naNil();
}
static naRef f_assert_equal(const nasal::CallContext& ctx )
{
naRef argA = ctx.requireArg<naRef>(0);
naRef argB = ctx.requireArg<naRef>(1);
auto msg = ctx.getArg<string>(2, "assert_equal failed");
bool same = naEqual(argA, argB);
if (!same) {
string aStr = ctx.from_nasal<string>(argA);
string bStr = ctx.from_nasal<string>(argB);
msg += "; expected:" + aStr + ", actual:" + bStr;
ctx.runtimeError(msg.c_str());
}
return naNil();
}
static naRef f_assert_doubles_equal(const nasal::CallContext& ctx )
{
double argA = ctx.requireArg<double>(0);
double argB = ctx.requireArg<double>(1);
double tolerance = ctx.requireArg<double>(2);
auto msg = ctx.getArg<string>(3, "assert_doubles_equal failed");
const bool same = fabs(argA - argB) < tolerance;
if (!same) {
msg += "; expected:" + std::to_string(argA) + ", actual:" + std::to_string(argB);
// static_activeTest->failure = true;
// static_activeTest->failureMessage = msg;
ctx.runtimeError(msg.c_str());
}
return naNil();
}
//------------------------------------------------------------------------------
naRef initNasalUnitTestCppUnit(naRef globals, naContext c)
naRef initNasalUnitTestCppUnit(naRef nasalGlobals, naContext c)
{
nasal::Hash globals_module(globals, c),
unitTest = globals_module.createHash("unitTest");
// http.set("save", f_http_save);
// http.set("load", f_http_load);
nasal::Hash globals_module(nasalGlobals, c),
unitTest = globals_module.createHash("unitTest");
unitTest.set("assert", f_assert);
unitTest.set("fail", f_fail);
unitTest.set("assert_equal", f_assert_equal);
unitTest.set("assert_doubles_equal", f_assert_doubles_equal);
return naNil();
}

View file

@ -80,15 +80,18 @@ public:
// insert waypt for the dpearture runway
auto dr = new RunwayWaypt(thePlan->departureRunway(), thePlan);
dr->setFlag(WPT_DEPARTURE);
dr->setFlag(WPT_GENERATED);
thePlan->insertWayptAtIndex(dr, 0);
if (thePlan->sid()) {
WayptVec sidRoute;
bool ok = thePlan->sid()->route(thePlan->departureRunway(), nullptr, sidRoute);
bool ok = thePlan->sid()->route(thePlan->departureRunway(), thePlan->sidTransition(), sidRoute);
if (!ok)
throw sg_exception("failed to route via SID");
int insertIndex = 1;
for (auto w : sidRoute) {
w->setFlag(WPT_DEPARTURE);
w->setFlag(WPT_GENERATED);
thePlan->insertWayptAtIndex(w, insertIndex++);
}
}
@ -104,15 +107,18 @@ public:
// insert waypt for the destination runway
auto dr = new RunwayWaypt(thePlan->destinationRunway(), thePlan);
dr->setFlag(WPT_ARRIVAL);
thePlan->insertWayptAtIndex(dr, 0);
dr->setFlag(WPT_GENERATED);
auto leg = thePlan->insertWayptAtIndex(dr, -1);
if (thePlan->star()) {
WayptVec starRoute;
bool ok = thePlan->star()->route(thePlan->destinationRunway(), nullptr, starRoute);
bool ok = thePlan->star()->route(thePlan->destinationRunway(), thePlan->starTransition(), starRoute);
if (!ok)
throw sg_exception("failed to route via STAR");
int insertIndex = 1;
int insertIndex = leg->index();
for (auto w : starRoute) {
w->setFlag(WPT_ARRIVAL);
w->setFlag(WPT_GENERATED);
thePlan->insertWayptAtIndex(w, insertIndex++);
}
}
@ -201,7 +207,7 @@ void RNAVProcedureTests::testBasic()
void RNAVProcedureTests::testHeadingToAlt()
{
auto vhhh = FGAirport::findByIdent("VHHH");
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = new FlightPlan;
@ -219,7 +225,7 @@ void RNAVProcedureTests::testHeadingToAlt()
wp->setAltitude(4000, RESTRICT_ABOVE);
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
// FGTestApi::writeFlightPlanToKML(fp);
// FGTestApi::writeFlightPlanToKML(fp);
auto depRwy = fp->departureRunway();
@ -237,8 +243,10 @@ void RNAVProcedureTests::testHeadingToAlt()
pilot->setSpeedKts(200);
pilot->flyGPSCourse(m_gps);
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
// check we sequence to the heading-to-alt wp
bool ok = FGTestApi::runForTimeWithCheck(240.0, [fp] () {
bool ok = FGTestApi::runForTimeWithCheck(300.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
@ -410,7 +418,11 @@ void RNAVProcedureTests::testEGPH_TLA6C()
CPPUNIT_ASSERT_EQUAL(std::string{"D242H"}, d242Wpt->ident());
CPPUNIT_ASSERT_EQUAL(true, d242Wpt->flag(WPT_OVERFLIGHT));
CPPUNIT_ASSERT_EQUAL(std::string{"D346T"}, fp->legAtIndex(3)->waypoint()->ident());
const auto wp3Ident = fp->legAtIndex(3)->waypoint()->ident();
// depeding which versino fo the procedures we loaded, we can find
// one ID or the other
CPPUNIT_ASSERT((wp3Ident == "D346T") || (wp3Ident == "D345T"));
// FGTestApi::writeFlightPlanToKML(fp);
@ -547,10 +559,10 @@ void RNAVProcedureTests::testLFKC_AJO1R()
void RNAVProcedureTests::testTransitionsSID()
{
auto kjfk = FGAirport::findByIdent("kjfk");
//auto sid = kjfk->findSIDWithIdent("DEEZZ5.13L");
// the method used by nasal to search for a transition only accepts transition ID as argument
// - not the associated SID. I believe this is an issue. This code will try to load DEEZZ5.04L!
auto sid = kjfk->selectSIDByTransition("CANDR");
auto runway = kjfk->getRunwayByIdent("13L");
auto sid = kjfk->selectSIDByTransition(runway, "CANDR");
// procedures not loaded, abandon test
if (!sid)
return;
@ -566,7 +578,7 @@ void RNAVProcedureTests::testTransitionsSID()
FGTestApi::setUp::populateFPWithNasal(fp, "KJFK", "13L", "KCLE", "24R", "");
fp->setSID(sid);
CPPUNIT_ASSERT_EQUAL(7, fp->numLegs());
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
auto wp = fp->legAtIndex(6);
CPPUNIT_ASSERT_EQUAL(std::string{"CANDR"}, wp->waypoint()->ident());
CPPUNIT_ASSERT(rm->activate());
@ -575,8 +587,8 @@ void RNAVProcedureTests::testTransitionsSID()
void RNAVProcedureTests::testTransitionsSTAR()
{
auto kjfk = FGAirport::findByIdent("kjfk");
//auto star = kjfk->findSIDWithIdent("DEEZZ5.13L");
auto star = kjfk->selectSTARByTransition("SEY");
auto runway = kjfk->getRunwayByIdent("22L");
auto star = kjfk->selectSTARByTransition(runway, "SEY");
// procedures not loaded, abandon test
if (!star)
return;
@ -592,7 +604,7 @@ void RNAVProcedureTests::testTransitionsSTAR()
FGTestApi::setUp::populateFPWithNasal(fp, "KBOS", "22R", "KJFK", "22L", "");
fp->setSTAR(star);
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
CPPUNIT_ASSERT_EQUAL(9, fp->numLegs());
auto wp = fp->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(std::string{"SEY"}, wp->waypoint()->ident());
CPPUNIT_ASSERT(rm->activate());
@ -682,4 +694,4 @@ void RNAVProcedureTests::testIndexOf()
auto SAM = fp->legAtIndex(6)->waypoint();
CPPUNIT_ASSERT_EQUAL(southamptonVOR->ident(), SAM->ident());
CPPUNIT_ASSERT_EQUAL(6, fp->findWayptIndex(southamptonVOR));
}
}

View file

@ -4,6 +4,8 @@
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <Navaids/FlightPlan.hxx>
#include <Navaids/routePath.hxx>
@ -18,6 +20,102 @@
using namespace flightgear;
static bool static_haveProcedures = false;
/////////////////////////////////////////////////////////////////////////////
namespace {
class TestFPDelegate : public FlightPlan::Delegate
{
public:
TestFPDelegate(FlightPlan* plan) :
_plan(plan)
{}
virtual ~TestFPDelegate()
{
}
void sequence() override
{
}
void currentWaypointChanged() override
{
}
void departureChanged() override
{
sawDepartureChange = true;
}
void arrivalChanged() override
{
sawArrivalChange = true;
}
void loaded() override
{
didLoad = true;
}
FlightPlan* _plan = nullptr;
bool didLoad = false;
bool sawDepartureChange = false;
bool sawArrivalChange = false;
};
class TestFPDelegateFactory : public FlightPlan::DelegateFactory
{
public:
TestFPDelegateFactory() = default;
virtual ~TestFPDelegateFactory() = default;
FlightPlan::Delegate* createFlightPlanDelegate(FlightPlan* fp) override
{
auto d = new TestFPDelegate(fp);
_instances.push_back(d);
return d;
}
void destroyFlightPlanDelegate(FlightPlan* fp, FlightPlan::Delegate* d) override
{
auto it = std::find_if(_instances.begin(), _instances.end(), [d] (TestFPDelegate* fpd) {
return fpd == d;
});
CPPUNIT_ASSERT(it != _instances.end());
_instances.erase(it);
delete d;
}
static TestFPDelegate* delegateForPlan(FlightPlan* fp);
std::vector<TestFPDelegate*> _instances;
};
std::shared_ptr<TestFPDelegateFactory> static_factory;
TestFPDelegate* TestFPDelegateFactory::delegateForPlan(FlightPlan* fp)
{
auto it = std::find_if(static_factory->_instances.begin(),
static_factory->_instances.end(),
[fp] (TestFPDelegate* del) {
return del->_plan == fp;
});
if (it == static_factory->_instances.end())
return nullptr;
return *it;
}
} // of anonymous namespace
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void FlightplanTests::setUp()
@ -25,6 +123,12 @@ void FlightplanTests::setUp()
FGTestApi::setUp::initTestGlobals("flightplan");
FGTestApi::setUp::initNavDataCache();
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
if (proceduresPath.exists()) {
static_haveProcedures = true;
globals->append_fg_scenery(proceduresPath);
}
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
@ -35,6 +139,11 @@ void FlightplanTests::setUp()
void FlightplanTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
if (static_factory) {
FlightPlan::unregisterDelegateFactory(static_factory);
static_factory.reset();
}
}
static FlightPlanRef makeTestFP(const std::string& depICAO, const std::string& depRunway,
@ -506,3 +615,237 @@ void FlightplanTests::testRadialIntercept()
RoutePath rtepath(f);
CPPUNIT_ASSERT_DOUBLES_EQUAL(232, f->legAtIndex(3)->courseDeg(), 1.0);
}
void FlightplanTests::loadFGFPWithoutDepartureArrival()
{
static_factory = std::make_shared<TestFPDelegateFactory>();
FlightPlan::registerDelegateFactory(static_factory);
FlightPlanRef f = new FlightPlan;
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_without_dep_arr.fgfp";
{
sg_ofstream s(fgfpPath);
s << R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">EDDM</airport>
<runway type="string">08R</runway>
</departure>
<destination>
<airport type="string">EDDF</airport>
</destination>
<route>
<wp n="1">
<type type="string">navaid</type>
<ident type="string">GIVMI</ident>
<lon type="double">11.364700</lon>
<lat type="double">48.701100</lat>
</wp>
<wp n="2">
<type type="string">navaid</type>
<ident type="string">ERNAS</ident>
<lon type="double">11.219400</lon>
<lat type="double">48.844700</lat>
</wp>
<wp n="3">
<type type="string">navaid</type>
<ident type="string">TALAL</ident>
<lon type="double">11.085300</lon>
<lat type="double">49.108300</lat>
</wp>
<wp n="4">
<type type="string">navaid</type>
<ident type="string">ERMEL</ident>
<lon type="double">11.044700</lon>
<lat type="double">49.187800</lat>
</wp>
<wp n="5">
<type type="string">navaid</type>
<ident type="string">PSA</ident>
<lon type="double">9.348300</lon>
<lat type="double">49.862200</lat>
</wp>
</route>
</PropertyList>
)";
}
auto ourDelegate = TestFPDelegateFactory::delegateForPlan(f);
CPPUNIT_ASSERT(!ourDelegate->didLoad);
CPPUNIT_ASSERT(f->load(fgfpPath));
CPPUNIT_ASSERT(ourDelegate->didLoad);
CPPUNIT_ASSERT(ourDelegate->sawArrivalChange);
CPPUNIT_ASSERT(ourDelegate->sawDepartureChange);
}
void FlightplanTests::loadFGFPWithEmbeddedProcedures()
{
static_factory = std::make_shared<TestFPDelegateFactory>();
FlightPlan::registerDelegateFactory(static_factory);
FlightPlanRef f = new FlightPlan;
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_with_dep_arr.fgfp";
{
sg_ofstream s(fgfpPath);
s << R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">EDDM</airport>
<runway type="string">08R</runway>
</departure>
<destination>
<airport type="string">EDDF</airport>
</destination>
<route>
<wp>
<type type="string">runway</type>
<departure type="bool">true</departure>
<ident type="string">08R</ident>
<icao type="string">EDDM</icao>
</wp>
<wp n="1">
<type type="string">navaid</type>
<ident type="string">GIVMI</ident>
<lon type="double">11.364700</lon>
<lat type="double">48.701100</lat>
</wp>
<wp n="2">
<type type="string">navaid</type>
<ident type="string">ERNAS</ident>
<lon type="double">11.219400</lon>
<lat type="double">48.844700</lat>
</wp>
<wp n="3">
<type type="string">navaid</type>
<ident type="string">TALAL</ident>
<lon type="double">11.085300</lon>
<lat type="double">49.108300</lat>
</wp>
<wp n="4">
<type type="string">navaid</type>
<ident type="string">ERMEL</ident>
<lon type="double">11.044700</lon>
<lat type="double">49.187800</lat>
</wp>
<wp n="5">
<type type="string">navaid</type>
<ident type="string">PSA</ident>
<lon type="double">9.348300</lon>
<lat type="double">49.862200</lat>
</wp>
<wp>
<type type="string">runway</type>
<arrival type="bool">true</arrival>
<ident type="string">36</ident>
<icao type="string">EDDF</icao>
</wp>
</route>
</PropertyList>
)";
}
auto ourDelegate = TestFPDelegateFactory::delegateForPlan(f);
CPPUNIT_ASSERT(!ourDelegate->didLoad);
CPPUNIT_ASSERT(f->load(fgfpPath));
CPPUNIT_ASSERT(ourDelegate->didLoad);
CPPUNIT_ASSERT(!ourDelegate->sawArrivalChange);
CPPUNIT_ASSERT(!ourDelegate->sawDepartureChange);
}
void FlightplanTests::loadFGFPWithOldProcedures()
{
if (!static_haveProcedures)
return;
FlightPlanRef f = new FlightPlan;
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_old_procedure_idents.fgfp";
{
sg_ofstream s(fgfpPath);
s << R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">KJFK</airport>
<runway type="string">13L</runway>
<sid type="string">CANDR</sid>
</departure>
<destination>
<airport type="string">EHAM</airport>
<star type="string">BEDUM</star>
</destination>
<route>
<wp>
<type type="string">runway</type>
<arrival type="bool">true</arrival>
<ident type="string">36</ident>
<icao type="string">EDDF</icao>
</wp>
</route>
</PropertyList>
)";
}
auto kjfk = FGAirport::findByIdent("KJFK");
auto eham = FGAirport::findByIdent("EHAM");
CPPUNIT_ASSERT(f->load(fgfpPath));
CPPUNIT_ASSERT(f->sid() == nullptr);
CPPUNIT_ASSERT(f->star() == nullptr);
}
void FlightplanTests::loadFGFPWithProcedureIdents()
{
if (!static_haveProcedures)
return;
FlightPlanRef f = new FlightPlan;
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_procedure_idents.fgfp";
{
sg_ofstream s(fgfpPath);
s << R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">KJFK</airport>
<runway type="string">13L</runway>
<sid type="string">DEEZZ5.13L</sid>
<sid_trans type="string">CANDR</sid_trans>
</departure>
<destination>
<airport type="string">EHAM</airport>
<star type="string">EEL1A</star>
<star_trans type="string">KUBAT</star_trans>
</destination>
<route>
<wp>
<type type="string">runway</type>
<arrival type="bool">true</arrival>
<ident type="string">36</ident>
<icao type="string">EDDF</icao>
</wp>
</route>
</PropertyList>
)";
}
auto kjfk = FGAirport::findByIdent("KJFK");
auto eham = FGAirport::findByIdent("EHAM");
CPPUNIT_ASSERT(f->load(fgfpPath));
CPPUNIT_ASSERT_EQUAL(f->sid()->ident(), string{"DEEZZ5.13L"});
CPPUNIT_ASSERT_EQUAL(f->sidTransition()->ident(), string{"CANDR"});
CPPUNIT_ASSERT_EQUAL(f->star()->ident(), string{"EEL1A"});
CPPUNIT_ASSERT_EQUAL(f->starTransition()->ident(), string{"KUBAT"});
}

View file

@ -45,6 +45,10 @@ class FlightplanTests : public CppUnit::TestFixture
CPPUNIT_TEST(testBasicDiscontinuity);
CPPUNIT_TEST(testLeadingWPDynamic);
CPPUNIT_TEST(testRadialIntercept);
CPPUNIT_TEST(loadFGFPWithoutDepartureArrival);
CPPUNIT_TEST(loadFGFPWithEmbeddedProcedures);
CPPUNIT_TEST(loadFGFPWithOldProcedures);
CPPUNIT_TEST(loadFGFPWithProcedureIdents);
// CPPUNIT_TEST(testParseICAORoute);
// CPPUNIT_TEST(testParseICANLowLevelRoute);
@ -74,6 +78,10 @@ public:
void testOnlyDiscontinuityRoute();
void testLeadingWPDynamic();
void testRadialIntercept();
void loadFGFPWithoutDepartureArrival();
void loadFGFPWithEmbeddedProcedures();
void loadFGFPWithOldProcedures();
void loadFGFPWithProcedureIdents();
};
#endif // FG_FLIGHTPLAN_UNIT_TESTS_HXX

View file

@ -19,6 +19,7 @@
using namespace flightgear;
static bool static_haveProcedures = false;
// Set up function for each test.
void FPNasalTests::setUp()
@ -26,6 +27,12 @@ void FPNasalTests::setUp()
FGTestApi::setUp::initTestGlobals("flightplan");
FGTestApi::setUp::initNavDataCache();
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
if (proceduresPath.exists()) {
static_haveProcedures = true;
globals->append_fg_scenery(proceduresPath);
}
// flightplan() acces needs the route manager
globals->add_new_subsystem<FGRouteMgr>();
@ -103,3 +110,101 @@ void FPNasalTests::testSegfaultWaypointGhost()
)");
CPPUNIT_ASSERT(ok);
}
void FPNasalTests::testSIDTransitionAPI()
{
if (!static_haveProcedures) {
return;
}
auto rm = globals->get_subsystem<FGRouteMgr>();
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
fp.departure = airportinfo("KJFK");
fp.destination = airportinfo("EGLL");
var sid = fp.departure.getSid("DEEZZ5.13L");
unitTest.assert(sid != nil, "SID not found");
unitTest.assert_equal(sid.id, "DEEZZ5.13L", "Incorrect SID loaded");
var trans = sid.transition('CANDR');
unitTest.assert_equal(trans.id, "CANDR", "Couldn't find transition");
fp.sid = trans;
fp.departure_runway = fp.departure.runway('13L')
)");
CPPUNIT_ASSERT(ok);
auto fp = rm->flightPlan();
CPPUNIT_ASSERT(fp->departureRunway());
CPPUNIT_ASSERT(fp->sid());
CPPUNIT_ASSERT(fp->sidTransition());
CPPUNIT_ASSERT_EQUAL(fp->departureRunway()->ident(), string{"13L"});
CPPUNIT_ASSERT_EQUAL(fp->sid()->ident(), string{"DEEZZ5.13L"});
CPPUNIT_ASSERT_EQUAL(fp->sidTransition()->ident(), string{"CANDR"});
// test specify SID via transition in Nasal
rm->setFlightPlan(new FlightPlan{});
ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
fp.departure = airportinfo("KJFK");
fp.destination = airportinfo("EGLL");
fp.departure_runway = airportinfo("KJFK").runways["13L"];
fp.sid = airportinfo("KJFK").getSid("DEEZZ5.13L");
fp.sid_trans = "CANDR";
)");
CPPUNIT_ASSERT(ok);
fp = rm->flightPlan();
CPPUNIT_ASSERT(fp->departureRunway());
CPPUNIT_ASSERT(fp->sid());
CPPUNIT_ASSERT(fp->sidTransition());
CPPUNIT_ASSERT_EQUAL(fp->departureRunway()->ident(), string{"13L"});
CPPUNIT_ASSERT_EQUAL(fp->sid()->ident(), string{"DEEZZ5.13L"});
CPPUNIT_ASSERT_EQUAL(fp->sidTransition()->ident(), string{"CANDR"});
}
void FPNasalTests::testSTARTransitionAPI()
{
if (!static_haveProcedures) {
return;
}
auto rm = globals->get_subsystem<FGRouteMgr>();
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
fp.departure = airportinfo("EGLL");
fp.destination = airportinfo("EDDM");
var star = fp.destination.getStar("RIXE3A.26L");
unitTest.assert(star != nil, "STAR not found");
unitTest.assert_equal(star.id, "RIXE3A.26L", "Incorrect STAR loaded");
fp.star = star;
fp.destination_runway = fp.destination.runway('26L')
)");
CPPUNIT_ASSERT(ok);
auto fp = rm->flightPlan();
CPPUNIT_ASSERT(fp->star());
CPPUNIT_ASSERT(fp->starTransition() == nullptr);
CPPUNIT_ASSERT_EQUAL(fp->star()->ident(), string{"RIXE3A.26L"});
}

View file

@ -31,6 +31,8 @@ class FPNasalTests : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(FPNasalTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testSegfaultWaypointGhost);
CPPUNIT_TEST(testSIDTransitionAPI);
CPPUNIT_TEST(testSTARTransitionAPI);
CPPUNIT_TEST_SUITE_END();
public:
@ -43,4 +45,6 @@ public:
// The tests.
void testBasic();
void testSegfaultWaypointGhost();
void testSIDTransitionAPI();
void testSTARTransitionAPI();
};

View file

@ -23,6 +23,8 @@
using namespace flightgear;
static bool static_haveProcedures = false;
static FlightPlanRef makeTestFP(const std::string& depICAO, const std::string& depRunway,
const std::string& destICAO, const std::string& destRunway,
const std::string& waypoints)
@ -39,6 +41,12 @@ void RouteManagerTests::setUp()
FGTestApi::setUp::initTestGlobals("routemanager");
FGTestApi::setUp::initNavDataCache();
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
if (proceduresPath.exists()) {
static_haveProcedures = true;
globals->append_fg_scenery(proceduresPath);
}
globals->add_new_subsystem<FGRouteMgr>();
// setup the default GPS, which is needed for waypoint
@ -676,3 +684,50 @@ void RouteManagerTests::loadFGFP()
CPPUNIT_ASSERT_EQUAL(std::string{"EDDF"}, wp2->waypoint()->ident());
}
void RouteManagerTests::testRouteWithProcedures()
{
if (!static_haveProcedures)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = new FlightPlan;
rm->setFlightPlan(f);
auto kjfk = FGAirport::findByIdent("KJFK");
auto eham = FGAirport::findByIdent("EHAM");
f->setDeparture(kjfk->getRunwayByIdent("13L"));
f->setSID(kjfk->findSIDWithIdent("DEEZZ5.13L"), "CANDR");
f->setDestination(eham->getRunwayByIdent("18R"));
f->setSTAR(eham->findSTARWithIdent("EEL1A"), "BEDUM");
f->setApproach(eham->findApproachWithIdent("VDM18R"));
auto w = f->waypointFromString("TOMYE");
f->insertWayptAtIndex(w, f->indexOfFirstNonDepartureWaypoint());
auto w2 = f->waypointFromString("DEVOL");
f->insertWayptAtIndex(w2, f->indexOfFirstArrivalWaypoint());
// let's check what we got
auto endOfSID = f->legAtIndex(f->indexOfFirstNonDepartureWaypoint() - 1);
CPPUNIT_ASSERT_EQUAL(endOfSID->waypoint()->ident(), string{"CANDR"});
auto startOfSTAR = f->legAtIndex(f->indexOfFirstArrivalWaypoint());
CPPUNIT_ASSERT_EQUAL(startOfSTAR->waypoint()->ident(), string{"BEDUM"});
auto endOfSTAR = f->legAtIndex(f->indexOfFirstApproachWaypoint() - 1);
CPPUNIT_ASSERT_EQUAL(endOfSTAR->waypoint()->ident(), string{"ARTIP"});
auto startOfApproach = f->legAtIndex(f->indexOfFirstApproachWaypoint());
CPPUNIT_ASSERT_EQUAL(startOfApproach->waypoint()->ident(), string{"D070O"});
auto landingRunway = f->legAtIndex(f->indexOfDestinationRunwayWaypoint());
CPPUNIT_ASSERT(landingRunway->waypoint()->source() == f->destinationRunway());
auto firstMiss = f->legAtIndex(f->indexOfDestinationRunwayWaypoint() + 1);
CPPUNIT_ASSERT_EQUAL(firstMiss->waypoint()->ident(), string{"(461)"});
}

View file

@ -42,6 +42,7 @@ class RouteManagerTests : public CppUnit::TestFixture
CPPUNIT_TEST(testHiddenWaypoints);
CPPUNIT_TEST(loadGPX);
CPPUNIT_TEST(loadFGFP);
CPPUNIT_TEST(testRouteWithProcedures);
CPPUNIT_TEST_SUITE_END();
@ -66,6 +67,8 @@ public:
void testHiddenWaypoints();
void loadGPX();
void loadFGFP();
void testRouteWithProcedures();
private:
GPS* m_gps = nullptr;
};