first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_AIFlightPlan.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_AIManager.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_traffic.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_TrafficMgr.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_groundnet.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_submodels.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_VectorMath.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_AIFlightPlan.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_AIManager.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_traffic.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_TrafficMgr.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_groundnet.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_submodels.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_VectorMath.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/>.
*/
#include "test_AIFlightPlan.hxx"
#include "test_AIManager.hxx"
#include "test_groundnet.hxx"
#include "test_traffic.hxx"
#include "test_TrafficMgr.hxx"
#include "test_submodels.hxx"
#include "test_AIFlightPlan.hxx"
#include "test_VectorMath.hxx"
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AIFlightPlanTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AIManagerTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(GroundnetTests, "Unit tests");
// CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TrafficTests, "Unit tests");
// CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TrafficMgrTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SubmodelsTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(VectorMathTests, "Unit tests");

View File

@@ -0,0 +1,363 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/>.
*/
#include "test_AIFlightPlan.hxx"
#include <cstring>
#include <memory>
#include "config.h"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestDataLogger.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/AIManager.hxx>
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
using namespace flightgear;
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void AIFlightPlanTests::setUp()
{
FGTestApi::setUp::initTestGlobals("AI");
FGTestApi::setUp::initNavDataCache();
globals->add_new_subsystem<FGAIManager>(SGSubsystemMgr::GENERAL);
auto props = globals->get_props();
props->setBoolValue("sim/ai/enabled", true);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void AIFlightPlanTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void AIFlightPlanTests::testAIFlightPlan()
{
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
aiFP->setName("Bob");
aiFP->setRunway("24");
CPPUNIT_ASSERT_EQUAL(string{"Bob"}, aiFP->getName());
CPPUNIT_ASSERT_EQUAL(string{"24"}, aiFP->getRunway());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
FGPositioned::TypeFilter ty(FGPositioned::VOR);
auto cache = flightgear::NavDataCache::instance();
auto shannonVOR = cache->findClosestWithIdent("SHA", SGGeod::fromDeg(-8, 52), &ty);
CPPUNIT_ASSERT_EQUAL(string{"SHANNON VOR-DME"}, shannonVOR->name());
auto wp1 = new FGAIWaypoint;
wp1->setPos(shannonVOR->geod());
wp1->setName("testWp_0");
wp1->setOn_ground(true);
wp1->setGear_down(true);
wp1->setSpeed(100);
auto wp2 = new FGAIWaypoint;
const auto g1 = SGGeodesy::direct(shannonVOR->geod(), 10.0, SG_NM_TO_METER * 5.0);
wp2->setPos(g1);
wp2->setName("upInTheAir");
wp2->setOn_ground(false);
wp2->setGear_down(true);
wp2->setSpeed(150);
aiFP->addWaypoint(wp1);
aiFP->addWaypoint(wp2);
CPPUNIT_ASSERT_EQUAL(2, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, aiFP->getBearing(wp1, wp2), 0.1);
time_t startTime = 1498;
aiFP->setTime(startTime);
CPPUNIT_ASSERT(!aiFP->isActive(1400));
CPPUNIT_ASSERT(aiFP->isActive(1500));
aiFP->IncrementWaypoint(false);
CPPUNIT_ASSERT_EQUAL(2, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
auto wp3 = new FGAIWaypoint;
auto diganWpt = cache->findClosestWithIdent("DIGAN", shannonVOR->geod(), nullptr);
wp3->setPos(diganWpt->geod());
wp3->setName("overDIGAN");
wp3->setOn_ground(false);
wp3->setGear_down(false);
wp3->setSpeed(180);
// check that adding a waypoint doesn't mess up the iterators or
// current position
aiFP->addWaypoint(wp3);
CPPUNIT_ASSERT_EQUAL(3, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
auto p3 = SGGeodesy::direct(diganWpt->geod(), 45, SG_NM_TO_METER * 4);
p3.setElevationFt(12000);
auto wp4 = new FGAIWaypoint;
wp4->setPos(p3);
wp4->setName("passDIGAN");
wp4->setSpeed(200);
aiFP->addWaypoint(wp4);
auto ingur = cache->findClosestWithIdent("INGUR", shannonVOR->geod(), nullptr);
auto p4 = ingur->geod();
p4.setElevationFt(16000);
auto wp5 = new FGAIWaypoint;
wp5->setPos(p4);
wp5->setName("INGUR");
wp5->setSpeed(250);
aiFP->addWaypoint(wp5);
aiFP->IncrementWaypoint(false);
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
// let's increment to the end
aiFP->IncrementWaypoint(false);
aiFP->IncrementWaypoint(false);
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
// one more increment 'off the end'
aiFP->IncrementWaypoint(false);
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
// should put us back on the last waypoint
aiFP->DecrementWaypoint();
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
aiFP->DecrementWaypoint(); // back to wp4
aiFP->DecrementWaypoint(); // back to wp3
aiFP->DecrementWaypoint(); // back to wp2
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
// restart to the beginning
aiFP->restart();
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
// test increment with delete
aiFP->IncrementWaypoint(true);
CPPUNIT_ASSERT_EQUAL(5, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getNextWaypoint());
aiFP->IncrementWaypoint(true);
CPPUNIT_ASSERT_EQUAL(4, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getNextWaypoint());
aiFP->IncrementWaypoint(true);
CPPUNIT_ASSERT_EQUAL(3, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getNextWaypoint());
// let's run up to the end and check nothing explodes
aiFP->IncrementWaypoint(true);
CPPUNIT_ASSERT_EQUAL(2, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp4, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
aiFP->IncrementWaypoint(true);
CPPUNIT_ASSERT_EQUAL(1, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp5, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
}
void AIFlightPlanTests::testAIFlightPlanLeftCircle()
{
auto aiFP = new FGAIFlightPlan;
aiFP->setName("Bob");
aiFP->setRunway("24");
CPPUNIT_ASSERT_EQUAL(string{"Bob"}, aiFP->getName());
CPPUNIT_ASSERT_EQUAL(string{"24"}, aiFP->getRunway());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
FGPositioned::TypeFilter ty(FGPositioned::VOR);
auto cache = flightgear::NavDataCache::instance();
auto shannonVOR = cache->findClosestWithIdent("SHA", SGGeod::fromDeg(-8, 52), &ty);
CPPUNIT_ASSERT_EQUAL(string{"SHANNON VOR-DME"}, shannonVOR->name());
auto wp1 = new FGAIWaypoint;
wp1->setPos(shannonVOR->geod());
wp1->setName("testWp_0");
wp1->setOn_ground(true);
wp1->setGear_down(true);
wp1->setSpeed(10);
aiFP->addWaypoint(wp1);
auto lastWp = wp1;
int course = 0;
for(int i = 1; i <= 10; i++) {
auto wp = new FGAIWaypoint;
course += 10;
const auto g1 = SGGeodesy::direct(lastWp->getPos(), course, SG_NM_TO_METER * 5.0);
wp->setPos(g1);
wp->setName("testWp_" + std::to_string(i));
wp->setOn_ground(true);
wp->setGear_down(true);
wp->setSpeed(10);
aiFP->addWaypoint(wp);
lastWp = wp;
}
CPPUNIT_ASSERT_EQUAL(aiFP->getNrOfWayPoints(), 11);
}
void AIFlightPlanTests::testAIFlightPlanLoadXML()
{
const auto xml = R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<flightplan>
<wp>
<name>onGroundWP</name>
<lat>57</lat>
<lon>3</lon>
<ktas>10</ktas>
<on-ground>1</on-ground>
</wp>
<wp>
<name>someWP</name>
<lat>57</lat>
<lon>4</lon>
<ktas>200</ktas>
<alt>8000</alt>
</wp>
<wp>
<name>END</name>
</wp>
</flightplan>
</PropertyList>
)";
std::istringstream is(xml);
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
bool ok = aiFP->readFlightplan(is, sg_location("In-memory test_ai_fp.xml"));
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(false, aiFP->getCurrentWaypoint()->getInAir());
CPPUNIT_ASSERT_EQUAL(true, aiFP->getCurrentWaypoint()->getGear_down());
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, aiFP->getCurrentWaypoint()->getFlaps(), 0.1);
auto wp2 = aiFP->getNextWaypoint();
CPPUNIT_ASSERT_EQUAL(true, wp2->getInAir());
CPPUNIT_ASSERT_EQUAL(false, wp2->getGear_down());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, wp2->getFlaps(), 0.1);
}
void AIFlightPlanTests::testLeftTurnFlightplanXML()
{
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
const auto fpath = SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "AI"/"Flightplan"/"left_onground.xml";
std::fstream fs(fpath.c_str());
bool ok = aiFP->readFlightplan(fs, sg_location("In-memory test_ai_fp.xml"));
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(false, aiFP->getCurrentWaypoint()->getInAir());
auto wp2 = aiFP->getNextWaypoint();
CPPUNIT_ASSERT_EQUAL(false, wp2->getInAir());
CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, wp2->getSpeed(), 0.1);
}
void AIFlightPlanTests::testRightTurnFlightplanXML()
{
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
const auto fpath = SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "AI"/"Flightplan"/"right_onground.xml";
std::fstream fs(fpath.c_str());
bool ok = aiFP->readFlightplan(fs, sg_location("In-memory test_ai_fp.xml"));
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(false, aiFP->getCurrentWaypoint()->getInAir());
auto wp2 = aiFP->getNextWaypoint();
CPPUNIT_ASSERT_EQUAL(false, wp2->getInAir());
CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, wp2->getSpeed(), 0.1);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
// The AI flight plan unit tests.
class AIFlightPlanTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AIFlightPlanTests);
CPPUNIT_TEST(testAIFlightPlan);
CPPUNIT_TEST(testAIFlightPlanLeftCircle);
CPPUNIT_TEST(testAIFlightPlanLoadXML);
CPPUNIT_TEST(testLeftTurnFlightplanXML);
CPPUNIT_TEST(testRightTurnFlightplanXML);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testAIFlightPlan();
void testAIFlightPlanLeftCircle();
void testAIFlightPlanLoadXML();
void testLeftTurnFlightplanXML();
void testRightTurnFlightplanXML();
};

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/>.
*/
#include "test_AIManager.hxx"
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestDataLogger.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/AIManager.hxx>
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void AIManagerTests::setUp()
{
FGTestApi::setUp::initTestGlobals("AI");
FGTestApi::setUp::initNavDataCache();
globals->add_new_subsystem<FGAIManager>(SGSubsystemMgr::GENERAL);
auto props = globals->get_props();
props->setBoolValue("sim/ai/enabled", true);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void AIManagerTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void AIManagerTests::testBasic()
{
auto aim = globals->get_subsystem<FGAIManager>();
auto bikf = FGAirport::findByIdent("BIKF");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
FGTestApi::setPosition(bikf->geod());
pilot->resetAtPosition(bikf->geod());
pilot->setSpeedKts(220);
pilot->setCourseTrue(0.0);
pilot->setTargetAltitudeFtMSL(10000);
FGTestApi::runForTime(10.0);
auto aiUserAircraft = aim->getUserAircraft();
CPPUNIT_ASSERT(aiUserAircraft->isValid());
CPPUNIT_ASSERT(!aiUserAircraft->getDie());
const SGGeod g = globals->get_aircraft_position();
CPPUNIT_ASSERT_DOUBLES_EQUAL(g.getLongitudeDeg(), aiUserAircraft->getGeodPos().getLongitudeDeg(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(g.getLatitudeDeg(), aiUserAircraft->getGeodPos().getLatitudeDeg(), 0.01);
// disable, the AI user aircraft doesn't track altitude?!
// CPPUNIT_ASSERT_DOUBLES_EQUAL(g.getElevationFt(), aiUserAircraft->getGeodPos().getElevationFt(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("orientation/heading-deg"), aiUserAircraft->getTrueHeadingDeg(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("velocities/groundspeed-kt"), aiUserAircraft->getSpeed(), 1);
}
// test for AIFLightPlan leg / legEnd pieces.
void AIManagerTests::testAircraftWaypoints()
{
auto aim = globals->get_subsystem<FGAIManager>();
SGPropertyNode_ptr aircraftDefinition(new SGPropertyNode);
aircraftDefinition->setStringValue("type", "aircraft");
aircraftDefinition->setStringValue("callsign", "G-ARTA");
// set class for performance data
auto eggd = FGAirport::findByIdent("EGGD");
aircraftDefinition->setDoubleValue("heading", 90.0);
aircraftDefinition->setDoubleValue("latitude", eggd->geod().getLatitudeDeg());
aircraftDefinition->setDoubleValue("longitude", eggd->geod().getLongitudeDeg());
aircraftDefinition->setDoubleValue("altitude", 6000.0);
aircraftDefinition->setDoubleValue("speed", 250.0); // IAS or TAS?
FGTestApi::setPositionAndStabilise(eggd->geod());
auto ai = aim->addObject(aircraftDefinition);
CPPUNIT_ASSERT(ai);
CPPUNIT_ASSERT_EQUAL(FGAIBase::object_type::otAircraft, ai->getType());
CPPUNIT_ASSERT_EQUAL(std::string{"aircraft"}, std::string{ai->getTypeString()});
auto aiAircraft = static_cast<FGAIAircraft*>(ai.get());
const auto aiPos = aiAircraft->getGeodPos();
CPPUNIT_ASSERT_DOUBLES_EQUAL(eggd->geod().getLatitudeDeg(), aiPos.getLatitudeDeg(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(eggd->geod().getLongitudeDeg(), aiPos.getLongitudeDeg(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(90.0, aiAircraft->getTrueHeadingDeg(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(250.0, aiAircraft->getSpeed(), 1);
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
ai->setFlightPlan(std::move(aiFP));
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
// The flight plan unit tests.
class AIManagerTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AIManagerTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testAircraftWaypoints);
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 testAircraftWaypoints();
};

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/>.
*/
#include "config.h"
#include "test_TrafficMgr.hxx"
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestDataLogger.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Airports/airport.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
// Set up function for each test.
void TrafficMgrTests::setUp()
{
FGTestApi::setUp::initTestGlobals("TrafficMgr");
FGTestApi::setUp::initNavDataCache();
fgSetBool("sim/ai/enabled", true);
fgSetBool("sim/traffic-manager/enabled", true);
fgSetBool("/environment/realwx/enabled", false);
fgSetBool("/environment/metar/valid", false);
//Otherwise TrafficMgr won't load
fgSetBool("sim/signals/fdm-initialized", true);
globals->set_fg_root(SGPath::fromUtf8(FG_TEST_SUITE_DATA));
}
// Clean up after each test.
void TrafficMgrTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void TrafficMgrTests::testParse() {
globals->add_new_subsystem<FGTrafficManager>(SGSubsystemMgr::GENERAL);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
FGTrafficManager *tmgr = (FGTrafficManager *) globals->get_subsystem("traffic-manager");
FGScheduledFlightVecIterator fltBegin, fltEnd;
for (size_t i = 0; i < 1000000; i++)
{
FGTestApi::runForTime(10.0);
// We have to wait for async parser
fltBegin = tmgr->getFirstFlight("TST_BN_2");
fltEnd = tmgr->getLastFlight("TST_BN_2");
if (fltBegin != fltEnd) {
break;
}
}
int counter = 0;
for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
cout << (*i)->getCallSign() << counter++ << endl;
}
CPPUNIT_ASSERT_EQUAL(2, counter);
}
void TrafficMgrTests::testTrafficManager()
{
FGAirportRef egeo = FGAirport::getByIdent("EGEO");
fgSetString("/sim/presets/airport-id", "EGEO");
std::cout << globals->get_fg_root() << "\r\n";
globals->set_fg_root(SGPath::fromUtf8(FG_TEST_SUITE_DATA));
std::cout << globals->get_fg_root() << "\r\n";
fgSetBool("/sim/traffic-manager/enabled", true);
fgSetBool("/sim/traffic-manager/active", false);
fgSetBool("/sim/ai/enabled", true);
fgSetBool("/environment/realwx/enabled", false);
fgSetBool("/environment/metar/valid", false);
fgSetBool("/sim/terrasync/ai-data-update-now", false);
fgSetBool("/sim/traffic-manager/instantaneous-action", true);
fgSetBool("/sim/traffic-manager/heuristics", true);
fgSetBool("/sim/traffic-manager/dumpdata", false);
fgSetBool("/sim/signals/fdm-initialized", true);
FGTestApi::setPositionAndStabilise(egeo->geod());
auto tmgr = globals->add_new_subsystem<FGTrafficManager>(SGSubsystemMgr::GENERAL);
tmgr->bind();
tmgr->init();
for( int i = 0; i < 30; i++) {
bool active = fgGetBool("/sim/traffic-manager/inited");
// std::cout << "Inited " << "\t" << i << "\t" << active << "\r\n";
FGTestApi::runForTime(5.0);
if(active) {
break;
}
}
const SGPropertyNode *tm = fgGetNode("/sim/traffic-manager", true);
for (int i = 0; i < tm->nChildren(); i++) {
const SGPropertyNode *model = tm->getChild(i);
std::cout << "TM : " << model->getDisplayName() << "\t" << model->nChildren() << "\n";
for (int g = 0; g < model->nChildren(); g++) {
const SGPropertyNode *v;
v = model->getChild(g);
std::cout << "Node " << g << "\t" << v->getDisplayName() << "\n";
}
}
FGTestApi::runForTime(360.0);
FGScheduledFlightVecIterator fltBegin, fltEnd;
fltBegin = tmgr->getFirstFlight("HBR_BN_2");
fltEnd = tmgr->getLastFlight("HBR_BN_2");
if (fltBegin == fltEnd) {
CPPUNIT_FAIL("No Traffic found");
}
int counter = 0;
for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
cout << (*i)->getDepartureAirport()->getId() << "\t" << (*i)->getArrivalAirport()->getId() << "\t" << (*i)->getDepartureTime() << "\n";
counter++;
}
CPPUNIT_ASSERT_EQUAL(25, counter);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class FGAIAircraft;
// The flight plan unit tests.
class TrafficMgrTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(TrafficMgrTests);
CPPUNIT_TEST(testParse);
CPPUNIT_TEST(testTrafficManager);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testTrafficManager();
void testParse();
};

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2022 Keith Paterson
*
* 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/>.
*/
#include "test_VectorMath.hxx"
#include <cstring>
#include <memory>
#include <cppunit/TestAssert.h>
#include "config.h"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <AIModel/VectorMath.hxx>
using namespace flightgear;
// Set up function for each test.
void VectorMathTests::setUp()
{
}
// Clean up after each test.
void VectorMathTests::tearDown()
{
}
void VectorMathTests::testInnerTanget()
{
double r1 = 10;
double r2 = 10;
// when the circles are dist appart the angle will be 45°
double dist = 2 * r1 + 2 * r2;
SGGeod m1 = SGGeod::fromDeg(9,51);
SGGeod m2 = SGGeodesy::direct(m1, 90, dist);
auto angles = VectorMath::innerTangentsAngle(m1, m2, r1, r2);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 60, angles[0], 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 120, angles[1], 0.1);
}
void VectorMathTests::testInnerTangent2()
{
double r1 = 10;
double r2 = 10;
// when the circles are dist appart the angle will be 45°
double dist = 2 * r1 + 2 * r2;
SGGeod m1 = SGGeod::fromDeg(9,51);
SGGeod m2 = SGGeodesy::direct(m1, 0, dist);
auto angles = VectorMath::innerTangentsAngle(m1, m2, r1, r2);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 330, angles[0], 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 30, angles[1], 0.1);
}
void VectorMathTests::testOuterTanget()
{
double r1 = 10;
double r2 = 50;
// when the circles are dist appart the angle will be 45°
double dist = 40;
SGGeod m1 = SGGeod::fromDeg(9,51);
SGGeod m2 = SGGeodesy::direct(m1, 90, dist);
auto angles = VectorMath::outerTangentsAngle(m1, m2, r1, r2);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 45, angles[0], 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 135, angles[1], 0.1);
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2022 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
// The AI flight plan unit tests.
class VectorMathTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(VectorMathTests);
CPPUNIT_TEST(testInnerTanget);
CPPUNIT_TEST(testInnerTangent2);
CPPUNIT_TEST(testOuterTanget);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testInnerTanget();
void testInnerTangent2();
void testOuterTanget();
};

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/>.
*/
#include "config.h"
#include "test_groundnet.hxx"
#include <cstring>
#include <memory>
#include <iostream>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestDataLogger.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/performancedb.hxx>
#include <Airports/airport.hxx>
#include <Airports/airportdynamicsmanager.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/parking.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <ATC/atc_mgr.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void GroundnetTests::setUp()
{
FGTestApi::setUp::initTestGlobals("Traffic");
FGTestApi::setUp::initNavDataCache();
auto props = globals->get_props();
props->setBoolValue("sim/ai/enabled", true);
props->setBoolValue("sim/signals/fdm-initialized", false);
// ensure EGPH has a valid ground net for parking testing
FGAirport::clearAirportsCache();
FGAirportRef egph = FGAirport::getByIdent("EGPH");
egph->testSuiteInjectGroundnetXML(SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "EGPH.groundnet.xml");
FGAirportRef ybbn = FGAirport::getByIdent("YBBN");
ybbn->testSuiteInjectGroundnetXML(SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "YBBN.groundnet.xml");
globals->add_new_subsystem<PerformanceDB>(SGSubsystemMgr::GENERAL);
globals->add_new_subsystem<FGATCManager>(SGSubsystemMgr::GENERAL);
globals->add_new_subsystem<FGAIManager>(SGSubsystemMgr::GENERAL);
globals->add_new_subsystem<flightgear::AirportDynamicsManager>(SGSubsystemMgr::GENERAL);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void GroundnetTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void GroundnetTests::testShortestRoute()
{
FGAirportRef egph = FGAirport::getByIdent("EGPH");
FGGroundNetwork* network = egph->groundNetwork();
FGParkingRef startParking = network->findParkingByName("main-apron10");
FGRunwayRef runway = egph->getRunwayByIndex(0);
FGTaxiNodeRef end = network->findNearestNodeOnRunwayEntry(runway->threshold());
FGTaxiRoute route = network->findShortestRoute(startParking, end);
CPPUNIT_ASSERT_EQUAL(true, network->exists());
CPPUNIT_ASSERT_EQUAL(29, route.size());
}
/**
* Tests various find methods.
*/
void GroundnetTests::testFind()
{
FGAirportRef ybbn = FGAirport::getByIdent("YBBN");
FGGroundNetwork* network = ybbn->groundNetwork();
FGParkingRef startParking = network->findParkingByName("GA1");
CPPUNIT_ASSERT_EQUAL(1020, startParking->getIndex());
FGTaxiSegment* segment1 = network->findSegment(startParking, NULL);
CPPUNIT_ASSERT(segment1);
FGTaxiSegment* segment2 = network->findSegment(startParking, segment1->getEnd());
CPPUNIT_ASSERT(segment2);
FGTaxiNodeVector segmentList = network->findSegmentsFrom(startParking);
CPPUNIT_ASSERT_EQUAL(2, (int)segmentList.size());
CPPUNIT_ASSERT_EQUAL(1026, segmentList.front()->getIndex());
CPPUNIT_ASSERT_EQUAL(1027, segmentList.back()->getIndex());
FGTaxiSegment* pushForwardSegment = network->findSegmentByHeading(startParking, startParking->getHeading());
CPPUNIT_ASSERT(pushForwardSegment);
CPPUNIT_ASSERT_EQUAL(1027, pushForwardSegment->getEnd()->getIndex());
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
// The groundnet unit tests.
class GroundnetTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(GroundnetTests);
CPPUNIT_TEST(testShortestRoute);
CPPUNIT_TEST(testFind);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testShortestRoute();
void testFind();
};

View File

@@ -0,0 +1,259 @@
/*
* Copyright (C) 2021 Colin Geniet
*
* 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/>.
*/
#include "test_submodels.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/submodel.hxx>
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <cmath>
using std::string;
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void SubmodelsTests::setUp()
{
FGTestApi::setUp::initTestGlobals("Submodels");
FGTestApi::setUp::initNavDataCache();
globals->append_aircraft_path(SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "Aircraft");
auto props = globals->get_props();
props->setBoolValue("sim/ai/enabled", true);
props->setStringValue("sim/submodels/path", "Aircraft/Test/submodels.xml");
globals->add_new_subsystem<FGAIManager>(SGSubsystemMgr::GENERAL);
globals->add_new_subsystem<FGSubmodelMgr>(SGSubsystemMgr::GENERAL);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void SubmodelsTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void SubmodelsTests::testLoadXML()
{
auto props = globals->get_props();
auto sm_node = props->getNode("ai/submodels");
CPPUNIT_ASSERT(sm_node->hasChild("submodel", 0));
sm_node = sm_node->getChild("submodel", 0);
CPPUNIT_ASSERT_EQUAL(string{"testLoadXML"}, static_cast<string>(sm_node->getStringValue("name")));
CPPUNIT_ASSERT_EQUAL(0, sm_node->getIntValue("id"));
CPPUNIT_ASSERT_EQUAL(42, sm_node->getIntValue("count"));
CPPUNIT_ASSERT(sm_node->getBoolValue("serviceable"));
}
FGAIBase* SubmodelsTests::findAIModel(std::string &name) {
auto ai_list = globals->get_subsystem<FGAIManager>()->get_ai_list();
auto filter = [name](FGAIBase *model) { return model->_getName() == name; };
return *std::find_if(ai_list.begin(), ai_list.end(), filter);
}
int SubmodelsTests::countAIModels(std::string &name) {
auto ai_list = globals->get_subsystem<FGAIManager>()->get_ai_list();
auto filter = [name](FGAIBase *model) { return model->_getName() == name; };
return static_cast<int>(std::count_if(ai_list.begin(), ai_list.end(), filter));
}
void SubmodelsTests::testRelease()
{
auto props = globals->get_props();
auto sm_node = props->getNode("ai/submodels/submodel[1]");
std::string name = sm_node->getStringValue("name");
// Setup reasonable flight conditions.
auto bikf = FGAirport::findByIdent("BIKF");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
FGTestApi::setPosition(bikf->geod());
pilot->resetAtPosition(bikf->geod());
pilot->setSpeedKts(0);
pilot->setCourseTrue(0.0);
pilot->setTargetAltitudeFtMSL(0);
// Sanity check
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// Don't release anything
FGTestApi::runForTime(10.0);
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// Release submodel
sm_node->setBoolValue("trigger", true);
FGTestApi::runForTime(1);
sm_node->setBoolValue("trigger", false);
FGTestApi::runForTime(1);
CPPUNIT_ASSERT_EQUAL(1, countAIModels(name));
// Check validity
auto sm = findAIModel(name);
CPPUNIT_ASSERT(sm->isValid());
CPPUNIT_ASSERT(!sm->getDie());
// Second time
sm_node->setBoolValue("trigger", true);
FGTestApi::runForTime(5);
sm_node->setBoolValue("trigger", false);
FGTestApi::runForTime(1);
CPPUNIT_ASSERT_EQUAL(2, countAIModels(name));
// Let submodels expire
FGTestApi::runForTime(20);
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// Switch to repeat release
sm_node->setBoolValue("repeat", true);
sm_node->setBoolValue("trigger", true);
FGTestApi::runForTime(4.2); // release interval is 1s
sm_node->setBoolValue("trigger", false);
CPPUNIT_ASSERT_EQUAL(4, countAIModels(name));
// Let submodels expire
FGTestApi::runForTime(20);
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// In repeat mode, release timer persists when trigger is false.
// Currently it is at ~0.2s, so this must not release anything.
sm_node->setBoolValue("trigger", true);
FGTestApi::runForTime(0.5);
sm_node->setBoolValue("trigger", false);
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// Set limited count
sm_node->setIntValue("count", 3);
sm_node->setBoolValue("trigger", true);
FGTestApi::runForTime(1);
CPPUNIT_ASSERT_EQUAL(2, sm_node->getIntValue("count"));
CPPUNIT_ASSERT_EQUAL(1, countAIModels(name));
FGTestApi::runForTime(5);
CPPUNIT_ASSERT_EQUAL(0, sm_node->getIntValue("count"));
CPPUNIT_ASSERT_EQUAL(3, countAIModels(name));
}
void SubmodelsTests::testInitialState()
{
auto props = globals->get_props();
auto sm_node = props->getNode("ai/submodels/submodel[2]");
std::string name = sm_node->getStringValue("name");
// Submodel parameters
double x_offset = 10, y_offset = 6, z_offset = 1, yaw_offset = 30, pitch_offset = 50, speed = 100;
// Setup reasonable flight conditions.
auto bikf = FGAirport::findByIdent("BIKF");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
FGTestApi::setPosition(bikf->geod());
pilot->resetAtPosition(bikf->geod());
pilot->setSpeedKts(0);
pilot->setCourseTrue(90);
pilot->setTargetAltitudeFtMSL(0);
props->setDoubleValue("/orientation/pitch-deg", 0);
props->setDoubleValue("/orientation/roll-deg", 0);
FGTestApi::runForTime(1);
sm_node->setBoolValue("trigger", true);
// Run update loop with dt=0 to capture initial state.
globals->get_subsystem<FGSubmodelMgr>()->update(0);
globals->get_subsystem<FGAIManager>()->update(0);
sm_node->setBoolValue("trigger", false);
CPPUNIT_ASSERT_EQUAL(1, countAIModels(name));
auto sm = findAIModel(name);
auto sm_pos = sm->getCartPos();
auto ac_pos = globals->get_aircraft_position_cart(); //ac->getCartPosAt(SGVec3d(0, 0, 0));
double heading, pitch, roll;
globals->get_aircraft_orientation(heading, pitch, roll);
CPPUNIT_ASSERT_DOUBLES_EQUAL(90, heading, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0, pitch, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0, roll, 0.1);
// Submodel release point
// Submodels offsets are in x-back,y-right,z-up frame.
// Computation is in x-forward,y-irght,z-down frame.
SGVec3d offset(-x_offset, y_offset, -z_offset);
SGQuatd local_frame_rotation = SGQuatd::fromLonLat(globals->get_aircraft_position());
local_frame_rotation *= SGQuatd::fromYawPitchRollDeg(heading, pitch, roll);
offset = local_frame_rotation.backTransform(offset);
auto release_pos = ac_pos + offset;
CPPUNIT_ASSERT_DOUBLES_EQUAL(release_pos.x(), sm_pos.x(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(release_pos.y(), sm_pos.y(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(release_pos.z(), sm_pos.z(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(remainder(heading + yaw_offset, 360), remainder(sm->getTrueHeadingDeg(), 360), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(pitch + pitch_offset, sm->_getPitch(), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(speed, sm->_getSpeed() * SG_KT_TO_FPS, 0.1);
// Let submodels expire
FGTestApi::runForTime(20);
CPPUNIT_ASSERT_EQUAL(0, countAIModels(name));
// Second test for velocities
double speed_east = 100, wind_north = 20;
pilot->setSpeedKts(100 * SG_FPS_TO_KT);
props->setDoubleValue("/orientation/pitch-deg", 0);
props->setDoubleValue("/orientation/roll-deg", 0);
props->setDoubleValue("/velocities/speed-north-fps", 0);
props->setDoubleValue("/velocities/speed-east-fps", speed_east);
props->setDoubleValue("/velocities/speed-down-fps", 0);
props->setDoubleValue("/environment/wind-from-north-fps", wind_north);
props->setDoubleValue("/environment/wind-from-east-fps", 0);
FGTestApi::runForTime(1);
sm_node->setBoolValue("trigger", true);
// Run update loop with dt=0 to capture initial state.
globals->get_subsystem<FGSubmodelMgr>()->update(0);
globals->get_subsystem<FGAIManager>()->update(0);
sm_node->setBoolValue("trigger", false);
CPPUNIT_ASSERT_EQUAL(1, countAIModels(name));
sm = findAIModel(name);
// Check initial speeds.
// _get_speed_*_fps gives airspeed for submodels, which initially must be the vector
// parent_ground_velocity + opposed_wind_velocity + submodel_launch_velocity
CPPUNIT_ASSERT_DOUBLES_EQUAL(cos((heading + yaw_offset) * SG_DEGREES_TO_RADIANS)
* cos((pitch + pitch_offset) * SG_DEGREES_TO_RADIANS)
* speed
+ wind_north,
sm->_get_speed_north_fps(), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(sin((heading + yaw_offset) * SG_DEGREES_TO_RADIANS)
* cos((pitch + pitch_offset) * SG_DEGREES_TO_RADIANS)
* speed
+ speed_east,
sm->_get_speed_east_fps(), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(sin((pitch + pitch_offset) * SG_DEGREES_TO_RADIANS) * speed,
sm->_getVS_fps(), 0.1);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 Colin Geniet
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <AIModel/AIBase.hxx>
#include <simgear/props/props.hxx>
class SGGeod;
// The flight plan unit tests.
class SubmodelsTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(SubmodelsTests);
CPPUNIT_TEST(testLoadXML);
CPPUNIT_TEST(testRelease);
CPPUNIT_TEST(testInitialState);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testLoadXML();
void testRelease();
void testInitialState();
private:
FGAIBase* findAIModel(std::string &name);
int countAIModels(std::string &name);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class FGAIAircraft;
// The flight plan unit tests.
class TrafficTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(TrafficTests);
CPPUNIT_TEST(testPushback);
CPPUNIT_TEST(testPushbackCargo);
CPPUNIT_TEST(testPushbackCargoInProgress);
CPPUNIT_TEST(testPushbackCargoInProgressDownWind);
CPPUNIT_TEST(testPushbackCargoInProgressNotBeyond);
CPPUNIT_TEST(testPushbackCargoInProgressBeyond);
CPPUNIT_TEST(testChangeRunway);
CPPUNIT_TEST(testPushforward);
CPPUNIT_TEST(testPushforwardSpeedy);
CPPUNIT_TEST(testPushforwardParkYBBN);
CPPUNIT_TEST(testPushforwardParkYBBNRepeatGa);
CPPUNIT_TEST(testPushforwardParkYBBNRepeatGaDelayed);
CPPUNIT_TEST(testPushforwardParkYBBNRepeatGate);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// Pushback Tests
void testPushback();
void testPushbackCargo();
void testPushbackCargoInProgress();
void testPushbackCargoInProgressDownWind();
void testPushbackCargoInProgressNotBeyond();
void testPushbackCargoInProgressBeyond();
void testChangeRunway();
//GA Tests with forward push
void testPushforward();
void testPushforwardSpeedy();
void testPushforwardParkYBBN();
void testPushforwardParkYBBNRepeatGa();
void testPushforwardParkYBBNRepeatGaDelayed();
void testPushforwardParkYBBNRepeatGate();
private:
long currentWorldTime;
std::string getTimeString(int timeOffset);
FGAIAircraft * flyAI(SGSharedPtr<FGAIAircraft> aiAircraft, std::string fName);
};

View File

@@ -0,0 +1,12 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_AddonManagement.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_AddonManagement.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,24 @@
/*
* 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/>.
*/
#include "test_AddonManagement.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AddonManagementTests, "Unit tests");

View File

@@ -0,0 +1,247 @@
// -*- coding: utf-8 -*-
//
// test_AddonManagement.cxx --- Automated tests for FlightGear classes dealing
// with add-ons
// Copyright (C) 2017 Florent Rougon
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "test_AddonManagement.hxx"
#include "config.h"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <cassert>
#include <cstddef>
#include "Add-ons/Addon.hxx"
#include "Add-ons/AddonVersion.hxx"
using std::string;
using std::vector;
using flightgear::addons::Addon;
using flightgear::addons::AddonVersion;
using flightgear::addons::AddonVersionSuffix;
void AddonManagementTests::testAddonVersionSuffix()
{
using AddonRelType = flightgear::addons::AddonVersionSuffixPrereleaseType;
FGTestApi::setUp::initTestGlobals("AddonVersionSuffix");
AddonVersionSuffix v1(AddonRelType::beta, 2, true, 5);
AddonVersionSuffix v1Copy(v1);
AddonVersionSuffix v1NonDev(AddonRelType::beta, 2, false);
CPPUNIT_ASSERT_EQUAL(v1, v1Copy);
CPPUNIT_ASSERT_EQUAL(v1, AddonVersionSuffix("b2.dev5"));
CPPUNIT_ASSERT(v1.makeTuple() ==
std::make_tuple(AddonRelType::beta, 2, true, 5));
CPPUNIT_ASSERT_EQUAL(AddonVersionSuffix(), AddonVersionSuffix(""));
// A simple comparison
CPPUNIT_ASSERT(v1 < v1NonDev); // b2.dev5 < b2
// Check string representation with str()
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none).str() == "");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none, 0, true, 12).str() ==
".dev12");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::alpha, 1).str() == "a1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::alpha, 1, false).str() == "a1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::alpha, 2, true, 4).str() ==
"a2.dev4");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::beta, 1).str() == "b1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::beta, 1, false).str() == "b1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::beta, 2, true, 4).str() ==
"b2.dev4");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::candidate, 1).str() == "rc1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::candidate, 1, false).str() ==
"rc1");
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::candidate, 2, true, 4).str() ==
"rc2.dev4");
// Check stream output
std::ostringstream oss;
oss << AddonVersionSuffix(AddonRelType::candidate, 2, true, 4);
CPPUNIT_ASSERT(oss.str() == "rc2.dev4");
// Check ordering with all types of transitions, using operator<()
auto checkStrictOrdering = [](const vector<AddonVersionSuffix>& v) {
assert(v.size() > 1);
for (std::size_t i=0; i < v.size() - 1; i++) {
CPPUNIT_ASSERT(v[i] < v[i+1]);
}
};
checkStrictOrdering({
{AddonRelType::none, 0, true, 1},
{AddonRelType::none, 0, true, 2},
{AddonRelType::alpha, 1, true, 1},
{AddonRelType::alpha, 1, true, 2},
{AddonRelType::alpha, 1, true, 3},
{AddonRelType::alpha, 1, false},
{AddonRelType::alpha, 2, true, 1},
{AddonRelType::alpha, 2, true, 3},
{AddonRelType::alpha, 2, false},
{AddonRelType::beta, 1, true, 1},
{AddonRelType::beta, 1, true, 25},
{AddonRelType::beta, 1, false},
{AddonRelType::beta, 2, true, 1},
{AddonRelType::beta, 2, true, 2},
{AddonRelType::beta, 2, false},
{AddonRelType::candidate, 1, true, 1},
{AddonRelType::candidate, 1, true, 2},
{AddonRelType::candidate, 1, false},
{AddonRelType::candidate, 2, true, 1},
{AddonRelType::candidate, 2, true, 2},
{AddonRelType::candidate, 2, false},
{AddonRelType::candidate, 21, false},
{AddonRelType::none}
});
// Check operator>() and operator!=()
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none) >
AddonVersionSuffix(AddonRelType::candidate, 21, false));
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none) !=
AddonVersionSuffix(AddonRelType::candidate, 21, false));
// Check operator<=() and operator>=()
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::candidate, 2, false) <=
AddonVersionSuffix(AddonRelType::candidate, 2, false));
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::candidate, 2, false) <=
AddonVersionSuffix(AddonRelType::none));
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none) >=
AddonVersionSuffix(AddonRelType::none));
CPPUNIT_ASSERT(AddonVersionSuffix(AddonRelType::none) >=
AddonVersionSuffix(AddonRelType::candidate, 21, false));
FGTestApi::tearDown::shutdownTestGlobals();
}
void AddonManagementTests::testAddonVersion()
{
using AddonRelType = flightgear::addons::AddonVersionSuffixPrereleaseType;
FGTestApi::setUp::initTestGlobals("AddonVersion");
AddonVersionSuffix suffix(AddonRelType::beta, 2, true, 5);
AddonVersion v1(2017, 4, 7, suffix);
AddonVersion v1Copy(v1);
AddonVersion v2 = v1;
AddonVersion v3(std::move(v1Copy));
AddonVersion v4 = std::move(v2);
CPPUNIT_ASSERT_EQUAL(v1, AddonVersion("2017.4.7b2.dev5"));
CPPUNIT_ASSERT_EQUAL(v1, AddonVersion(std::make_tuple(2017, 4, 7, suffix)));
CPPUNIT_ASSERT_EQUAL(v1, v3);
CPPUNIT_ASSERT_EQUAL(v1, v4);
CPPUNIT_ASSERT(v1 < AddonVersion("2017.4.7b2"));
CPPUNIT_ASSERT(v1 <= AddonVersion("2017.4.7b2"));
CPPUNIT_ASSERT(v1 <= v1);
CPPUNIT_ASSERT(AddonVersion("2017.4.7b2") > v1);
CPPUNIT_ASSERT(AddonVersion("2017.4.7b2") >= v1);
CPPUNIT_ASSERT(v1 >= v1);
CPPUNIT_ASSERT(v1 != AddonVersion("2017.4.7b3"));
CPPUNIT_ASSERT_EQUAL(v1.majorNumber(), 2017);
CPPUNIT_ASSERT_EQUAL(v1.minorNumber(), 4);
CPPUNIT_ASSERT_EQUAL(v1.patchLevel(), 7);
CPPUNIT_ASSERT_EQUAL(v1.suffix(), suffix);
// Round-trips std::string <-> AddonVersion
CPPUNIT_ASSERT(AddonVersion("2017.4.7.dev13").str() == "2017.4.7.dev13");
CPPUNIT_ASSERT(AddonVersion("2017.4.7a2.dev8").str() == "2017.4.7a2.dev8");
CPPUNIT_ASSERT(AddonVersion("2017.4.7a2").str() == "2017.4.7a2");
CPPUNIT_ASSERT(AddonVersion("2017.4.7b2.dev5").str() == "2017.4.7b2.dev5");
CPPUNIT_ASSERT(AddonVersion("2017.4.7b2").str() == "2017.4.7b2");
CPPUNIT_ASSERT(AddonVersion("2017.4.7rc1.dev3").str() == "2017.4.7rc1.dev3");
CPPUNIT_ASSERT(AddonVersion("2017.4.7rc1").str() == "2017.4.7rc1");
CPPUNIT_ASSERT(AddonVersion("2017.4.7").str() == "2017.4.7");
// Check stream output
std::ostringstream oss;
oss << AddonVersion("2017.4.7b2.dev5");
CPPUNIT_ASSERT(oss.str() == "2017.4.7b2.dev5");
// Check ordering with all types of transitions, using operator<()
auto checkStrictOrdering = [](const vector<AddonVersion>& v) {
assert(v.size() > 1);
for (std::size_t i=0; i < v.size() - 1; i++) {
CPPUNIT_ASSERT(v[i] < v[i+1]);
}
};
checkStrictOrdering({
"3.12.8.dev1", "3.12.8.dev2", "3.12.8.dev12", "3.12.8a1.dev1",
"3.12.8a1.dev2", "3.12.8a1", "3.12.8a2", "3.12.8b1.dev1",
"3.12.8b1.dev2", "3.12.8b1", "3.12.8b2", "3.12.8b10",
"3.12.8rc1.dev1", "3.12.8rc1.dev2", "3.12.8rc1.dev3",
"3.12.8rc1", "3.12.8rc2", "3.12.8rc3", "3.12.8", "3.12.9.dev1",
"3.12.9", "3.13.0", "4.0.0.dev1", "4.0.0.dev10", "4.0.0a1", "4.0.0",
"2017.4.0", "2017.4.1", "2017.4.10", "2017.5.0", "2018.0.0"});
FGTestApi::tearDown::shutdownTestGlobals();
}
void AddonManagementTests::testAddon()
{
FGTestApi::setUp::initTestGlobals("Addon");
std::string addonId = "org.FlightGear.addons.MyGreatAddon";
Addon addon{addonId};
addon.setVersion(AddonVersion("2017.2.5rc3"));
addon.setBasePath(SGPath("/path/to/MyGreatAddon"));
addon.setMinFGVersionRequired("2017.4.1");
addon.setMaxFGVersionRequired("none");
CPPUNIT_ASSERT_EQUAL(addon.getId(), addonId);
CPPUNIT_ASSERT_EQUAL(*addon.getVersion(), AddonVersion("2017.2.5rc3"));
CPPUNIT_ASSERT_EQUAL(addon.getBasePath(), SGPath("/path/to/MyGreatAddon"));
CPPUNIT_ASSERT(addon.getMinFGVersionRequired() == "2017.4.1");
const string refText = "addon '" + addonId + "' (version = 2017.2.5rc3, "
"base path = '/path/to/MyGreatAddon', "
"minFGVersionRequired = '2017.4.1', "
"maxFGVersionRequired = 'none')";
CPPUNIT_ASSERT_EQUAL(addon.str(), refText);
// Check stream output
std::ostringstream oss;
oss << addon;
CPPUNIT_ASSERT_EQUAL(oss.str(), refText);
// Set a max FG version and recheck
addon.setMaxFGVersionRequired("2018.2.5");
const string refText2 = "addon '" + addonId + "' (version = 2017.2.5rc3, "
"base path = '/path/to/MyGreatAddon', "
"minFGVersionRequired = '2017.4.1', "
"maxFGVersionRequired = '2018.2.5')";
CPPUNIT_ASSERT(addon.getMaxFGVersionRequired() == "2018.2.5");
CPPUNIT_ASSERT_EQUAL(addon.str(), refText2);
FGTestApi::tearDown::shutdownTestGlobals();
}

View File

@@ -0,0 +1,52 @@
/*
* 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/>.
*/
#ifndef _FG_ADDON_MANAGEMENT_UNIT_TESTS_HXX
#define _FG_ADDON_MANAGEMENT_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The unit tests of the Add-on system.
class AddonManagementTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AddonManagementTests);
CPPUNIT_TEST(testAddon);
CPPUNIT_TEST(testAddonVersion);
CPPUNIT_TEST(testAddonVersionSuffix);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp() {}
// Clean up after each test.
void tearDown() {}
// The tests.
void testAddon();
void testAddonVersion();
void testAddonVersionSuffix();
};
#endif // _FG_ADDON_MANAGEMENT_UNIT_TESTS_HXX

View File

@@ -0,0 +1,15 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_airport.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_runway.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_airport.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_runway.cxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/>.
*/
#include "test_airport.hxx"
#include "test_runway.hxx"
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AirportTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(RunwayTests, "Unit tests");

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/>.
*/
#include "config.h"
#include "test_airport.hxx"
#include <iostream>
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestDataLogger.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/SGGeod.hxx>
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/performancedb.hxx>
#include <Airports/airport.hxx>
#include <Airports/airportdynamicsmanager.hxx>
#include <Airports/runways.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Time/TimeManager.hxx>
#include <ATC/atc_mgr.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void AirportTests::setUp()
{
FGTestApi::setUp::initTestGlobals("Airports");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void AirportTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
/**
* @brief Read an airport from the apt.dat
*
*/
void AirportTests::testAirport()
{
FGAirportRef departureAirport = FGAirport::getByIdent("YSSY");
CPPUNIT_ASSERT_EQUAL_MESSAGE("Must have correct id", (std::string)"YSSY", departureAirport->getId());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Must have runways", (unsigned int) 6, departureAirport->numRunways());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Must have runway 16R", true, departureAirport->hasRunwayWithIdent("16R"));
int length = 3962;
FGRunwayRef runway = departureAirport->getRunwayByIdent("16R");
int calculated = SGGeodesy::distanceM(runway->begin(), runway->end());
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Distance between the runway endpoints should be runway length", length, calculated, 1);
calculated = SGGeodesy::distanceM(runway->begin(), runway->pointOnCenterline(-length));
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Distance between the runway start and point on centerline should be runway length", length, calculated, 1);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
// The flight plan unit tests.
class AirportTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AirportTests);
CPPUNIT_TEST(testAirport);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testAirport();
};

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/>.
*/
#include "config.h"
#include "test_runway.hxx"
#include <iostream>
#include <cstring>
#include <memory>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/SGGeod.hxx>
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/performancedb.hxx>
#include <Airports/airport.hxx>
#include <Airports/airportdynamicsmanager.hxx>
#include <Airports/runways.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Time/TimeManager.hxx>
#include <ATC/atc_mgr.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void RunwayTests::setUp()
{
}
// Clean up after each test.
void RunwayTests::tearDown()
{
}
void RunwayTests::testRunway()
{
PositionedID aAirport = 0;
SGGeod aGeod = SGGeod::fromDeg(-33.92935800, 151.17160300);
double heading = 155;
int length = 3962;
double width = 45.0;
double displ_thresh = 79;
double stopway = 0;
FGRunway runway = FGRunway( FGPositioned::RUNWAY,
aAirport,
"16R",
aGeod,
heading,
length,
width,
displ_thresh,
stopway,
1);
int calculated = SGGeodesy::distanceM(runway.begin(), runway.end());
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Distance between the runway endpoints should be runway length", length, calculated, 1);
calculated = SGGeodesy::distanceM(runway.begin(), runway.pointOnCenterline(-length));
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Distance between the runway start and point on centerline should be runway length", length, calculated, 1);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 Keith Paterson
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class FGAIAircraft;
// The flight plan unit tests.
class RunwayTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(RunwayTests);
CPPUNIT_TEST(testRunway);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testRunway();
};

View File

@@ -0,0 +1,18 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testDigitalFilter.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testPidController.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testPidControllerData.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testInputValue.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/testDigitalFilter.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testPidController.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testPidControllerData.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testInputValue.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,28 @@
/*
* 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/>.
*/
#include "testDigitalFilter.hxx"
#include "testPidController.hxx"
#include "testInputValue.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DigitalFilterTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PidControllerTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(InputValueTests, "Unit tests");

View File

@@ -0,0 +1,81 @@
#include "testDigitalFilter.hxx"
#include <strstream>
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Autopilot/autopilot.hxx>
#include <Autopilot/digitalfilter.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/props/props_io.hxx>
// Set up function for each test.
void DigitalFilterTests::setUp()
{
FGTestApi::setUp::initTestGlobals("ap-digitialfilter");
}
// Clean up after each test.
void DigitalFilterTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
SGPropertyNode_ptr DigitalFilterTests::configFromString(const std::string& s)
{
SGPropertyNode_ptr config = new SGPropertyNode;
std::istringstream iss(s);
readProperties(iss, config);
return config;
}
void DigitalFilterTests::testNoise()
{
sg_srandom(999);
auto config = configFromString(R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<filter>
<input>/test/a</input>
<output>/test/b</output>
<type>coherent-noise</type>
<amplitude>3.0</amplitude>
<absolute type="bool">true</absolute>
<discrete-resolution>1024</discrete-resolution>
</filter>
</PropertyList>
)");
auto ap = new FGXMLAutopilot::Autopilot(globals->get_props(), config);
globals->add_subsystem("ap", ap, SGSubsystemMgr::FDM);
ap->bind();
ap->init();
//
// for (double x=0.0; x < 5.0; x+=0.01) {
// fgSetDouble("/test/a", x);
// ap->update(0.1);
// std::cerr << fgGetDouble("/test/b") << std::endl;
// }
fgSetDouble("/test/a", 0.5);
ap->update(0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.029, fgGetDouble("/test/b"), 0.001);
fgSetDouble("/test/a", 0.8);
ap->update(0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.808, fgGetDouble("/test/b"), 0.001);
fgSetDouble("/test/a", 0.3);
ap->update(0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.478, fgGetDouble("/test/b"), 0.001);
}

View File

@@ -0,0 +1,50 @@
/*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/props/props.hxx>
// The system tests.
class DigitalFilterTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(DigitalFilterTests);
CPPUNIT_TEST(testNoise);
CPPUNIT_TEST_SUITE_END();
SGPropertyNode_ptr configFromString(const std::string& s);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testNoise();
};

View File

@@ -0,0 +1,115 @@
/*
SPDX-Copyright: James Turner
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "testInputValue.hxx"
#include <strstream>
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Autopilot/autopilot.hxx>
#include <Autopilot/inputvalue.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/props/props_io.hxx>
using namespace FGXMLAutopilot;
// Set up function for each test.
void InputValueTests::setUp()
{
FGTestApi::setUp::initTestGlobals("ap-inputvalue");
}
// Clean up after each test.
void InputValueTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
SGPropertyNode_ptr InputValueTests::configFromString(const std::string& s)
{
SGPropertyNode_ptr config = new SGPropertyNode;
std::istringstream iss(s);
readProperties(iss, config);
return config;
}
void InputValueTests::testPropertyPath()
{
sg_srandom(999);
auto config = configFromString(R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<property-path>/test/altitude-ft-node-path</property-path>
<value>1.23</value>
</PropertyList>
)");
fgSetString("/test/altitude-ft-node-path", "/test/a");
fgSetDouble("/test/a", 0.5);
InputValue_ptr valueA = new InputValue(*globals->get_props(), *config);
CPPUNIT_ASSERT(valueA->is_enabled());
// check value is not written back
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, valueA->get_value(), 0.001);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, fgGetDouble("/test/a"), 0.001);
fgSetDouble("/test/a", 2.34);
CPPUNIT_ASSERT_DOUBLES_EQUAL(2.34, valueA->get_value(), 0.001);
fgSetString("/test/altitude-ft-node-path", "blah");
CPPUNIT_ASSERT(!valueA->is_enabled());
// <value> is used
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.23, valueA->get_value(), 0.001);
fgSetDouble("/foo/bpath", 99);
fgSetString("/test/altitude-ft-node-path", "/foo/bpath");
CPPUNIT_ASSERT(valueA->is_enabled());
CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, valueA->get_value(), 0.001);
fgSetDouble("/foo/bpath", -45.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-45.1, valueA->get_value(), 0.001);
// start with different config
auto config2 = configFromString(R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<property-path>/test/indicated-knots-node-path</property-path>
</PropertyList>
)");
InputValue_ptr valueB = new InputValue(*globals->get_props(), *config2);
CPPUNIT_ASSERT(!valueB->is_enabled());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, valueB->get_value(), 0.001);
fgSetString("/test/indicated-knots-node-path", "/instruments/airspeed/output/knots");
CPPUNIT_ASSERT(!valueB->is_enabled());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, valueB->get_value(), 0.001);
// create the property, but this does not trigger the change listener, so
// stays invalid
fgSetDouble("/instruments/airspeed/output/knots", 415);
CPPUNIT_ASSERT(!valueB->is_enabled());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, valueB->get_value(), 0.001);
// set the path again (with some whitespace, which is trimmed)
fgSetString("/test/indicated-knots-node-path", " /instruments/airspeed/output/knots ");
CPPUNIT_ASSERT(valueB->is_enabled());
CPPUNIT_ASSERT_DOUBLES_EQUAL(415.0, valueB->get_value(), 0.001);
fgSetString("/test/indicated-knots-node-path", "");
CPPUNIT_ASSERT(!valueB->is_enabled());
}

View File

@@ -0,0 +1,35 @@
/*
SPDX-Copyright: James Turner
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/props/props.hxx>
// The system tests.
class InputValueTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(InputValueTests);
CPPUNIT_TEST(testPropertyPath);
CPPUNIT_TEST_SUITE_END();
SGPropertyNode_ptr configFromString(const std::string& s);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testPropertyPath();
};

View File

@@ -0,0 +1,127 @@
#include "testPidController.hxx"
#include "testPidControllerData.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Autopilot/autopilot.hxx>
#include <Autopilot/pidcontroller.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/props/props_io.hxx>
// Set up function for each test.
void PidControllerTests::setUp()
{
FGTestApi::setUp::initTestGlobals("ap-pidcontroller");
}
// Clean up after each test.
void PidControllerTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
SGPropertyNode_ptr PidControllerTests::configFromString(const std::string& s)
{
SGPropertyNode_ptr config = new SGPropertyNode;
std::istringstream iss(s);
readProperties(iss, config);
return config;
}
void PidControllerTests::test0()
{
test(false /*startup_current*/);
}
void PidControllerTests::test1()
{
test(true /*startup_current*/);
}
void PidControllerTests::test(bool startup_current)
{
sg_srandom(999);
// Define vertical-hold pid-controller (based on Harrier-GR3).
//
std::string config_text0 =
R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<pid-controller>
<name>Vertical Speed Hold</name>
<debug>false</debug>
<startup-current></startup-current>
<enable>true</enable>
<input>
<prop>/velocities/vertical-speed-fps</prop>
</input>
<reference>
<prop>/autopilot/settings/vertical-speed-fpm</prop>
<scale>0.01667</scale>
</reference>
<output>
<prop>/controls/flight/elevator</prop>
</output>
<config>
<Kp>-0.025</Kp> <!-- proportional gain -->
<beta>1.0</beta> <!-- input value weighing factor -->
<alpha>0.1</alpha> <!-- low pass filter weighing factor -->
<gamma>0.0</gamma> <!-- input value weighing factor for -->
<!-- unfiltered derivative error -->
<Ti>5</Ti> <!-- integrator time -->
<Td>0.01</Td> <!-- derivator time -->
<!-- Restrict elevator values to +/-0.35 at high speed, but allow full
+/-1 range at low speed. -->
<u_min>-1</u_min>
<u_max>1</u_max>
</config>
</pid-controller>
</PropertyList>
)";
// Set the <startup-current> element.
std::string from = "<startup-current></startup-current>";
std::string config_text = config_text0;
config_text.replace(config_text.find(from), from.size(),
(startup_current) ? "<startup-current>true</startup-current>" : "<startup-current>false</startup-current>"
);
assert(config_text != config_text0);
std::cout << "config_text is:\n" << config_text << "\n";
SGPropertyNode_ptr config = configFromString(config_text);
auto ap = new FGXMLAutopilot::Autopilot(globals->get_props(), config);
globals->add_subsystem("ap", ap, SGSubsystemMgr::FDM);
ap->bind();
ap->init();
const std::vector<PidControllerOutput>& outputs = (startup_current) ? pidControllerOutputs1 : pidControllerOutputs0;
assert(pidControllerInputs.size() == outputs.size());
fgSetDouble("/controls/flight/elevator", 0);
for (unsigned i=0; i<pidControllerInputs.size(); ++i) {
const PidControllerInput& input = pidControllerInputs[i];
const PidControllerOutput& output = outputs[i];
fgSetDouble("/velocities/vertical-speed-fps", input.vspeed_fps);
fgSetDouble("/autopilot/settings/vertical-speed-fpm", input.reference / 0.01667);
ap->update(0.00833333 /*dt*/);
double elevator = fgGetDouble("/controls/flight/elevator");
CPPUNIT_ASSERT_DOUBLES_EQUAL(output.output, elevator, 0.0001);
if (0) {
// This generates C++ text for setting the <outputs> vector above.
std::cout << "{ " << elevator << "},\n";
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/props/props.hxx>
// The system tests.
struct PidControllerTests : public CppUnit::TestFixture
{
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void test0();
void test1();
private:
// Set up the test suite.
CPPUNIT_TEST_SUITE(PidControllerTests);
CPPUNIT_TEST(test0);
CPPUNIT_TEST(test1);
CPPUNIT_TEST_SUITE_END();
SGPropertyNode_ptr configFromString(const std::string& s);
void test(bool startup_zeros);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
#pragma once
#include <vector>
struct PidControllerInput
{
double vspeed_fps;
double reference;
};
struct PidControllerOutput
{
double output;
};
// Define sequence of input values. These are the values actually used with
// Harrier-GR3 when startup_current=true, but we use the same inputs for
// startup_current=false also.
//
extern std::vector<PidControllerInput> pidControllerInputs;
// Expected output when startup_current is false (the default old behaviour).
//
// Note the large transient at the start which messes up the initial behaviour.
//
extern std::vector<PidControllerOutput> pidControllerOutputs0;
// Expected output when startup_current is true (the new improved behaviour).
//
extern std::vector<PidControllerOutput> pidControllerOutputs1;

View File

@@ -0,0 +1,31 @@
# Add each unit test category.
foreach( unit_test_category
Add-ons
general
FDM
Input
Main
Navaids
Network
Instrumentation
Scripting
AI
Airports
Autopilot
)
add_subdirectory(${unit_test_category})
endforeach( unit_test_category )
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
PARENT_SCOPE
)

View File

@@ -0,0 +1,18 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_ls_matrix.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testAeroElement.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testYASimAtmosphere.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testYASimGear.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_ls_matrix.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testAeroElement.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testYASimAtmosphere.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testYASimGear.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,30 @@
/*
* 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/>.
*/
#include "test_ls_matrix.hxx"
#include "testAeroElement.hxx"
#include "testYASimAtmosphere.hxx"
#include "testYASimGear.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AeroElementTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(LaRCSimMatrixTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(YASimAtmosphereTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(YASimGearTests, "Unit tests");

View File

@@ -0,0 +1,134 @@
#include <simgear/constants.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/SGVec3.hxx>
#include "FDM/AIWake/AeroElement.hxx"
#include "testAeroElement.hxx"
void AeroElementTests::testNormal()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d n = el->getNormal();
CPPUNIT_ASSERT_DOUBLES_EQUAL(n[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(n[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(n[2], -1.0, 1e-9);
}
void AeroElementTests::testCollocationPoint()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d cp = el->getCollocationPoint();
CPPUNIT_ASSERT_DOUBLES_EQUAL(cp[0], -0.75, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(cp[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(cp[2], 0.0, 1e-9);
}
void AeroElementTests::testBoundVortexMidPoint()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
CPPUNIT_ASSERT_DOUBLES_EQUAL(mp[0], -0.25, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(mp[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(mp[2], 0.0, 1e-9);
}
void AeroElementTests::testBoundVortex()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d v = el->getBoundVortex();
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 1.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], 0.0, 1e-9);
}
void AeroElementTests::testInducedVelocityOnBoundVortex()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
SGVec3d v = el->getInducedVelocity(mp);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], 1.0/M_PI, 1e-9);
}
void AeroElementTests::testInducedVelocityOnCollocationPoint()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d cp = el->getCollocationPoint();
SGVec3d v = el->getInducedVelocity(cp);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], (1.0+sqrt(2.0)/M_PI), 1e-9);
}
void AeroElementTests::testInducedVelocityAtFarField()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
SGVec3d v = el->getInducedVelocity(mp+SGVec3d(-1000.,0.,0.));
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], 2.0/M_PI, 1e-7);
}
void AeroElementTests::testInducedVelocityAbove()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
SGVec3d v = el->getInducedVelocity(mp+SGVec3d(0.,0.,-0.5));
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], -1.0/(sqrt(2.0)*M_PI), 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], 0.5/M_PI, 1e-9);
}
void AeroElementTests::testInducedVelocityAboveWithOffset()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
SGVec3d v = el->getInducedVelocity(mp+SGVec3d(0.0, 0.5, -1.0));
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], -1.0/(4.0*M_PI*sqrt(2.0)), 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], -0.125/M_PI, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], 0.125/M_PI, 1e-9);
}
void AeroElementTests::testInducedVelocityUpstream()
{
AeroElement_ptr el = new AeroElement(SGVec3d(-1., -0.5, 0.),
SGVec3d(0., -0.5, 0.),
SGVec3d(0., 0.5, 0.),
SGVec3d(-1., 0.5, 0.));
SGVec3d mp = el->getBoundVortexMidPoint();
SGVec3d v = el->getInducedVelocity(mp+SGVec3d(0.5, 0.0, 0.0));
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[0], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[1], 0.0, 1e-9);
CPPUNIT_ASSERT_DOUBLES_EQUAL(v[2], (1.0-sqrt(2.0))/M_PI, 1e-9);
}

View File

@@ -0,0 +1,66 @@
/*
* 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/>.
*/
#ifndef _FG_AERO_ELEMENT_UNIT_TESTS_HXX
#define _FG_AERO_ELEMENT_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The unit tests.
class AeroElementTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AeroElementTests);
CPPUNIT_TEST(testBoundVortex);
CPPUNIT_TEST(testBoundVortexMidPoint);
CPPUNIT_TEST(testCollocationPoint);
CPPUNIT_TEST(testInducedVelocityAbove);
CPPUNIT_TEST(testInducedVelocityAboveWithOffset);
CPPUNIT_TEST(testInducedVelocityAtFarField);
CPPUNIT_TEST(testInducedVelocityOnBoundVortex);
//CPPUNIT_TEST(testInducedVelocityOnCollocationPoint); // Not run in the original ctest.
CPPUNIT_TEST(testInducedVelocityUpstream);
CPPUNIT_TEST(testNormal);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp() {}
// Clean up after each test.
void tearDown() {}
// The tests.
void testBoundVortex();
void testBoundVortexMidPoint();
void testCollocationPoint();
void testInducedVelocityAbove();
void testInducedVelocityAboveWithOffset();
void testInducedVelocityAtFarField();
void testInducedVelocityOnBoundVortex();
void testInducedVelocityOnCollocationPoint();
void testInducedVelocityUpstream();
void testNormal();
};
#endif // _FG_AERO_ELEMENT_UNIT_TESTS_HXX

View File

@@ -0,0 +1,44 @@
#include "test_suite/FGTestApi/PrivateAccessorFDM.hxx"
#include "testYASimAtmosphere.hxx"
#include <FDM/YASim/Math.hpp>
#include <simgear/debug/logstream.hxx>
using namespace yasim;
void YASimAtmosphereTests::setUp()
{
a.reset(new Atmosphere());
}
void YASimAtmosphereTests::tearDown()
{
a.reset();
}
void YASimAtmosphereTests::testAtmosphere()
{
auto accessor = FGTestApi::PrivateAccessor::FDM::Accessor();
int numColumns = accessor.read_FDM_YASim_Atmosphere_numColumns(a);
int maxTableIndex = a->maxTableIndex();
int rows = maxTableIndex + 1;
const float maxDeviation = 0.0002f;
SG_LOG(SG_GENERAL, SG_INFO, "Columns = " << numColumns);
SG_LOG(SG_GENERAL, SG_INFO, "Rows = " << rows);
for (int alt = 0; alt <= maxTableIndex; alt++) {
float density = a->calcStdDensity(accessor.read_FDM_YASim_Atmosphere_data(a, alt, a->PRESSURE), accessor.read_FDM_YASim_Atmosphere_data(a, alt, a->TEMPERATURE));
float delta = accessor.read_FDM_YASim_Atmosphere_data(a, alt, a->DENSITY) - density;
SG_LOG(SG_GENERAL, SG_INFO, "alt: " << alt << ", delta: " << delta);
if (Math::abs(delta) > maxDeviation)
CPPUNIT_FAIL("Deviation above limit of 0.0002");
}
SG_LOG(SG_GENERAL, SG_INFO, "Deviation below " << maxDeviation << " for all rows.");
}

View File

@@ -0,0 +1,52 @@
/*
* 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/>.
*/
#ifndef _FG_YASIM_ATMOSPHERE_UNIT_TESTS_HXX
#define _FG_YASIM_ATMOSPHERE_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <src/FDM/YASim/Atmosphere.hpp>
// The unit tests.
class YASimAtmosphereTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(YASimAtmosphereTests);
CPPUNIT_TEST(testAtmosphere);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testAtmosphere();
// Data.
std::unique_ptr<yasim::Atmosphere> a;
};
#endif // _FG_YASIM_ATMOSPHERE_UNIT_TESTS_HXX

View File

@@ -0,0 +1,128 @@
#include "testYASimGear.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "FDM/YASim/Gear.hpp"
#include <simgear/debug/logstream.hxx>
#include <sstream>
void YASimGearTests::setUp()
{
FGTestApi::setUp::initTestGlobals("");
}
void YASimGearTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void YASimGearTests::test()
{
/* Check we get expected values for a particular set of inputs. */
float ground[4] = { -0.22097, 0.00507429, -0.975263, 2.35943};
float wheel_pos[3] = { -0.953044, 2.20823, -2.00883};
yasim::GearVector wheel_axle( -0, 0.16383, -0.114715);
float wheel_radius = 0.261257;
float tyre_radius = 0.130629;
yasim::GearVector compression( -0.139389, -0, 0.480178);
float contact[3];
float compress_distance_vertical;
float compress_norm;
bool on_ground = yasim::gearCompression(
ground,
compression,
wheel_pos,
wheel_axle,
wheel_radius,
tyre_radius,
[] () { return 0; },
/* output params: */
contact,
compress_distance_vertical,
compress_norm
);
SG_LOG( SG_GENERAL, SG_ALERT, "on_ground=" << on_ground);
SG_LOG( SG_GENERAL, SG_ALERT, "contact=(" << contact[0] << ", " << contact[1] << ", " << contact[1] << ")");
SG_LOG( SG_GENERAL, SG_ALERT, "compress_distance_vertical=" << compress_distance_vertical);
SG_LOG( SG_GENERAL, SG_ALERT, "compress_norm=" << compress_norm);
double e = 0.0001;
CPPUNIT_ASSERT( on_ground);
CPPUNIT_ASSERT_DOUBLES_EQUAL( -1.1053, contact[0], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0645, contact[1], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( -2.1581, contact[2], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 0.167955, compress_distance_vertical, e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( 0.383887, compress_norm, e);
/* Now check we get same results as old point-contact algorithm, when using
wheel_radius=0 and tyre_radius=0. */
/* For this test we set gear contact point to bottom of what was the wheel,
so that it will be approximately in same position as earlier contact point,
and so slightly underground. */
wheel_pos[2] -= wheel_radius + tyre_radius;
float bump_altitude_override = 0.1;
on_ground = yasim::gearCompression(
ground,
compression,
wheel_pos /* contact point. */,
wheel_axle /* values don't matter. */,
0 /*wheel_radius*/,
0 /*tyre_radius*/,
[bump_altitude_override] () { return bump_altitude_override; },
/* output params: */
contact,
compress_distance_vertical,
compress_norm
);
float contact_old[3];
float compress_distance_vertical_old;
float compress_norm_old;
bool on_ground_old = yasim::gearCompressionOld(
ground,
compression,
wheel_pos /* contact point. */,
[bump_altitude_override] () { return bump_altitude_override; },
/* output params: */
contact_old,
compress_distance_vertical_old,
compress_norm_old
);
SG_LOG( SG_GENERAL, SG_ALERT, "comparing with old algorithm.");
SG_LOG( SG_GENERAL, SG_ALERT, "old:");
SG_LOG( SG_GENERAL, SG_ALERT, " on_ground_old=" << on_ground_old);
SG_LOG( SG_GENERAL, SG_ALERT, " contact=(" << contact_old[0] << ", " << contact_old[1] << ", " << contact_old[1] << ")");
SG_LOG( SG_GENERAL, SG_ALERT, " compress_distance_vertical=" << compress_distance_vertical_old);
SG_LOG( SG_GENERAL, SG_ALERT, " compress_norm=" << compress_norm_old);
SG_LOG( SG_GENERAL, SG_ALERT, "new:");
SG_LOG( SG_GENERAL, SG_ALERT, " on_ground=" << on_ground);
SG_LOG( SG_GENERAL, SG_ALERT, " contact=(" << contact[0] << ", " << contact[1] << ", " << contact[1] << ")");
SG_LOG( SG_GENERAL, SG_ALERT, " compress_distance_vertical=" << compress_distance_vertical);
SG_LOG( SG_GENERAL, SG_ALERT, " compress_norm=" << compress_norm);
CPPUNIT_ASSERT( on_ground);
CPPUNIT_ASSERT_EQUAL( on_ground_old, on_ground);
CPPUNIT_ASSERT_DOUBLES_EQUAL( contact_old[0], contact[0], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( contact_old[1], contact[1], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( contact_old[2], contact[2], e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( compress_distance_vertical_old, compress_distance_vertical, e);
CPPUNIT_ASSERT_DOUBLES_EQUAL( compress_norm_old, compress_norm, e);
}

View File

@@ -0,0 +1,15 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
struct YASimGearTests : CppUnit::TestFixture
{
void setUp();
void tearDown();
void test();
CPPUNIT_TEST_SUITE(YASimGearTests);
CPPUNIT_TEST(test);
CPPUNIT_TEST_SUITE_END();
};

View File

@@ -0,0 +1,155 @@
#include "test_ls_matrix.hxx"
#include <simgear/constants.h>
#include <simgear/misc/test_macros.hxx>
extern "C" {
#include "src/FDM/LaRCsim/ls_matrix.h"
}
void LaRCSimMatrixTests::testCopyMatrix()
{
int nelm = 20;
double **src = nr_matrix(1, nelm, 1, nelm);
double **dest = nr_matrix(1, nelm, 1, nelm);
double invmaxlong = 1.0/(double)RAND_MAX;
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
src[i][j] = 2.0 - 4.0*invmaxlong*(double) rand();
nr_copymat(src, nelm, dest);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
CPPUNIT_ASSERT_EQUAL(src[i][j], dest[i][j]);
nr_free_matrix(src, 1, nelm, 1, nelm);
nr_free_matrix(dest, 1, nelm, 1, nelm);
}
void LaRCSimMatrixTests::testIdentityMatrix()
{
int nelm = 10;
double **id = nr_matrix(1, nelm, 1, nelm);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
id[i][j] = i == j ? 1.0 : 0.0;
nr_gaussj(id, nelm, 0, 0);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
CPPUNIT_ASSERT_DOUBLES_EQUAL(id[i][j], i == j ? 1.0 : 0.0, 1e-9);
nr_free_matrix(id, 1, nelm, 1, nelm);
}
void LaRCSimMatrixTests::testOrthogonalMatrix()
{
int nelm = 3;
double **m = nr_matrix(1, nelm, 1, nelm);
double **inv = nr_matrix(1, nelm, 1, nelm);
double angle = M_PI/3.0;
m[1][1] = cos(angle);
m[1][2] = sin(angle);
m[1][3] = 0.0;
m[2][1] = -m[1][2];
m[2][2] = m[1][1];
m[2][3] = 0.0;
m[3][1] = 0.0;
m[3][2] = 0.0;
m[3][3] = 1.0;
nr_copymat(m, nelm, inv);
nr_gaussj(inv, nelm, 0, 0);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
CPPUNIT_ASSERT_DOUBLES_EQUAL(m[i][j], inv[j][i], 1e-9);
nr_free_matrix(m, 1, nelm, 1, nelm);
nr_free_matrix(inv, 1, nelm, 1, nelm);
}
void LaRCSimMatrixTests::testRandomMatrix()
{
int nelm = 20;
double **src = nr_matrix(1, nelm, 1, nelm);
double **inv = nr_matrix(1, nelm, 1, nelm);
double **id = nr_matrix(1, nelm, 1, nelm);
double invmaxlong = 1.0/(double)RAND_MAX;
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
src[i][j] = 2.0 - 4.0*invmaxlong*(double) rand();
nr_copymat(src, nelm, inv);
nr_gaussj(inv, nelm, 0, 0);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j) {
id[i][j] = 0.0;
for (int k=1; k<=nelm; ++k)
id[i][j] += src[i][k]*inv[k][j];
}
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
CPPUNIT_ASSERT_DOUBLES_EQUAL(id[i][j], i == j ? 1.0 : 0.0, 1e-9);
nr_free_matrix(src, 1, nelm, 1, nelm);
nr_free_matrix(inv, 1, nelm, 1, nelm);
nr_free_matrix(id, 1, nelm, 1, nelm);
}
void LaRCSimMatrixTests::testSolveLinearSystem()
{
int nelm = 20;
double **src = nr_matrix(1, nelm, 1, nelm);
double **inv = nr_matrix(1, nelm, 1, nelm);
double **rhs = nr_matrix(1, nelm, 1, 1);
double **sol = nr_matrix(1, nelm, 1, 1);
double **check = nr_matrix(1, nelm, 1, nelm);
double invmaxlong = 1.0/(double)RAND_MAX;
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
src[i][j] = 2.0 - 4.0*invmaxlong*(double) rand();
for (int i=1; i<=nelm; ++i) {
rhs[i][1] = 2.0+cos(i*M_PI/nelm);
sol[i][1] = rhs[i][1];
}
nr_copymat(src, nelm, inv);
nr_gaussj(inv, nelm, sol, 1);
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j) {
check[i][j] = 0.0;
for (int k=1; k<=nelm; ++k)
check[i][j] += src[i][k]*inv[k][j];
}
for (int i=1; i<=nelm; ++i)
for (int j=1; j<=nelm; ++j)
CPPUNIT_ASSERT_DOUBLES_EQUAL(check[i][j], i == j ? 1.0 : 0.0, 1e-9);
for (int i=1; i<=nelm; ++i) {
check[i][1] = 0.0;
for (int j=1; j<=nelm; ++j)
check[i][1] += src[i][j]*sol[j][1];
}
for (int i=1; i<=nelm; ++i)
CPPUNIT_ASSERT_DOUBLES_EQUAL(check[i][1], rhs[i][1], 1e-9);
nr_free_matrix(src, 1, nelm, 1, nelm);
nr_free_matrix(inv, 1, nelm, 1, nelm);
nr_free_matrix(check, 1, nelm, 1, nelm);
nr_free_matrix(rhs, 1, nelm, 1, 1);
nr_free_matrix(sol, 1, nelm, 1, 1);
}

View File

@@ -0,0 +1,56 @@
/*
* 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/>.
*/
#ifndef _FG_LARCSIM_MATRIX_UNIT_TESTS_HXX
#define _FG_LARCSIM_MATRIX_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The LaRCSim matrix unit tests.
class LaRCSimMatrixTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(LaRCSimMatrixTests);
CPPUNIT_TEST(testCopyMatrix);
CPPUNIT_TEST(testIdentityMatrix);
CPPUNIT_TEST(testOrthogonalMatrix);
CPPUNIT_TEST(testRandomMatrix);
CPPUNIT_TEST(testSolveLinearSystem);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp() {}
// Clean up after each test.
void tearDown() {}
// The tests.
void testCopyMatrix();
void testIdentityMatrix();
void testOrthogonalMatrix();
void testRandomMatrix();
void testSolveLinearSystem();
};
#endif // _FG_LARCSIM_MATRIX_UNIT_TESTS_HXX

View File

@@ -0,0 +1,17 @@
if(ENABLE_HID_INPUT)
set(HID_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test_hidinput.cxx)
set(HID_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/test_hidinput.hxx)
endif()
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${HID_SOURCE}
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${HID_HEADER}
PARENT_SCOPE
)

View File

@@ -0,0 +1,28 @@
/*
* 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/>.
*/
#include <config.h>
#include "test_hidinput.hxx"
// Set up the unit tests.
#ifdef ENABLE_HID_INPUT
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HIDInputTests, "Unit tests");
#endif

View File

@@ -0,0 +1,73 @@
// Written by James Turner, started 2017.
//
// Copyright (C) 2017 James Turner
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "test_hidinput.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <simgear/misc/test_macros.hxx>
#include <Input/FGHIDEventInput.hxx>
void HIDInputTests::testValueExtract()
{
uint8_t testDataFromSpec[4] = {0, 0xf4, 0x1 | (0x7 << 2), 0x03};
CPPUNIT_ASSERT(extractBits(testDataFromSpec, 4, 8, 10) == 500);
CPPUNIT_ASSERT(extractBits(testDataFromSpec, 4, 18, 10) == 199);
uint8_t testData2[4] = {0x01 << 6 | 0x0f,
0x17 | (1 << 6),
0x3 | (0x11 << 2),
0x3d | (1 << 6) };
CPPUNIT_ASSERT(extractBits(testData2, 4, 0, 6) == 15);
CPPUNIT_ASSERT(extractBits(testData2, 4, 6, 12) == 3421);
CPPUNIT_ASSERT(extractBits(testData2, 4, 18, 12) == 3921);
CPPUNIT_ASSERT(extractBits(testData2, 4, 30, 1) == 1);
CPPUNIT_ASSERT(extractBits(testData2, 4, 31, 1) == 0);
}
// void writeBits(uint8_t* bytes, size_t bitOffset, size_t bitSize, int value)
void HIDInputTests::testValueInsert()
{
uint8_t buf[8];
memset(buf, 0, 8);
int a = 3421;
int b = 3921;
writeBits(buf, 6, 12, a);
writeBits(buf, 18, 12, b);
CPPUNIT_ASSERT(buf[0] == 0x40);
CPPUNIT_ASSERT(buf[1] == 0x57);
CPPUNIT_ASSERT(buf[2] == (0x03 | 0x44));
CPPUNIT_ASSERT(buf[3] == 0x3d);
}
void HIDInputTests::testSignExtension()
{
CPPUNIT_ASSERT(signExtend(0x80, 8) == -128);
CPPUNIT_ASSERT(signExtend(0xff, 8) == -1);
CPPUNIT_ASSERT(signExtend(0x7f, 8) == 127);
CPPUNIT_ASSERT(signExtend(0x831, 12) == -1999);
CPPUNIT_ASSERT(signExtend(0x7dd, 12) == 2013);
}

View File

@@ -0,0 +1,52 @@
/*
* 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/>.
*/
#ifndef _FG_HIDINPUT_UNIT_TESTS_HXX
#define _FG_HIDINPUT_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The unit tests.
class HIDInputTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(HIDInputTests);
CPPUNIT_TEST(testValueExtract);
CPPUNIT_TEST(testValueInsert);
CPPUNIT_TEST(testSignExtension);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp() {}
// Clean up after each test.
void tearDown() {}
// The tests.
void testValueExtract();
void testValueInsert();
void testSignExtension();
};
#endif // _FG_HIDINPUT_UNIT_TESTS_HXX

View File

@@ -0,0 +1,24 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_gps.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_hold_controller.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_rnav_procedures.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_dme.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_commRadio.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_transponder.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_gps.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_hold_controller.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_rnav_procedures.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_dme.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_commRadio.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_transponder.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,35 @@
/*
* 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/>.
*/
#include "test_commRadio.hxx"
#include "test_dme.hxx"
#include "test_gps.hxx"
#include "test_hold_controller.hxx"
#include "test_navRadio.hxx"
#include "test_rnav_procedures.hxx"
#include "test_transponder.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NavRadioTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(GPSTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HoldControllerTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(RNAVProcedureTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DMEReceiverTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommRadioTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TransponderTests, "Unit tests");

View File

@@ -0,0 +1,207 @@
#include "test_commRadio.hxx"
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Instrumentation/commradio.hxx>
#include <Main/fg_props.hxx>
#include <Main/locale.hxx>
// Set up function for each test.
void CommRadioTests::setUp()
{
FGTestApi::setUp::initTestGlobals("commradio");
FGTestApi::setUp::initNavDataCache();
// otherwise ATCSPeech will call locale functions and assert
globals->get_locale()->selectLanguage({});
}
// Clean up after each test.
void CommRadioTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
// std::string NavRadioTests::formatFrequency(double f)
// {
// char buf[16];
// ::snprintf(buf, 16, "%3.2f", f);
// return buf;
// }
SGSubsystemRef CommRadioTests::setupStandardRadio(const std::string& name, int index, bool enable833)
{
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", name);
configNode->setIntValue("number", index);
configNode->setBoolValue("eight-point-three", enable833);
auto r = Instrumentation::CommRadio::createInstance(configNode);
fgSetBool("/sim/atis/enabled", false);
r->bind();
r->init();
globals->add_subsystem("comm-radio", r, SGSubsystemMgr::GENERAL);
return r;
}
void CommRadioTests::testBasic()
{
auto r = setupStandardRadio("commtest", 2, false);
FGAirportRef apt = FGAirport::getByIdent("EDDM");
FGTestApi::setPositionAndStabilise(apt->geod());
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
// EDDM ATIS
n->setDoubleValue("frequencies/selected-mhz", 123.125);
r->update(1.0);
// CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("123.12"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL("EDDM"s, string{n->getStringValue("airport-id")});
CPPUNIT_ASSERT_EQUAL("ATIS"s, string{n->getStringValue("station-name")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
n->setDoubleValue("frequencies/selected-mhz", 121.72);
r->update(1.0);
CPPUNIT_ASSERT_EQUAL("121.72"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL("EDDM"s, string{n->getStringValue("airport-id")});
CPPUNIT_ASSERT_EQUAL("CLNC DEL"s, string{n->getStringValue("station-name")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
}
void CommRadioTests::testEightPointThree()
{
auto r = setupStandardRadio("commtest", 2, true);
FGAirportRef apt = FGAirport::getByIdent("EGKK");
FGTestApi::setPositionAndStabilise(apt->geod());
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
// EGKK ATIS
n->setDoubleValue("frequencies/selected-mhz", 136.525);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("136.525"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
// random 8.3Khz station
n->setDoubleValue("frequencies/selected-mhz", 120.11);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("120.110"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(338, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(120.10833, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
// select station by channel, on 8.3khz boundary
n->setIntValue("frequencies/selected-channel", 2561);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("134.005"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(2561, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(134.000, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
// select station by channel, on 25Khz boundary
n->setIntValue("frequencies/selected-channel", 2560);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("134.000"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(2560, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(134.000, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
// select by frequency
n->setDoubleValue("frequencies/selected-mhz", 120.035);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("120.035"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(326, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(120.03333, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
// under-run the permitted frequency range
n->setDoubleValue("frequencies/selected-mhz", 117.99);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(25.0, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL(0, n->getIntValue("frequencies/selected-channel"));
n->setDoubleValue("frequencies/selected-mhz", 118.705);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("118.705"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(113, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(118.700, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
// over-run the frequency range
n->setDoubleValue("frequencies/selected-mhz", 137.000);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
CPPUNIT_ASSERT_EQUAL("136.990"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
CPPUNIT_ASSERT_EQUAL(3039, n->getIntValue("frequencies/selected-channel"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(136.99166, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
}
void CommRadioTests::testEPLLTuning833()
{
// this test is disabled until data entry for EPLL is fixed
return;
auto r = setupStandardRadio("commtest", 2, true);
FGAirportRef apt = FGAirport::getByIdent("EPLL");
FGTestApi::setPositionAndStabilise(apt->geod());
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
// should be EPLL TWR
n->setDoubleValue("frequencies/selected-mhz", 124.225);
r->update(1.0);
CPPUNIT_ASSERT_EQUAL("EPLL"s, string{n->getStringValue("airport-id")});
CPPUNIT_ASSERT_EQUAL("Lodz TOWER"s, string{n->getStringValue("station-name")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
}
void CommRadioTests::testEPLLTuning25()
{
auto r = setupStandardRadio("commtest", 2, false);
FGAirportRef apt = FGAirport::getByIdent("EPLL");
FGTestApi::setPositionAndStabilise(apt->geod());
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
// should be EPLL TWR
n->setDoubleValue("frequencies/selected-mhz", 124.23);
r->update(1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(124.23, n->getDoubleValue("frequencies/selected-mhz"), 1e-6);
CPPUNIT_ASSERT_EQUAL("124.22"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
// fail for now
#if 0
CPPUNIT_ASSERT_EQUAL("EPLL"s, string{n->getStringValue("airport-id")});
CPPUNIT_ASSERT_EQUAL("Lodz TOWER"s, string{n->getStringValue("station-name")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
#endif
}

View File

@@ -0,0 +1,61 @@
/*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/structure/subsystem_mgr.hxx>
class FGNavRadio;
class SGGeod;
// The flight plan unit tests.
class CommRadioTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(CommRadioTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testEightPointThree);
CPPUNIT_TEST(testEPLLTuning833);
CPPUNIT_TEST(testEPLLTuning25);
CPPUNIT_TEST_SUITE_END();
SGSubsystemRef setupStandardRadio(const std::string& name, int index, bool enable833);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// std::string formatFrequency(double f);
// The tests.
void testBasic();
void testEightPointThree();
void testEPLLTuning833();
void testEPLLTuning25();
};

View File

@@ -0,0 +1,120 @@
#include "test_dme.hxx"
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navlist.hxx>
#include <Navaids/navrecord.hxx>
#include <Instrumentation/dme.hxx>
#include <Main/fg_props.hxx>
// Set up function for each test.
void DMEReceiverTests::setUp()
{
FGTestApi::setUp::initTestGlobals("navradio");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void DMEReceiverTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void DMEReceiverTests::setPositionAndStabilise(DME* r, const SGGeod& g)
{
FGTestApi::setPosition(g);
for (int i = 0; i < 60; ++i) {
r->update(0.1);
}
}
SGSharedPtr<DME> DMEReceiverTests::setupStandardDME()
{
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "dmetest");
configNode->setIntValue("number", 2);
return new DME(configNode);
}
void DMEReceiverTests::testBasic()
{
SGSharedPtr<DME> r = setupStandardDME();
// set a source string pointing at a fictious nav-receiver
fgSetString("/instrumentation/dmetest[2]/frequencies/source",
"/instrumentation/nav[0]/frequencies/selected-mhz");
r->bind();
r->init();
globals->get_subsystem_mgr()->add("dme", r.get());
auto arlanda = fgFindAirportID("ESSA");
// set the nav frequency
fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", 113.30);
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/dmetest[2]");
node->setBoolValue("serviceable", true);
fgSetDouble("systems/electrical/outputs/dme", 12.0);
setPositionAndStabilise(r.get(), arlanda->geod());
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(4.4, node->getDoubleValue("indicated-distance-nm"), 0.1);
// fly towards the station at constant speed
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->setSpeedKts(150);
FGPositioned::TypeFilter f{FGPositioned::DME};
FGNavRecordRef arlandaDME = fgpositioned_cast<FGNavRecord>(
FGPositioned::findClosestWithIdent("ANE", arlanda->geod(), &f));
const double trueCourseToANE = SGGeodesy::courseDeg(arlanda->geod(), arlandaDME->geod());
pilot->setCourseTrue(trueCourseToANE);
FGTestApi::runForTime(30.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(150, node->getDoubleValue("indicated-ground-speed-kt"), 0.5);
// should have travelled (150 / 3600 * 30 ) = 1.25nm
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.15, node->getDoubleValue("indicated-distance-nm"), 0.1);
}
void DMEReceiverTests::testRCFN_04DME()
{
// disabled pending discussion about the data for this one
return;
auto rcfn = fgFindAirportID("RCFN");
auto dmeReceiver = setupStandardDME();
FGRunwayRef rwy04 = rcfn->getRunwayByIdent("04");
FGPositioned::TypeFilter filter(FGPositioned::DME);
auto matches = FGPositioned::findAllWithIdent("IFNN", &filter);
FGPositioned::sortByRange(matches, rcfn->geod());
CPPUNIT_ASSERT(!matches.empty());
// should be size two, really
auto station = fgpositioned_cast<FGNavRecord>(matches.front());
CPPUNIT_ASSERT(station);
CPPUNIT_ASSERT_DOUBLES_EQUAL(110.9, station->get_freq(), 0.01);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 James Turner <james@flightgear.org>
*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/structure/SGSharedPtr.hxx>
class DME;
class SGGeod;
// The DME unit tests.
class DMEReceiverTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(DMEReceiverTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testRCFN_04DME);
CPPUNIT_TEST_SUITE_END();
void setPositionAndStabilise(DME* r, const SGGeod& g);
SGSharedPtr<DME> setupStandardDME();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBasic();
void testRCFN_04DME();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2019 James Turner
*
* 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/>.
*/
#ifndef _FG_GPS_UNIT_TESTS_HXX
#define _FG_GPS_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class GPS;
// The flight plan unit tests.
class GPSTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(GPSTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testNavRadioSlave);
CPPUNIT_TEST(testTurnAnticipation);
CPPUNIT_TEST(testOBSMode);
CPPUNIT_TEST(testDirectTo);
CPPUNIT_TEST(testLegMode);
CPPUNIT_TEST(testDirectToLegOnFlightplan);
CPPUNIT_TEST(testLongLeg);
CPPUNIT_TEST(testLongLegWestbound);
CPPUNIT_TEST(testOffsetFlight);
CPPUNIT_TEST(testOverflightSequencing);
CPPUNIT_TEST(testOffcourseSequencing);
CPPUNIT_TEST(testLegIntercept);
CPPUNIT_TEST(testDirectToLegOnFlightplanAndResumeBuiltin);
CPPUNIT_TEST(testBuiltinRevertToOBSAtEnd);
CPPUNIT_TEST(testRadialIntercept);
CPPUNIT_TEST(testSWIFT8);
CPPUNIT_TEST(testDMEIntercept);
CPPUNIT_TEST(testFinalLegCourse);
CPPUNIT_TEST(testCourseLegIntermediateWaypoint);
CPPUNIT_TEST(testExceedFlyByMaxAngleTurn);
CPPUNIT_TEST(testFlyOverMaxInterceptAngle);
CPPUNIT_TEST_SUITE_END();
void setPositionAndStabilise(GPS* gps, const SGGeod& g);
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
const std::string name = "gps", const int index = 0);
void setupRouteManager();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBasic();
void testNavRadioSlave();
void testTurnAnticipation();
void testOBSMode();
void testDirectTo();
void testLegMode();
void testDirectToLegOnFlightplan();
void testLongLeg();
void testLongLegWestbound();
void testOffsetFlight();
void testOffcourseSequencing();
void testOverflightSequencing();
void testLegIntercept();
void testDirectToLegOnFlightplanAndResumeBuiltin();
void testBuiltinRevertToOBSAtEnd();
void testRadialIntercept();
void testSWIFT8();
void testDMEIntercept();
void testFinalLegCourse();
void testCourseLegIntermediateWaypoint();
void testExceedFlyByMaxAngleTurn();
void testFlyOverMaxInterceptAngle();
};
#endif // _FG_GPS_UNIT_TESTS_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2019 James Turner
*
* 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/>.
*/
#ifndef _FG_HOLD_CTL_UNIT_TESTS_HXX
#define _FG_HOLD_CTL_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class GPS;
// The flight plan unit tests.
class HoldControllerTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(HoldControllerTests);
CPPUNIT_TEST(testHoldEntryDirect);
CPPUNIT_TEST(testHoldEntryTeardrop);
CPPUNIT_TEST(testHoldEntryParallel);
CPPUNIT_TEST(testLeftHoldEntryDirect);
CPPUNIT_TEST(testLeftHoldEntryTeardrop);
CPPUNIT_TEST(testLeftHoldEntryParallel);
CPPUNIT_TEST(testHoldNotEntered);
CPPUNIT_TEST(testHoldEntryOffCourse);
CPPUNIT_TEST_SUITE_END();
void setPositionAndStabilise(const SGGeod& g);
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
const std::string name = "gps", const int index = 0);
void setupRouteManager();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testHoldEntryDirect();
void testHoldEntryTeardrop();
void testHoldEntryParallel();
void testLeftHoldEntryDirect();
void testLeftHoldEntryTeardrop();
void testLeftHoldEntryParallel();
void testHoldNotEntered();
void testHoldEntryOffCourse();
private:
GPS* m_gps = nullptr;
SGPropertyNode_ptr m_gpsNode;
};
#endif // _FG_HOLD_CTL_UNIT_TESTS_HXX

View File

@@ -0,0 +1,840 @@
#include "test_navRadio.hxx"
#include <memory>
#include <cstring>
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx>
#include <Instrumentation/navradio.hxx>
// Set up function for each test.
void NavRadioTests::setUp()
{
FGTestApi::setUp::initTestGlobals("navradio");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void NavRadioTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void NavRadioTests::setPositionAndStabilise(FGNavRadio* r, const SGGeod& g)
{
FGTestApi::setPosition(g);
for (int i=0; i<60; ++i) {
r->update(0.1);
}
}
std::string NavRadioTests::formatFrequency(double f)
{
char buf[16];
::snprintf(buf, 16, "%3.2f", f);
return buf;
}
void NavRadioTests::testBasic()
{
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
node->setDoubleValue("frequencies/selected-mhz", 113.8);
SGGeod pos = SGGeod::fromDegFt(-3.352780, 55.499199, 20000);
setPositionAndStabilise(r.get(), pos);
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("operable"));
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "TLA");
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
}
static const struct {
int nvType;
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvTwst;
const string& nvIden;
double onRose, atDstNM, atAltFt, atHdg, rSele;
bool vOpnl, vToFlag;
double vSigNorm, vSigTolr, vHdgDefl, vDeflTolr, vHdgNorm, vDefnTolr, xtkTolr;
const string& tDesc;
} CDITestRoll[] = {
//
// Test Items: Add test cases here:
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial =><= v- Values expected / tested => <= Line / Desc for Mesg =>
//Type Lat Lon Alt Freq Rnge Twist Iden] onRose atNm atAlt atHdg rSele Op To sigN - Tolr Defl - Tolr DNrm -Tolr xtkTolr
//
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 25, 10.0, 4000, 200, 25, 1, 0, 1.0, 0.01, 0.0, 9.01, 0.0, 0.01, 50.0, "1: MCT EGCC On Radial" },
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 25, 10.0, 4000, 200, 25, 1, 0, 1.0, 0.01, 0.0, 9.01, 0.0, 0.01, 50.0, "2: MCT EGCC On Again " },
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 20, 20.0, 12000, 20, 25, 1, 0, 1.0, 0.01, 5.0, 0.1, 0.5, 0.01, 50.0, "3: MCT 5deg Off radial" },
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 33, 30.0, 16000, 100, 25, 1, 0, 1.0, 0.01, -8.0, 0.1, -0.8, 0.01, 50.0, "4: MCT 8deg Off radial" },
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 38, 40.0, 16000, 280, 25, 1, 0, 1.0, 0.01, -10.0, 0.1, -1.0, 0.01, 50.0, "5: MCT >10 Off radial" },
{ 3, -31.9, 115.95, 87, 113.70, 130, -2.0, "PH", 222, 20.0, 12000, 220, 42, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "6: PH Perth W.Aus On Radial"},
{ 3, -31.9, 115.95, 87, 113.70, 130, -2.0, "PH", 225, 20.0, 18000, 220, 42, 1, 1, 1.0, 0.01, 3.0, 0.01, 0.3, 0.01, 50.0, "7: PH +3deg Off radial" }
};
void NavRadioTests::callNavRadioCDI() {
//
//2021Ja15 set flag for newnavradio
//
fgSetBool("/instrumentation/use-new-navradio", true);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
int tale = sizeof(CDITestRoll) / sizeof(CDITestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string& itemDesc = " navradioCDI Item " + CDITestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", CDITestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", CDITestRoll[i].rSele);
// tbd Filter on type as defined in nav dat
//FGPositioned::TypeFilter f{FGPositioned::VOR};
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(CDITestRoll[i].nvIden,
SGGeod::fromDeg(CDITestRoll[i].nvLon, CDITestRoll[i].nvLat), &f));
//
// For VOR nav dat field 7: 'Twist' == Easterly rotation of Txmitter's 360 wrt True North c.f for Compass: 'Deviation West Rose is Best'
// Rx posn is specified according to navaid's radials as printed on chart: True Bng = ( Radial on Rose + Twist ( Deviation ))
// ( ftr: Both MCT -5 and PH -2 Are Negative Twists )
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (CDITestRoll[i].onRose + CDITestRoll[i].nvTwst),
(CDITestRoll[i].atDstNM * SG_NM_TO_METER));
posWrtRadial.setElevationFt(CDITestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), posWrtRadial);
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
double bngToNavaid, az2, s;
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
// calc XTrack error
double xtkE = sin((CDITestRoll[i].rSele - CDITestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (CDITestRoll[i].atDstNM * SG_NM_TO_METER);
// Verify expected vs Result
string tMesg = itemDesc + "VOR type";
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::VOR);
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vOpnl, node->getBoolValue("operable"));
tMesg = itemDesc + "TO Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, node->getBoolValue("to-flag"));
tMesg = itemDesc + "FROM Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
//
tMesg = itemDesc + "nav-id";
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == CDITestRoll[i].nvIden);
//tbd VOR seems to not set selected-mhz-fmt
// Converting nvFreq to string results in trailing zeros
tMesg = itemDesc + "selected-mhz-fmt";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(CDITestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
tMesg = itemDesc + "actual-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), CDITestRoll[i].vDefnTolr);
// heading-deg means true bearing to navaid, not affected by plane's heading
tMesg = itemDesc + "heading-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
//
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), CDITestRoll[i].vSigTolr);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgDefl, node->getDoubleValue("heading-needle-deflection"), CDITestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgNorm, node->getDoubleValue("heading-needle-deflection-norm"), CDITestRoll[i].vDefnTolr);
tMesg = itemDesc + "xTrack error";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), CDITestRoll[i].xtkTolr);
}
}
void NavRadioTests::callNewNavRadioCDI()
{
//
//2021Ja15 set flag for newnavradio
//
fgSetBool("/instrumentation/use-new-navradio", true);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
int tale = sizeof(CDITestRoll) / sizeof(CDITestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string& itemDesc = " navradioCDI Item " + CDITestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", CDITestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", CDITestRoll[i].rSele);
// tbd Filter on type as defined in nav dat
//FGPositioned::TypeFilter f{FGPositioned::VOR};
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(CDITestRoll[i].nvIden,
SGGeod::fromDeg(CDITestRoll[i].nvLon, CDITestRoll[i].nvLat), &f));
//
// For VOR nav dat field 7: 'Twist' == Easterly rotation of Txmitter's 360 wrt True North c.f for Compass: 'Deviation West Rose is Best'
// Rx posn is specified according to navaid's radials as printed on chart: True Bng = ( Radial on Rose + Twist ( Deviation ))
// ( ftr: Both MCT -5 and PH -2 Are Negative Twists )
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (CDITestRoll[i].onRose + CDITestRoll[i].nvTwst),
(CDITestRoll[i].atDstNM * SG_NM_TO_METER));
posWrtRadial.setElevationFt(CDITestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), posWrtRadial);
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
double bngToNavaid, az2, s;
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
// calc XTrack error
double xtkE = sin((CDITestRoll[i].rSele - CDITestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (CDITestRoll[i].atDstNM * SG_NM_TO_METER);
// Verify expected vs Result
string tMesg = itemDesc + "VOR type";
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::VOR);
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vOpnl, node->getBoolValue("operable"));
tMesg = itemDesc + "TO Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, node->getBoolValue("to-flag"));
tMesg = itemDesc + "FROM Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
//
tMesg = itemDesc + "nav-id";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].nvIden, string{node->getStringValue("nav-id")});
// Converting nvFreq to string results in trailing zeros
tMesg = itemDesc + "selected-mhz-fmt";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(CDITestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
tMesg = itemDesc + "actual-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), CDITestRoll[i].vDefnTolr);
// heading-deg means true bearing to navaid, not affected by plane's heading
tMesg = itemDesc + "heading-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
//
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), CDITestRoll[i].vSigTolr);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgDefl, node->getDoubleValue("heading-needle-deflection"), CDITestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgNorm, node->getDoubleValue("heading-needle-deflection-norm"), CDITestRoll[i].vDefnTolr);
tMesg = itemDesc + "xTrack error";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), CDITestRoll[i].xtkTolr);
}
}
static const struct {
int nvType;
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvTwst;
const string& nvIden;
double onRose, atDstNM, atAltFt, atHdg, rSele;
bool vOpnl, vToFlag;
double vSigNorm, vSigTolr, vHdgDefl, vDeflTolr, vHdgNorm, vDefnTolr, xtkTolr;
const string& tDesc;
} ILSTestRoll[] = {
//
// Ref pilotscafe.com ILS width: 700ft wide at thrsh. WIthin range, sensed at +-35dg @ 10NM +-10dg @ 18NM
//
// Test Items: Add test cases here:
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial => <= v- Values expected / tested => <= Item - Desc =>
//Typ Lat Lon Alt Freq Rnge TruHdng Iden] onRose atNm atAlt atHdg rSele Op To sigN - Tolr Defl - Tolr DNrm -Tolr xtkTol ]
//
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 117.932, 2.5, 2500, 27, 297.932, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "1: ISFO On LOC"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 116.932, 6.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -1.0, 0.10, -0.1, 0.01, 50.0, "2: ISFO -1 Deg"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 118.932, 6.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 1.0, 0.01, -0.1, 0.01, 50.0, "3: ISFO +1 Deg"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 113.932, 3.0, 600, 27, 297.932, 1, 1, 1.0, 0.01, -3.0, 0.10, -0.1, 0.01, 50.0, "4: ISFO < MinDefl"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 121.932, 3.0, 600, 27, 297.932, 1, 1, 1.0, 0.01, 3.0, 0.01, -0.1, 0.01, 50.0, "5: ISFO > MzxDefl"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 297.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "6: ISFO BC On LOC"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 296.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 1.0, 0.10, -0.1, 0.01, 50.0, "7: ISFO BC -1 Deg"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 298.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -1.0, 0.01, -0.1, 0.01, 50.0, "8: ISFO BC +1 Deg"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 293.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 3.0, 0.10, -0.1, 0.01, 50.0, "9: ISFO BC > MaxD"}, //
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 301.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -3.0, 0.01, -0.1, 0.01, 50.0, "10: ISFO BC < MinD"} //
};
void NavRadioTests::callNavRadioILS()
{
// set flag for newnavradio
fgSetBool("/instrumentation/use-new-navradio", false);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
int tale = sizeof(ILSTestRoll) / sizeof(ILSTestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string& itemDesc = " navRadioILS Item " + ILSTestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", ILSTestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", ILSTestRoll[i].rSele);
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(ILSTestRoll[i].nvIden,
SGGeod::fromDeg(ILSTestRoll[i].nvLon, ILSTestRoll[i].nvLat), &f));
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (ILSTestRoll[i].onRose), (ILSTestRoll[i].atDstNM * SG_NM_TO_METER));
posWrtRadial.setElevationFt(ILSTestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), posWrtRadial);
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
double bngToNavaid, az2, s;
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
double xtkE = sin((ILSTestRoll[i].rSele - ILSTestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (ILSTestRoll[i].atDstNM * SG_NM_TO_METER);
//
const double locWidth = nav->localizerWidth();
// Expected Defl / Scaling is hokey because ILS width varies ??
const double deflectionScale = 20.0 / locWidth; // 20 degrees is full VOR swing (-10 to +10 degrees)
double xpecDefl = (ILSTestRoll[i].vHdgDefl * deflectionScale);
xpecDefl = (xpecDefl > 10) ? 10 : xpecDefl;
xpecDefl = (xpecDefl < -10) ? -10 : xpecDefl;
//
// Verify expected: Operational and To flags
string tMesg = itemDesc + "ILS type";
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::ILS);
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vOpnl, node->getBoolValue("operable"));
tMesg = itemDesc + "TO Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vToFlag, node->getBoolValue("to-flag"));
tMesg = itemDesc + "FROM Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
//
tMesg = itemDesc + "heading-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
tMesg = itemDesc + "nav-id";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].nvIden, string{node->getStringValue("nav-id")});
// Converting nvFreq to string results in trailing zeros
tMesg = itemDesc + "selected-mhz-fmt";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(ILSTestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
tMesg = itemDesc + "actual-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), ILSTestRoll[i].vDefnTolr);
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), ILSTestRoll[i].vSigTolr);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("heading-needle-deflection"), ILSTestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, (xpecDefl * 0.1), node->getDoubleValue("heading-needle-deflection-norm"), ILSTestRoll[i].vDefnTolr);
tMesg = itemDesc + "xTrack error";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), ILSTestRoll[i].xtkTolr);
}
}
void NavRadioTests::callNewNavRadioILS()
{
// set flag for newnavradio
fgSetBool("/instrumentation/use-new-navradio", true);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
int tale = sizeof(ILSTestRoll) / sizeof(ILSTestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string & itemDesc = "newNavRadioILS Item " + ILSTestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", ILSTestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", ILSTestRoll[i].rSele);
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(ILSTestRoll[i].nvIden, \
SGGeod::fromDeg( ILSTestRoll[i].nvLon, ILSTestRoll[i].nvLat), &f));
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (ILSTestRoll[i].onRose ), (ILSTestRoll[i].atDstNM * SG_NM_TO_METER));
posWrtRadial.setElevationFt(ILSTestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), posWrtRadial);
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
double bngToNavaid, az2, s;
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
double xtkE = sin( (ILSTestRoll[i].rSele - ILSTestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) \
* ( ILSTestRoll[i].atDstNM * SG_NM_TO_METER ) ;
//
const double locWidth = nav->localizerWidth();
// Expected Defl / Scaling is hokey because ILS width varies ??
const double deflectionScale = 20.0 / locWidth; // 20 degrees is full VOR swing (-10 to +10 degrees)
double xpecDefl = (ILSTestRoll[i].vHdgDefl * deflectionScale);
xpecDefl = ( xpecDefl > 10 ) ? 10 : xpecDefl;
xpecDefl = ( xpecDefl < -10 ) ? -10 : xpecDefl;
//
// Verify expected: Operational and To flags
string tMesg = itemDesc + "ILS type";
CPPUNIT_ASSERT_MESSAGE( tMesg, nav->type() == FGPositioned::ILS);
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vOpnl, node->getBoolValue("operable"));
tMesg = itemDesc + "TO Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vToFlag, node->getBoolValue("to-flag"));
tMesg = itemDesc + "FROM Flag";
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
//
tMesg = itemDesc + "heading-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
tMesg = itemDesc + "nav-id";
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].nvIden, string{node->getStringValue("nav-id")});
// Converting nvFreq to string results in trailing zeros
tMesg = itemDesc + "selected-mhz-fmt";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(ILSTestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
tMesg = itemDesc + "actual-deg";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), ILSTestRoll[i].vDefnTolr);
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), ILSTestRoll[i].vSigTolr);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, xpecDefl, node->getDoubleValue("heading-needle-deflection"), ILSTestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, (xpecDefl * 0.1 ), node->getDoubleValue("heading-needle-deflection-norm"), ILSTestRoll[i].vDefnTolr);
tMesg = itemDesc + "xTrack error";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), ILSTestRoll[i].xtkTolr);
}
}
static const struct {
int nvType;
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvAzim;
const string& nvIden;
double onRose, atDstNM, atAltFt, plusDeg, atHdg, rTruDeg;
bool vInRnge, vFalse;
double vSigNorm, vSigTolr, vGSDefl, vDeflTolr, vGSDefn, vDefnTolr;
const string& tDesc;
} GSTestRoll[] = {
//
// Test Items: Add test cases here:
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial => <= v- Values expected / tested => <= Item - Desc =>
//Typ Lat Lon Alt Freq Rnge GSAzim Iden] onRose atNm atAlt or Deg atHdg TruDeg Rng Fls SgN - Tolr GSDefl-Tolr GSDefn-Tolr ]
//
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 8.0, 0, 0, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "1: EDDT 26R +0 deg" }, //
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 4.0, 0, 0.50, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "2: EDDT 26R +0.5 d" }, //
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 2.0, 0, -1.00, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "3: EDDT 26R -1 deg" }, //
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 5.0, 0, 3.00, 80.828, 260.857, 1, 1, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "4: EDDT 26R +3.0 Fls"}, //
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 3.0, 0, +2.65, 80.828, 260.857, 1, 1, 1.0, 0.01,-1.75, 0.1, -0.5, 0.01, "5: EDDT 26R +3.5 Fls"}, //
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690, 7.5, 2500, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "6: EGLL 27L 2K5 7M5"}, // Fail: Rx finds IBB
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690, 9.0, 3000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "7: EGLL 27L 3K0 9M0"}, //
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690,17.5, 4000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "8: EGLL 27L 4K 17M5"}, //
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690,25.0, 4000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "9: EGLL 27L 4K 25M0"} //
};
void NavRadioTests::callNavRadioGS() {
//set flag for newnavradio
fgSetBool("/instrumentation/use-new-navradio", false);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
// GS beam depth +-0.7deg; needle deflection -+3.5; gs-direct: Rxvr elevation from GS Txmitter level
//
const double halfBeam = 0.700;
const double deflFact = 3.500;
//
int tale = sizeof(GSTestRoll) / sizeof(GSTestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string& itemDesc = " navRadioGS Item " + GSTestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", GSTestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", GSTestRoll[i].rTruDeg);
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::GS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(GSTestRoll[i].nvIden,
SGGeod::fromDeg(GSTestRoll[i].nvLon, GSTestRoll[i].nvLat), &f));
// Check for proper nav type befor doing GS things
string tMesg = itemDesc + "GS Type ?";
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::GS);
/////////////
// derive the GS geometry in cartesian vectors, to match what navradio.cxx does
SGGeod aboveGS = nav->geod();
aboveGS.setElevationM(nav->geod().getElevationM() + 100);
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - nav->cart();
// intentionally different approach to what navradio uses
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
// derive the baseline
SGQuatd baseLineRot = SGQuatd::fromLonLat(nav->geod()) * SGQuatd::fromHeadAttBankDeg(GSTestRoll[i].atHdg, 0, 0);
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
const SGVec3d gsCart = nav->cart();
//////////////////
// expected deflection is calculated here if atAltFt is non-zero
double xpecAzim, xpecDefl, xpecDefn;
double bngToNavaid, az2, s;
if (GSTestRoll[i].atAltFt == 0) {
// Line item atAlt is zero so use degrees off GlideSlope for Rx position
double gsAngleRad = (nav->glideSlopeAngleDeg() + GSTestRoll[i].plusDeg) * SG_DEGREES_TO_RADIANS;
SGVec3d radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
radioPos += (gsAltAxis * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
xpecAzim = (GSTestRoll[i].nvAzim + GSTestRoll[i].plusDeg);
} else {
// Line item atAlt is non zero so use altitude for Rx position
SGGeod p = SGGeodesy::direct(nav->geod(), GSTestRoll[i].atAltFt, GSTestRoll[i].atDstNM * SG_NM_TO_METER);
p.setElevationFt(GSTestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), p);
//tbd calc Rx Azim from Tx for altitude case
SGGeodesy::inverse(p, nav->geod(), bngToNavaid, az2, s);
xpecAzim = SG_RADIANS_TO_DEGREES * (atan((GSTestRoll[i].atAltFt * SG_FEET_TO_METER) / s));
}
//
if (GSTestRoll[i].vFalse) {
// rxFlse indicates false signal, use deflections manually entered in Item
xpecDefl = GSTestRoll[i].vGSDefl;
xpecDefn = GSTestRoll[i].vGSDefn;
} else {
if (GSTestRoll[i].atAltFt == 0) {
// not rxFlse and atAltFt is zero : calculate needle deflections from Rx posn degrees wrt beam
xpecDefn = 0 - GSTestRoll[i].plusDeg / halfBeam; // Plane high: needle below
} else {
// not rxFlse and atAltFt non zero : calculate needle deflections from Rx posn azimuth wrt beam
xpecDefn = (GSTestRoll[i].nvAzim - az2) / halfBeam; // Plane high: needle below;
}
xpecDefn = (xpecDefn > 1) ? 1 : xpecDefn;
xpecDefn = (xpecDefn < -1) ? -1 : xpecDefn;
xpecDefl = xpecDefn * deflFact;
}
//
// Verify expected:
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vInRnge, node->getBoolValue("operable"));
tMesg = itemDesc + "nav-id";
string dddbug = node->getStringValue("nav-id");
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == GSTestRoll[i].nvIden);
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), GSTestRoll[i].vSigTolr);
tMesg = itemDesc + "gs-direct";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecAzim, node->getDoubleValue("gs-direct-deg"), 1);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("gs-needle-deflection"), GSTestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefn, node->getDoubleValue("gs-needle-deflection-norm"), GSTestRoll[i].vDefnTolr);
//
}
}
void NavRadioTests::callNewNavRadioGS()
{
//set flag for newnavradio
fgSetBool("/instrumentation/use-new-navradio", true);
// setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
// needed for the radio to power up
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
//
// GS beam depth +-0.7deg; needle deflection -+3.5; gs-direct: Rxvr elevation from GS Txmitter level
//
const double halfBeam = 0.700;
const double deflFact = 3.500;
//
int tale = sizeof(GSTestRoll) / sizeof(GSTestRoll[0]);
for (int i = 0; (i < tale); i++) {
// prep error message
const string& itemDesc = "newnavRadioGS Item " + GSTestRoll[i].tDesc + " @ ";
// Txmitting navaid
node->setDoubleValue("frequencies/selected-mhz", GSTestRoll[i].nvFreq);
node->setDoubleValue("radials/selected-deg", GSTestRoll[i].rTruDeg);
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::GS, FGPositioned::LOC}};
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(GSTestRoll[i].nvIden,
SGGeod::fromDeg(GSTestRoll[i].nvLon, GSTestRoll[i].nvLat), &f));
// Check for proper nav type befor doing GS things
string tMesg = itemDesc + "GS Type ?";
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::GS);
/////////////
// derive the GS geometry in cartesian vectors, to match what navradio.cxx does
SGGeod aboveGS = nav->geod();
aboveGS.setElevationM(nav->geod().getElevationM() + 100);
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - nav->cart();
// intentionally different approach to what navradio uses
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
// derive the baseline
SGQuatd baseLineRot = SGQuatd::fromLonLat(nav->geod()) * SGQuatd::fromHeadAttBankDeg(GSTestRoll[i].atHdg, 0, 0);
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
const SGVec3d gsCart = nav->cart();
//////////////////
// expected deflection is calculated here if atAltFt is non-zero
double xpecAzim, xpecDefl, xpecDefn;
double bngToNavaid, az2, s;
if (GSTestRoll[i].atAltFt == 0) {
// Line item atAlt is zero so use degrees off GlideSlope for Rx position
double gsAngleRad = (nav->glideSlopeAngleDeg() + GSTestRoll[i].plusDeg) * SG_DEGREES_TO_RADIANS;
SGVec3d radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
radioPos += (gsAltAxis * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
xpecAzim = (GSTestRoll[i].nvAzim + GSTestRoll[i].plusDeg);
} else {
// Line item atAlt is non zero so use altitude for Rx position
SGGeod p = SGGeodesy::direct(nav->geod(), GSTestRoll[i].atAltFt, GSTestRoll[i].atDstNM * SG_NM_TO_METER);
p.setElevationFt(GSTestRoll[i].atAltFt);
setPositionAndStabilise(r.get(), p);
//tbd calc Rx Azim from Tx for altitude case
SGGeodesy::inverse(p, nav->geod(), bngToNavaid, az2, s);
xpecAzim = SG_RADIANS_TO_DEGREES * (atan((GSTestRoll[i].atAltFt * SG_FEET_TO_METER) / s));
}
//
if (GSTestRoll[i].vFalse) {
// rxFlse indicates false signal, use deflections manually entered in Item
xpecDefl = GSTestRoll[i].vGSDefl;
xpecDefn = GSTestRoll[i].vGSDefn;
} else {
if (GSTestRoll[i].atAltFt == 0) {
// not rxFlse and atAltFt is zero : calculate needle deflections from Rx posn degrees wrt beam
xpecDefn = 0 - GSTestRoll[i].plusDeg / halfBeam; // Plane high: needle below
} else {
// not rxFlse and atAltFt non zero : calculate needle deflections from Rx posn azimuth wrt beam
xpecDefn = (GSTestRoll[i].nvAzim - az2) / halfBeam; // Plane high: needle below;
}
xpecDefn = (xpecDefn > 1) ? 1 : xpecDefn;
xpecDefn = (xpecDefn < -1) ? -1 : xpecDefn;
xpecDefl = xpecDefn * deflFact;
}
//
// Verify expected:
tMesg = itemDesc + "Operable";
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vInRnge, node->getBoolValue("operable"));
tMesg = itemDesc + "nav-id";
string dddbug = node->getStringValue("nav-id");
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == GSTestRoll[i].nvIden);
tMesg = itemDesc + "Sig Norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), GSTestRoll[i].vSigTolr);
tMesg = itemDesc + "gs-direct";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecAzim, node->getDoubleValue("gs-direct-deg"), 1);
tMesg = itemDesc + "needle defl";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("gs-needle-deflection"), GSTestRoll[i].vDeflTolr);
tMesg = itemDesc + "defl norm";
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefn, node->getDoubleValue("gs-needle-deflection-norm"), GSTestRoll[i].vDefnTolr);
//
}
}
void NavRadioTests::testGS()
{
// radio setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
// EDDT 28R
FGPositioned::TypeFilter f{FGPositioned::GS};
FGNavRecordRef gs = fgpositioned_cast<FGNavRecord>(
FGPositioned::findClosestWithIdent("ITLW", SGGeod::fromDeg(13, 52), &f));
CPPUNIT_ASSERT(gs->type() == FGPositioned::GS);
node->setDoubleValue("frequencies/selected-mhz", 110.10);
CPPUNIT_ASSERT(node->getStringValue("frequencies/selected-mhz-fmt") == "110.10");
CPPUNIT_ASSERT_DOUBLES_EQUAL(gs->glideSlopeAngleDeg(), 3.0, 0.001);
double gsAngleRad = gs->glideSlopeAngleDeg() * SG_DEGREES_TO_RADIANS;
/////////////
// derive the GS geometry in cartesian vectors, to match what
// navradio.cxx does
SGGeod aboveGS = gs->geod();
aboveGS.setElevationM(gs->geod().getElevationM() + 100.0);
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - gs->cart();
// intentionally different approach to what navradio uses
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
// dervice the baseline
SGQuatd baseLineRot = SGQuatd::fromLonLat(gs->geod()) * SGQuatd::fromHeadAttBankDeg(80.828, 0, 0);
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
const SGVec3d gsCart = gs->cart();
//////////////////
SGVec3d radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 8 * SG_NM_TO_METER);
radioPos += (gsAltAxis * 8 * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "ITLW");
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, node->getDoubleValue("gs-direct-deg"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
// 0.5 degree offset above
gsAngleRad = (gs->glideSlopeAngleDeg() + 0.5) * SG_DEGREES_TO_RADIANS;
radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 4 * SG_NM_TO_METER);
radioPos += (gsAltAxis * 4 * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "ITLW");
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.5, node->getDoubleValue("gs-direct-deg"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.5, node->getDoubleValue("gs-needle-deflection"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.714, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
// 1 degree below (danger!)
gsAngleRad = (gs->glideSlopeAngleDeg() - 1.0) * SG_DEGREES_TO_RADIANS;
radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 2 * SG_NM_TO_METER);
radioPos += (gsAltAxis * 2 * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, node->getDoubleValue("gs-direct-deg"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.5, node->getDoubleValue("gs-needle-deflection"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
// false course above, reversed
gsAngleRad = (gs->glideSlopeAngleDeg() + 3.0) * SG_DEGREES_TO_RADIANS;
radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 5 * SG_NM_TO_METER);
radioPos += (gsAltAxis * 5 * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(6.0, node->getDoubleValue("gs-direct-deg"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
// false course above, reversed, 0.35 offset below
gsAngleRad = (gs->glideSlopeAngleDeg() + 2.65) * SG_DEGREES_TO_RADIANS;
radioPos = gsCart;
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 3 * SG_NM_TO_METER);
radioPos += (gsAltAxis * 3 * SG_NM_TO_METER);
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(5.65, node->getDoubleValue("gs-direct-deg"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.75, node->getDoubleValue("gs-needle-deflection"), 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.5, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
}
void NavRadioTests::testILSFalseCourse()
{
// also GS false lobes
}
void NavRadioTests::testILSPaired()
{
// EGPH and countless more
}
void NavRadioTests::testILSAdjacentPaired()
{
// eg KJFK
}
void NavRadioTests::testGlideslopeLongDistance()
{
// radio setup
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "navtest");
configNode->setIntValue("number", 2);
std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode));
r->bind();
r->init();
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]");
node->setBoolValue("serviceable", true);
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
// EGLL 27L
FGPositioned::TypeFilter f{FGPositioned::GS};
FGNavRecordRef gs = fgpositioned_cast<FGNavRecord>(
FGPositioned::findClosestWithIdent("ILL", SGGeod::fromDeg(0, 51), &f));
CPPUNIT_ASSERT(gs->type() == FGPositioned::GS);
node->setDoubleValue("frequencies/selected-mhz", 109.50);
CPPUNIT_ASSERT(node->getStringValue("frequencies/selected-mhz-fmt") == "109.50");
CPPUNIT_ASSERT_DOUBLES_EQUAL(gs->glideSlopeAngleDeg(), 3.0, 0.001);
double gsAngleRad = gs->glideSlopeAngleDeg() * SG_DEGREES_TO_RADIANS;
// standard approach (per charts)
SGGeod p = SGGeodesy::direct(gs->geod(), 90, 7.5 * SG_NM_TO_METER);
p.setElevationFt(2500);
setPositionAndStabilise(r.get(), p);
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("gs-in-range"));
// normal approach
p = SGGeodesy::direct(gs->geod(), 90, 9 * SG_NM_TO_METER);
p.setElevationFt(3000);
setPositionAndStabilise(r.get(), p);
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("gs-in-range"));
// in our current nav data, the GS range is defined as 10nm, so the gs-in-range
// is false for these
// 4000 feet intercept
p = SGGeodesy::direct(gs->geod(), 90, 12 * SG_NM_TO_METER);
p.setElevationFt(4000);
setPositionAndStabilise(r.get(), p);
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
// further back
p = SGGeodesy::direct(gs->geod(), 90, 17.5 * SG_NM_TO_METER);
p.setElevationFt(4000);
setPositionAndStabilise(r.get(), p);
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
// really pushing it
p = SGGeodesy::direct(gs->geod(), 90, 25 * SG_NM_TO_METER);
p.setElevationFt(4000);
setPositionAndStabilise(r.get(), p);
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
}

View File

@@ -0,0 +1,82 @@
/*
* 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/>.
*/
#ifndef _FG_NAVRADIO_UNIT_TESTS_HXX
#define _FG_NAVRADIO_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
class FGNavRadio;
class SGGeod;
// The flight plan unit tests.
class NavRadioTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(NavRadioTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(callNavRadioCDI);
CPPUNIT_TEST(callNewNavRadioCDI);
CPPUNIT_TEST(callNavRadioILS);
CPPUNIT_TEST(callNewNavRadioILS);
CPPUNIT_TEST(callNavRadioGS);
CPPUNIT_TEST(callNewNavRadioGS);
CPPUNIT_TEST(testGS);
CPPUNIT_TEST(testILSFalseCourse);
CPPUNIT_TEST(testILSPaired);
CPPUNIT_TEST(testILSAdjacentPaired);
CPPUNIT_TEST(testGlideslopeLongDistance);
CPPUNIT_TEST_SUITE_END();
void setPositionAndStabilise(FGNavRadio* r, const SGGeod& g);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
std::string formatFrequency(double f);
// The tests.
void testBasic();
void callNavRadioCDI();
void callNewNavRadioCDI();
void callNavRadioILS();
void callNewNavRadioILS();
void callNavRadioGS();
void callNewNavRadioGS();
void testGS();
void testILSFalseCourse();
void testILSPaired();
void testILSAdjacentPaired();
void testGlideslopeLongDistance();
};
#endif // _FG_NAVRADIO_UNIT_TESTS_HXX

View File

@@ -0,0 +1,702 @@
/*
* Copyright (C) 2019 James Turner
*
* 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/>.
*/
#include "test_rnav_procedures.hxx"
#include <memory>
#include <cstring>
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include <simgear/structure/exception.hxx>
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx>
#include <Navaids/FlightPlan.hxx>
#include <Instrumentation/gps.hxx>
#include <Autopilot/route_mgr.hxx>
using namespace flightgear;
/////////////////////////////////////////////////////////////////////////////
namespace {
class TestFPDelegate : public FlightPlan::Delegate
{
public:
FlightPlanRef thePlan;
int sequenceCount = 0;
virtual ~TestFPDelegate()
{
}
void sequence() override
{
++sequenceCount;
int newIndex = thePlan->currentIndex() + 1;
if (newIndex >= thePlan->numLegs()) {
thePlan->finish();
return;
}
thePlan->setCurrentIndex(newIndex);
}
void currentWaypointChanged() override
{
}
void departureChanged() override
{
// mimic the default delegate, inserting the SID waypoints
// clear anything existing
thePlan->clearWayptsWithFlag(WPT_DEPARTURE);
// 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(), 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++);
}
}
}
void arrivalChanged() override
{
// mimic the default delegate, inserting the STAR waypoints
// clear anything existing
thePlan->clearWayptsWithFlag(WPT_ARRIVAL);
// insert waypt for the destination runway
auto dr = new RunwayWaypt(thePlan->destinationRunway(), thePlan);
dr->setFlag(WPT_ARRIVAL);
dr->setFlag(WPT_GENERATED);
auto leg = thePlan->insertWayptAtIndex(dr, -1);
if (thePlan->star()) {
WayptVec starRoute;
bool ok = thePlan->star()->route(thePlan->destinationRunway(), thePlan->starTransition(), starRoute);
if (!ok)
throw sg_exception("failed to route via STAR");
int insertIndex = leg->index();
for (auto w : starRoute) {
w->setFlag(WPT_ARRIVAL);
w->setFlag(WPT_GENERATED);
thePlan->insertWayptAtIndex(w, insertIndex++);
}
}
}
};
} // of anonymous namespace
/////////////////////////////////////////////////////////////////////////////
// Set up function for each test.
void RNAVProcedureTests::setUp()
{
FGTestApi::setUp::initTestGlobals("rnav-procedures");
FGTestApi::setUp::initNavDataCache();
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
if (proceduresPath.exists()) {
globals->append_fg_scenery(proceduresPath);
}
setupRouteManager();
}
// Clean up after each test.
void RNAVProcedureTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
GPS* RNAVProcedureTests::setupStandardGPS(SGPropertyNode_ptr config,
const std::string name, const int index)
{
SGPropertyNode_ptr configNode(config.valid() ? config
: SGPropertyNode_ptr{new SGPropertyNode});
configNode->setStringValue("name", name);
configNode->setIntValue("number", index);
GPS* gps(new GPS(configNode));
m_gps = gps;
m_gpsNode = globals->get_props()->getNode("instrumentation", true)->getChild(name, index, true);
m_gpsNode->setBoolValue("serviceable", true);
globals->get_props()->setDoubleValue("systems/electrical/outputs/gps", 6.0);
gps->bind();
gps->init();
globals->add_subsystem("gps", gps, SGSubsystemMgr::POST_FDM);
return gps;
}
void RNAVProcedureTests::setupRouteManager()
{
auto rm = globals->add_new_subsystem<FGRouteMgr>(SGSubsystemMgr::GENERAL);
rm->bind();
rm->init();
rm->postinit();
}
void RNAVProcedureTests::setPositionAndStabilise(const SGGeod& g)
{
FGTestApi::setPosition(g);
for (int i=0; i<60; ++i) {
m_gps->update(0.015);
}
}
/////////////////////////////////////////////////////////////////////////////
#if 0
void RNAVProcedureTests::testBasic()
{
setupStandardGPS();
FGPositioned::TypeFilter f{FGPositioned::VOR};
auto bodrumVOR = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("BDR", SGGeod::fromDeg(27.6, 37), &f));
SGGeod p1 = SGGeodesy::direct(bodrumVOR->geod(), 45.0, 5.0 * SG_NM_TO_METER);
FGTestApi::setPositionAndStabilise(p1);
}
#endif
void RNAVProcedureTests::testHeadingToAlt()
{
auto vhhh = FGAirport::findByIdent("VHHH");
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
// we don't have Nasal, but our delegate does the same work
FGTestApi::setUp::populateFPWithNasal(fp, "VHHH", "25R", "EGLL", "27R", "HAZEL");
auto wp = new HeadingToAltitude(fp, "TO_4000", 270);
wp->setAltitude(4000, RESTRICT_ABOVE);
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
// FGTestApi::writeFlightPlanToKML(fp);
auto depRwy = fp->departureRunway();
setupStandardGPS();
setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0));
fp->activate();
m_gpsNode->setStringValue("command", "leg");
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(depRwy->pointOnCenterline(0.0));
pilot->setCourseTrue(depRwy->headingDeg());
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(300.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
// leisurely climb out
pilot->setVerticalFPM(1800);
pilot->setTargetAltitudeFtMSL(8000);
CPPUNIT_ASSERT_EQUAL(std::string{"VHHH-25R"}, std::string{m_gpsNode->getStringValue("wp/wp[0]/ID")});
CPPUNIT_ASSERT_EQUAL(std::string{"TO_4000"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
// fly until we're turned to to heading
ok = FGTestApi::runForTimeWithCheck(20, [pilot] () {
return pilot->isOnHeading(270.);
});
CPPUNIT_ASSERT(ok);
// capture the position now
SGGeod posAtHdgAltStart = globals->get_aircraft_position();
FGTestApi::runForTime(40.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
const double crs = SGGeodesy::courseDeg(posAtHdgAltStart, globals->get_aircraft_position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, crs, 1.0);
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
return (fp->currentIndex() == 2);
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_DOUBLES_EQUAL(4000.0, globals->get_aircraft_position().getElevationFt(), 100.0);
FGTestApi::runForTime(40.0);
}
// ugly version: the heading to hold will be very mis-aligned with
// the course when the leg is sequenced. (more than 90 degrees turn(
void RNAVProcedureTests::testUglyHeadingToAlt()
{
auto vhhh = FGAirport::findByIdent("VHHH");
// FGTestApi::setUp::logPositionToKML("heading_to_alt_ugly");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
// we don't have Nasal, but our delegate does the same work
FGTestApi::setUp::populateFPWithNasal(fp, "VHHH", "07L", "EGLL", "27R", "HAZEL");
auto wp = new HeadingToAltitude(fp, "TO_4000", 210);
wp->setAltitude(4000, RESTRICT_ABOVE);
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
// FGTestApi::writeFlightPlanToKML(fp);
auto depRwy = fp->departureRunway();
setupStandardGPS();
setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0));
fp->activate();
m_gpsNode->setStringValue("command", "leg");
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(depRwy->pointOnCenterline(0.0));
pilot->setCourseTrue(depRwy->headingDeg());
pilot->setSpeedKts(200);
pilot->flyGPSCourse(m_gps);
// check we sequence to the heading-to-alt wp
bool ok = FGTestApi::runForTimeWithCheck(240.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
// leisurely climb out
pilot->setVerticalFPM(1800);
pilot->setTargetAltitudeFtMSL(8000);
CPPUNIT_ASSERT_EQUAL(std::string{"VHHH-07L"}, std::string{m_gpsNode->getStringValue("wp/wp[0]/ID")});
CPPUNIT_ASSERT_EQUAL(std::string{"TO_4000"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
// fly until we're turned to to heading
ok = FGTestApi::runForTimeWithCheck(120, [pilot] () {
return pilot->isOnHeading(210.0);
});
CPPUNIT_ASSERT(ok);
// capture the position now
SGGeod posAtHdgAltStart = globals->get_aircraft_position();
FGTestApi::runForTime(40.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
const double crs = SGGeodesy::courseDeg(posAtHdgAltStart, globals->get_aircraft_position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, crs, 1.0);
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
return (fp->currentIndex() == 2);
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_DOUBLES_EQUAL(4000.0, globals->get_aircraft_position().getElevationFt(), 100.0);
FGTestApi::runForTime(40.0);
}
void RNAVProcedureTests::testEGPH_TLA6C()
{
auto egph = FGAirport::findByIdent("EGPH");
auto sid = egph->findSIDWithIdent("TLA6C");
// procedures not loaded, abandon test
if (!sid)
return;
// FGTestApi::setUp::logPositionToKML("procedure_egph_tla6c");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "EGPH", "24", "EGLL", "27R", "DCS POL DTY");
fp->setSID(sid);
FGRunwayRef departureRunway = fp->departureRunway();
CPPUNIT_ASSERT_EQUAL(std::string{"24"}, fp->legAtIndex(0)->waypoint()->source()->name());
CPPUNIT_ASSERT_EQUAL(std::string{"UW"}, fp->legAtIndex(1)->waypoint()->ident());
auto d242Wpt = fp->legAtIndex(2)->waypoint();
CPPUNIT_ASSERT_EQUAL(std::string{"D242H"}, d242Wpt->ident());
CPPUNIT_ASSERT_EQUAL(true, d242Wpt->flag(WPT_OVERFLIGHT));
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);
CPPUNIT_ASSERT(rm->activate());
setupStandardGPS();
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
m_gpsNode->setStringValue("command", "leg");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(globals->get_aircraft_position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
pilot->setSpeedKts(220);
pilot->flyGPSCourse(m_gps);
FGTestApi::runForTime(20.0);
// check we're somewhere along the runway, on the centerline
// and still on waypoint zero
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
// check what we sequenced to
double elapsed = globals->get_sim_time_sec();
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 2) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
elapsed = globals->get_sim_time_sec();
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 3) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
elapsed = globals->get_sim_time_sec();
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 4) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 5) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, fp->legAtIndex(5)->waypoint()->ident());
CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
}
void RNAVProcedureTests::testLFKC_AJO1R()
{
auto lfkc = FGAirport::findByIdent("LFKC");
auto sid = lfkc->findSIDWithIdent("AJO1R");
// procedures not loaded, abandon test
if (!sid)
return;
// FGTestApi::setUp::logPositionToKML("procedure_LFKC_AJO1R");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "LFKC", "36", "EGLL", "27R", "");
fp->setSID(sid);
CPPUNIT_ASSERT_EQUAL(std::string{"BEBEV"}, fp->legAtIndex(4)->waypoint()->ident());
CPPUNIT_ASSERT_EQUAL(std::string{"AJO"}, fp->legAtIndex(5)->waypoint()->ident());
double d = fp->legAtIndex(5)->distanceAlongRoute();
CPPUNIT_ASSERT_DOUBLES_EQUAL(72, d, 1.0); // ensure the route didn't blow up to 0,0
FGRunwayRef departureRunway = fp->departureRunway();
// FGTestApi::writeFlightPlanToKML(fp);
CPPUNIT_ASSERT(rm->activate());
setupStandardGPS();
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
m_gpsNode->setStringValue("command", "leg");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(globals->get_aircraft_position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
pilot->setSpeedKts(220);
pilot->flyGPSCourse(m_gps);
FGTestApi::runForTime(20.0);
// check we're somewhere along the runway, on the centerline
// and still on waypoint zero
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
}
void RNAVProcedureTests::testTransitionsSID()
{
auto kjfk = FGAirport::findByIdent("kjfk");
auto runway = kjfk->getRunwayByIdent("13L");
auto sid = kjfk->selectSIDByTransition(runway, "CANDR");
// procedures not loaded, abandon test
if (!sid)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "KJFK", "13L", "KCLE", "24R", "");
fp->setSID(sid);
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
auto wp = fp->legAtIndex(6);
CPPUNIT_ASSERT_EQUAL(std::string{"CANDR"}, wp->waypoint()->ident());
CPPUNIT_ASSERT(rm->activate());
}
void RNAVProcedureTests::testTransitionsSTAR()
{
auto kjfk = FGAirport::findByIdent("kjfk");
auto runway = kjfk->getRunwayByIdent("22L");
auto star = kjfk->selectSTARByTransition(runway, "SEY");
// procedures not loaded, abandon test
if (!star)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "KBOS", "22R", "KJFK", "22L", "");
fp->setSTAR(star);
CPPUNIT_ASSERT_EQUAL(9, fp->numLegs());
auto wp = fp->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(std::string{"SEY"}, wp->waypoint()->ident());
CPPUNIT_ASSERT(rm->activate());
}
void RNAVProcedureTests::testLEBL_LARP2F()
{
auto lebl = FGAirport::findByIdent("LEBL");
auto sid = lebl->findSIDWithIdent("LARP1F.25L");
// procedures not loaded, abandon test
if (!sid)
return;
FGTestApi::setUp::logPositionToKML("procedure_LEBL-LARP2F");
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "LEBL", "25L", "LEIB", "06", "");
fp->setSID(sid);
// I don't know if this should pass or not!
// If its a bug, wrap next line in CPPUNIT_ASSERT_ASSERTION_FAIL
CPPUNIT_ASSERT_EQUAL(fp->legAtIndex(1)->waypoint()->position(), SGGeod::fromDeg(0, 0));
CPPUNIT_ASSERT_EQUAL(std::string{"LARPA"}, fp->legAtIndex(8)->waypoint()->ident());
double d = fp->legAtIndex(8)->distanceAlongRoute();
CPPUNIT_ASSERT_DOUBLES_EQUAL(46.6, d, 1.0); // ensure the route didn't blow up
FGTestApi::writeFlightPlanToKML(fp);
CPPUNIT_ASSERT(rm->activate());
FGRunwayRef departureRunway = fp->departureRunway();
setupStandardGPS();
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
m_gpsNode->setStringValue("command", "leg");
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(globals->get_aircraft_position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
pilot->setSpeedKts(220);
pilot->flyGPSCourse(m_gps);
pilot->setTargetAltitudeFtMSL(8000);
pilot->setVerticalFPM(1800);
FGTestApi::runForTime(20.0);
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
if (fp->currentIndex() == 1) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
FGTestApi::runForTime(180.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(199, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
}
// This could probably be in a better place but this allows it to access TestDelegate.
// Also it does relate to procedures as its a bug that only occurs with procedures
void RNAVProcedureTests::testIndexOf()
{
auto egkk = FGAirport::findByIdent("EGKK");
auto sid = egkk->findSIDWithIdent("SAM3P");
// procedures not loaded, abandon test
if (!sid)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
auto fp = FlightPlan::create();
auto testDelegate = new TestFPDelegate;
testDelegate->thePlan = fp;
fp->addDelegate(testDelegate);
rm->setFlightPlan(fp);
FGTestApi::setUp::populateFPWithNasal(fp, "EGKK", "08R", "EGJJ", "27", "LELNA");
fp->setSID(sid);
FGPositioned::TypeFilter f{FGPositioned::VOR};
auto southamptonVOR = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("SAM", SGGeod::fromDeg(-1.25, 51.0), &f));
auto SAM = fp->legAtIndex(6)->waypoint();
CPPUNIT_ASSERT_EQUAL(southamptonVOR->ident(), SAM->ident());
CPPUNIT_ASSERT_EQUAL(6, fp->findWayptIndex(southamptonVOR));
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2019 James Turner
*
* 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/>.
*/
#ifndef _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX
#define _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <memory>
#include <simgear/props/props.hxx>
class SGGeod;
class GPS;
// The flight plan unit tests.
class RNAVProcedureTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(RNAVProcedureTests);
CPPUNIT_TEST(testEGPH_TLA6C);
CPPUNIT_TEST(testHeadingToAlt);
CPPUNIT_TEST(testUglyHeadingToAlt);
CPPUNIT_TEST(testLFKC_AJO1R);
CPPUNIT_TEST(testTransitionsSID);
CPPUNIT_TEST(testTransitionsSTAR);
CPPUNIT_TEST(testLEBL_LARP2F);
CPPUNIT_TEST(testIndexOf);
CPPUNIT_TEST_SUITE_END();
void setPositionAndStabilise(const SGGeod& g);
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
const std::string name = "gps", const int index = 0);
void setupRouteManager();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
//void testBasic();
void testEGPH_TLA6C();
void testHeadingToAlt();
void testUglyHeadingToAlt();
void testLFKC_AJO1R();
void testTransitionsSID();
void testTransitionsSTAR();
void testLEBL_LARP2F();
void testIndexOf();
private:
GPS* m_gps = nullptr;
SGPropertyNode_ptr m_gpsNode;
};
#endif // _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX

View File

@@ -0,0 +1,107 @@
#include "test_transponder.hxx"
#include <cstring>
#include <memory>
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Instrumentation/transponder.hxx>
#include <Main/fg_props.hxx>
#include <Main/locale.hxx>
// Set up function for each test.
void TransponderTests::setUp()
{
FGTestApi::setUp::initTestGlobals("transponder");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void TransponderTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
SGSubsystemRef TransponderTests::setupStandardTransponder(const std::string& name, int index)
{
SGPropertyNode_ptr configNode(new SGPropertyNode);
globals->get_props()->setDoubleValue("systems/electrical/outputs/transponder", 10.0);
configNode->setStringValue("name", name);
configNode->setIntValue("number", index);
auto r = new Transponder(configNode);
r->bind();
r->init();
globals->add_subsystem("transponder", r, SGSubsystemMgr::FDM);
return r;
}
void TransponderTests::testBasic()
{
SGPropertyNode* altitudeSource = fgGetNode("/instrumentation/altimeter", true);
auto t = setupStandardTransponder("transponder", 0);
altitudeSource->setDoubleValue("mode-s-alt-ft", 1234.0);
SGPropertyNode* xpdrNode = fgGetNode("/instrumentation/transponder[0]");
xpdrNode->setIntValue("inputs/knob-mode", 5); // KNOB_ALT
xpdrNode->setIntValue("inputs/mode", 2); // MODE S
xpdrNode->setIntValue("id-code", 4621);
t->update(1.0);
CPPUNIT_ASSERT_EQUAL(4, xpdrNode->getIntValue("inputs/digit[3]"));
CPPUNIT_ASSERT_EQUAL(6, xpdrNode->getIntValue("inputs/digit[2]"));
CPPUNIT_ASSERT_EQUAL(2, xpdrNode->getIntValue("inputs/digit[1]"));
CPPUNIT_ASSERT_EQUAL(1, xpdrNode->getIntValue("inputs/digit[0]"));
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("altitude-valid"));
CPPUNIT_ASSERT_EQUAL(1234, xpdrNode->getIntValue("altitude"));
xpdrNode->setIntValue("inputs/digit[2]", 2);
CPPUNIT_ASSERT_EQUAL(4221, xpdrNode->getIntValue("id-code"));
t->update(1.0);
CPPUNIT_ASSERT_EQUAL(4221, xpdrNode->getIntValue("transmitted-id"));
xpdrNode->setBoolValue("inputs/ident-btn", true);
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("ident"));
xpdrNode->setBoolValue("inputs/ident-btn", false);
// remain on for now
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("ident"));
FGTestApi::runForTime(20.0);
CPPUNIT_ASSERT_EQUAL(false, xpdrNode->getBoolValue("ident"));
xpdrNode->setIntValue("inputs/knob-mode", 1); // KNOB_STANDBY
}
void TransponderTests::testStandby()
{
SGPropertyNode* altitudeSource = fgGetNode("/instrumentation/altimeter", true);
auto t = setupStandardTransponder("transponder", 0);
altitudeSource->setDoubleValue("mode-s-alt-ft", 1234.0);
SGPropertyNode* xpdrNode = fgGetNode("/instrumentation/transponder[0]");
xpdrNode->setIntValue("inputs/knob-mode", 1); // KNOB_STANDBY
xpdrNode->setIntValue("id-code", 4621);
t->update(1.0);
CPPUNIT_ASSERT_EQUAL(4621, xpdrNode->getIntValue("id-code"));
CPPUNIT_ASSERT_EQUAL(-9999, xpdrNode->getIntValue("transmitted-id"));
}

View File

@@ -0,0 +1,55 @@
/*
* 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/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/structure/subsystem_mgr.hxx>
class Transponder;
class SGGeod;
// The flight plan unit tests.
class TransponderTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(TransponderTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testStandby);
CPPUNIT_TEST_SUITE_END();
SGSubsystemRef setupStandardTransponder(const std::string& name, int index);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBasic();
void testStandby();
};

View File

@@ -0,0 +1,16 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_autosaveMigration.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_posinit.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_timeManager.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_autosaveMigration.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_posinit.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_timeManager.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,28 @@
/*
* 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/>.
*/
#include "test_autosaveMigration.hxx"
#include "test_posinit.hxx"
#include "test_timeManager.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutosaveMigrationTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PosInitTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TimeManagerTests, "Unit tests");

View File

@@ -0,0 +1,169 @@
// Written by James Turner, started 2017.
//
// Copyright (C) 2017 James Turner
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "test_autosaveMigration.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <simgear/props/props_io.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_dir.hxx>
#include "Main/globals.hxx"
#include "Main/options.hxx"
#include "Main/fg_props.hxx"
using namespace flightgear;
void writeLegacyAutosave(SGPath userData, int majorVersion, int minorVersion)
{
std::ostringstream os;
os << "autosave_" << majorVersion << "_" << minorVersion << ".xml";
sg_ofstream of(userData / os.str());
{
of << "<?xml version=\"1.0\"?>" \
"<PropertyList>" \
"<sim>" \
"<window-height>42</window-height>" \
"<presets>" \
"<foo>12</foo>" \
"<child><bar>12</bar></child>" \
"</presets>" \
"<presets n=\"1\">" \
"<foo>13</foo>" \
"</presets>" \
"<rendering>" \
"<msaa>10</msaa>" \
"<texture-size>512</texture-size>" \
"<texture-pack>" \
"<foo>abc</foo>" \
"<wibble>abc</wibble>" \
"</texture-pack>" \
"</rendering>" \
"<gui>" \
"<dialog n=\"1\">" \
"<widget>button</widget>" \
"</dialog>" \
"<dialog n=\"2\">" \
"<widget>slider</widget>" \
"</dialog>"\
"</gui>" \
"</sim>" \
"<some-setting>888</some-setting>" \
"<views>" \
"<view>" \
"<new-prop>somevalue</new-prop>" \
"<old-prop>somevalue</old-prop>" \
"</view>" \
"</views>" \
"</PropertyList>";
}
of.close();
}
void writeLegacyAutosave2(SGPath userData, int majorVersion, int minorVersion)
{
std::ostringstream os;
os << "autosave_" << majorVersion << "_" << minorVersion << ".xml";
sg_ofstream of(userData / os.str());
{
of << "<?xml version=\"1.0\"?>" \
"<PropertyList>" \
"<sim>" \
"<bad>1</bad>" \
"</sim>" \
"</views>" \
"</PropertyList>";
}
of.close();
}
// Set up function for each test.
void AutosaveMigrationTests::setUp()
{
FGTestApi::setUp::initTestGlobals("autosaveMigration");
Options::reset();
}
// Clean up after each test.
void AutosaveMigrationTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void AutosaveMigrationTests::testMigration()
{
SGPath testUserDataPath = globals->get_fg_home() / "test_autosave_migrate";
if (!testUserDataPath.exists()) {
SGPath p = testUserDataPath / "foo";
p.create_dir(0755);
}
simgear::Dir homeDir(testUserDataPath);
for (auto path : homeDir.children(simgear::Dir::TYPE_FILE, ".xml")) {
path.remove();
}
writeLegacyAutosave(testUserDataPath, 2016, 1);
const string_list versionParts = simgear::strutils::split(VERSION, ".");
CPPUNIT_ASSERT(versionParts.size() == 3);
const int currentMajor = simgear::strutils::to_int(versionParts[0]);
const int currentMinor = simgear::strutils::to_int(versionParts[1]);
// none of these should not be read
writeLegacyAutosave2(testUserDataPath, 2016, 0);
writeLegacyAutosave2(testUserDataPath, currentMajor, currentMinor + 1);
writeLegacyAutosave2(testUserDataPath, currentMajor+1, currentMinor + 1);
SGPath p = globals->autosaveFilePath(testUserDataPath);
if (p.exists()) {
CPPUNIT_ASSERT(p.remove());
}
// write some blck-list rules to property tree
SGPropertyNode_ptr blacklist = fgGetNode("/sim/autosave-migration/blacklist", true);
blacklist->addChild("path")->setStringValue("/sim[0]/presets[0]/*");
blacklist->addChild("path")->setStringValue("/sim[0]/rendering[0]/texture-");
blacklist->addChild("path")->setStringValue("/views[0]/view[*]/old-prop");
blacklist->addChild("path")->setStringValue("/sim[0]/gui");
// execute method under test
globals->loadUserSettings(testUserDataPath);
CPPUNIT_ASSERT_EQUAL((int)globals->get_props()->getNode("sim")->getChildren("presets").size(), 2);
CPPUNIT_ASSERT_EQUAL((int)globals->get_props()->getNode("sim")->getChildren("gui").size(), 0);
CPPUNIT_ASSERT_EQUAL(globals->get_props()->getIntValue("sim/window-height"), 42);
CPPUNIT_ASSERT_EQUAL(globals->get_props()->getIntValue("sim/presets/foo"), 0);
CPPUNIT_ASSERT_EQUAL(globals->get_props()->getIntValue("sim/presets[1]/foo"), 13);
CPPUNIT_ASSERT_EQUAL(globals->get_props()->getIntValue("some-setting"), 888);
// if this is not zero, one of the bad autosaves was read
CPPUNIT_ASSERT_EQUAL(globals->get_props()->getIntValue("sim/bad"), 0);
}

View File

@@ -0,0 +1,48 @@
/*
* 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/>.
*/
#ifndef _FG_AUTOSAVE_MIGRATION_UNIT_TESTS_HXX
#define _FG_AUTOSAVE_MIGRATION_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The unit tests.
class AutosaveMigrationTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AutosaveMigrationTests);
CPPUNIT_TEST(testMigration);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testMigration();
};
#endif // _FG_AUTOSAVE_MIGRATION_UNIT_TESTS_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
/*
* 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/>.
*/
#ifndef _FG_POSINIT_UNIT_TESTS_HXX
#define _FG_POSINIT_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include "Airports/airport.hxx"
// The unit tests of the FGNasalSys subsystem.
class PosInitTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(PosInitTests);
// Airport-based tests
CPPUNIT_TEST(testAirportAltitudeOffsetStartup);
CPPUNIT_TEST(testAirportAndMetarStartup);
CPPUNIT_TEST(testAirportAndRunwayStartup);
CPPUNIT_TEST(testAirportAndAvailableParkingStartup);
CPPUNIT_TEST(testAirportAndParkingStartup);
CPPUNIT_TEST(testAirportOnlyStartup);
CPPUNIT_TEST(testAirportRunwayOffsetAltitudeStartup);
CPPUNIT_TEST(testAirportRunwayOffsetGlideslopeStartup);
CPPUNIT_TEST(testDefaultStartup);
CPPUNIT_TEST(testRepositionAtParking);
CPPUNIT_TEST(testParkAtOccupied);
CPPUNIT_TEST(testParkInvalid);
CPPUNIT_TEST(testAirportRunwayRepositionAirport);
CPPUNIT_TEST(testParkNoAI);
// Navaid tests
CPPUNIT_TEST(testVOROnlyStartup);
CPPUNIT_TEST(testVOROffsetAltitudeHeadingStartup);
CPPUNIT_TEST(testFixOnlyStartup);
CPPUNIT_TEST(testFixOffsetAltitudeHeadingStartup);
CPPUNIT_TEST(testNDBOnlyStartup);
CPPUNIT_TEST(testNDBOffsetAltitudeHeadingStartup);
CPPUNIT_TEST(testLatLonStartup);
//CPPUNIT_TEST(testLatLonOffsetStartup); This is not yet supported.
// Carrier tests
// We are not able to test the carrier code thoroughly as it depends
// heavily on finalizePosition(), which requires that the carrier model
// itself be loaded into the scenegraph.
CPPUNIT_TEST(testCarrierStartup);
// Reposition tests
CPPUNIT_TEST(testAirportRepositionAirport);
CPPUNIT_TEST(testRepositionAtSameParking);
CPPUNIT_TEST(testRepositionAtOccupied);
CPPUNIT_TEST(testRepositionAtInvalid);
// MP tests
CPPUNIT_TEST(testMPRunwayStart);
CPPUNIT_TEST(testMPRunwayStartNoGroundnet);
CPPUNIT_TEST_SUITE_END();
void simulateStartReposition();
void simulateFinalizePosition();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testAirportAltitudeOffsetStartup();
void testAirportAndMetarStartup();
void testAirportAndAvailableParkingStartup();
void testAirportAndParkingStartup();
void testAirportAndRunwayStartup();
void testAirportOnlyStartup();
void testAirportRunwayOffsetAltitudeStartup();
void testAirportRunwayOffsetGlideslopeStartup();
void testDefaultStartup();
void testParkAtOccupied();
void testParkInvalid();
void testParkNoAI();
// Navaid tests
void testVOROnlyStartup();
void testVOROffsetAltitudeHeadingStartup();
void testFixOnlyStartup();
void testFixOffsetAltitudeHeadingStartup();
void testNDBOnlyStartup();
void testNDBOffsetAltitudeHeadingStartup();
//Lat Lon tests
void testLatLonStartup();
void testLatLonOffsetStartup();
//Carrier tests
void testCarrierStartup();
//Reposition tests
void testAirportRepositionAirport();
void testRepositionAtParking();
void testRepositionAtSameParking();
void testRepositionAtOccupied();
void testRepositionAtInvalid();
void testAirportRunwayRepositionAirport();
// MP tests
void testMPRunwayStart();
void testMPRunwayStartNoGroundnet();
private:
// Helper functions for tests. Return void as they use CPPUNIT_ASSERT
void checkAlt(float value);
void checkHeading(float value);
void checkPosition(SGGeod expectedPos, float delta=1000.0);
void checkClosestAirport(std::string icao);
void checkStringProp(std::string property, std::string expected);
void checkRunway(std::string expected);
void checkOnGround();
void checkInAir();
};
#endif // _FG_POSINIT_UNIT_TESTS_HXX

View File

@@ -0,0 +1,237 @@
// Written by James Turner, started 2021.
//
// Copyright (C) 2021 James Turner
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "test_timeManager.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/timing/sg_time.hxx>
#include "Main/fg_props.hxx"
#include "Main/globals.hxx"
#include <Airports/airport.hxx>
#include <Time/TimeManager.hxx>
using namespace flightgear;
// Set up function for each test.
void TimeManagerTests::setUp()
{
FGTestApi::setUp::initTestGlobals("timeManager");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void TimeManagerTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void TimeManagerTests::testBasic()
{
auto timeManager = globals->get_subsystem<TimeManager>();
// set standard values
fgSetBool("/sim/freeze", false);
fgSetBool("/sim/sceneryloaded", true);
fgSetDouble("/sim/model-hz", 120.0);
timeManager->bind();
timeManager->init();
timeManager->postinit();
double simDt, realDt;
// first run: values are zero
timeManager->computeTimeDeltas(simDt, realDt);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, simDt, 1.0e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, realDt, 1.0e-6);
// manually modify the 'last time' to check delta computation
timeManager->_lastStamp = SGTimeStamp::now() - SGTimeStamp::fromMSec(25);
timeManager->computeTimeDeltas(simDt, realDt);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.025, simDt, 1.0e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.025, realDt, 1.0e-3);
timeManager->update(simDt);
}
void TimeManagerTests::testFreezeUnfreeze()
{
auto timeManager = globals->get_subsystem<TimeManager>();
// set standard values
fgSetBool("/sim/freeze/clock", false);
fgSetBool("/sim/sceneryloaded", true);
fgSetDouble("/sim/model-hz", 120.0);
timeManager->postinit();
double simDt, realDt;
// first run: values are zero
timeManager->computeTimeDeltas(simDt, realDt);
// test hack: force dtRemainder to zero so we aren't affected by the
// system -> stread clock offset in our test assertions. Without this,
// depending on exaclty when thetest suite is run, we would get different
// results
timeManager->_dtRemainder = 0.0;
SGTimeStamp n;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, simDt, 1.0e-6);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, realDt, 1.0e-6);
timeManager->_lastStamp = SGTimeStamp::now() - SGTimeStamp::fromMSec(15);
timeManager->computeTimeDeltas(simDt, realDt);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.008333, simDt, 1.0e-5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.008333, realDt, 1.0e-5);
fgSetBool("/sim/freeze/clock", true);
timeManager->_lastStamp = SGTimeStamp::now() - SGTimeStamp::fromMSec(20);
timeManager->computeTimeDeltas(simDt, realDt);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, simDt, 1.0e-5); // sim time should not advance
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.025, realDt, 1.0e-5);
}
void TimeManagerTests::testTimeZones()
{
auto timeManager = globals->get_subsystem<TimeManager>();
auto vabb = fgFindAirportID("VABB");
FGTestApi::setPositionAndStabilise(vabb->geod());
// set standard values
fgSetBool("/sim/freeze", false);
fgSetBool("/sim/sceneryloaded", true);
fgSetDouble("/sim/model-hz", 120.0);
timeManager->postinit();
// fake Unix time by setting this; it will then
// set the 'current unix time' passed to SGTime
const auto testDate = 314611200L;
fgSetInt("/sim/time/cur-time-override", testDate);
timeManager->update(0.0);
CPPUNIT_ASSERT_EQUAL((time_t)19800, globals->get_time_params()->get_local_offset());
auto gmt = globals->get_time_params()->getGmt();
CPPUNIT_ASSERT_EQUAL(79, gmt->tm_year);
CPPUNIT_ASSERT_EQUAL(11, gmt->tm_mon);
CPPUNIT_ASSERT_EQUAL(21, gmt->tm_mday);
// relocate to somewhere, check the time values update
auto eddf = FGAirport::getByIdent("EDDF");
FGTestApi::setPositionAndStabilise(eddf->geod());
timeManager->reposition();
timeManager->update(0.0);
CPPUNIT_ASSERT_EQUAL((time_t)3600, globals->get_time_params()->get_local_offset());
auto zbaa = FGAirport::getByIdent("ZBAA");
FGTestApi::setPositionAndStabilise(zbaa->geod());
timeManager->reposition();
timeManager->update(0.0);
CPPUNIT_ASSERT_EQUAL((time_t)28800, globals->get_time_params()->get_local_offset());
}
void TimeManagerTests::testETCTimeZones()
{
auto timeManager = globals->get_subsystem<TimeManager>();
auto phto = fgFindAirportID("PHTO");
FGTestApi::setPositionAndStabilise(phto->geod());
timeManager->postinit();
// fake Unix time by setting this; it will then
// set the 'current unix time' passed to SGTime
const auto testDate = 314611200L;
fgSetInt("/sim/time/cur-time-override", testDate);
FGTestApi::setPositionAndStabilise(phto->geod());
timeManager->reposition();
timeManager->update(0.0);
SGPropertyNode_ptr tzNameNode = fgGetNode("/sim/time/local-timezone", true);
CPPUNIT_ASSERT_EQUAL((time_t)-36000, globals->get_time_params()->get_local_offset());
CPPUNIT_ASSERT_EQUAL("Pacific/Honolulu"s, string{tzNameNode->getStringValue()});
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->setSpeedKts(1000);
pilot->setCourseTrue(320.0);
bool ok = FGTestApi::runForTimeWithCheck(600.0, [tzNameNode]() {
const string tz = tzNameNode->getStringValue();
return tz == "Etc/GMT+10"s;
});
CPPUNIT_ASSERT(ok);
ok = FGTestApi::runForTimeWithCheck(600.0, [tzNameNode]() {
const string tz = tzNameNode->getStringValue();
return tz == "Pacific/Honolulu"s;
});
CPPUNIT_ASSERT(ok);
}
void TimeManagerTests::testSpecifyTimeOffset()
{
// disabled for now since this code depends on epehmeris as well
// to define sun position
return;
auto timeManager = globals->get_subsystem<TimeManager>();
// set standard values
fgSetBool("/sim/freeze", false);
fgSetBool("/sim/sceneryloaded", true);
fgSetDouble("/sim/model-hz", 120.0);
timeManager->postinit();
const auto testDate = 314611200L;
fgSetInt("/sim/time/cur-time-override", testDate);
auto uudd = fgFindAirportID("UUDD");
FGTestApi::setPositionAndStabilise(uudd->geod());
timeManager->setTimeOffset("dawn", 0);
timeManager->update(0.0);
auto localTime = globals->get_time_params()->get_cur_time();
CPPUNIT_ASSERT_EQUAL((time_t) 0, localTime);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 James Turner <james@flightgear.org>
*
* 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 unit tests.
class TimeManagerTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(TimeManagerTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testTimeZones);
CPPUNIT_TEST(testFreezeUnfreeze);
CPPUNIT_TEST(testSpecifyTimeOffset);
CPPUNIT_TEST(testETCTimeZones);
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 testTimeZones();
void testFreezeUnfreeze();
void testSpecifyTimeOffset();
void testETCTimeZones();
};

View File

@@ -0,0 +1,19 @@
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
PARENT_SCOPE
)
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

@@ -0,0 +1,31 @@
/*
* 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/>.
*/
#include "test_flightplan.hxx"
#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

@@ -0,0 +1,73 @@
#include "test_aircraftPerformance.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <simgear/misc/strutils.hxx>
#include <Main/fg_props.hxx>
#include <Aircraft/AircraftPerformance.hxx>
using namespace flightgear;
// Set up function for each test.
void AircraftPerformanceTests::setUp()
{
FGTestApi::setUp::initTestGlobals("aircraft-perf");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void AircraftPerformanceTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void AircraftPerformanceTests::testBasic()
{
fgSetString("/aircraft/performance/icao-category", "C");
AircraftPerformance ap;
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(1000), 152, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(8000), 224, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(20000), 491, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(35000), 461, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(38000), 458, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.groundSpeedForAltitudeKnots(40000), 458, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.timeBetween(3000, 6000), 100, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.distanceNmBetween(3000, 6000), 5.430, 1e-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.timeBetween(36000, 34000), 100, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.distanceNmBetween(36000, 34000), 12.805, 1e-1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.timeBetween(15000, 20000), 300, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.distanceNmBetween(15000, 18000), 14.270, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.timeBetween(2000, 25000), 1191.6, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.distanceNmBetween(2000, 25000), 123.06, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.timeBetween(36000, 3000), 1666.6, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(ap.distanceNmBetween(36000, 3000), 162.02, 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(251.5, ap.timeToCruise(32.0, 350000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(503.0, ap.timeToCruise(64.0, 380000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(1553.7, ap.turnRadiusMForAltitude(4000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3322.4, ap.turnRadiusMForAltitude(16000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(4602.6, ap.turnRadiusMForAltitude(30000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(4475.5, ap.turnRadiusMForAltitude(38000), 1);
}
void AircraftPerformanceTests::testAltitudeGradient()
{
fgSetString("/aircraft/performance/icao-category", "E");
AircraftPerformance ap;
CPPUNIT_ASSERT_DOUBLES_EQUAL(8332, ap.computePreviousAltitude(10000, 6000), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3260, ap.computeNextAltitude(4000, 2000), 1);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2018 James Turner
*
* 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/>.
*/
#ifndef _FG_AIRCRAFT_PERFORMANCE_UNIT_TESTS_HXX
#define _FG_AIRCRAFT_PERFORMANCE_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
class AircraftPerformanceTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(AircraftPerformanceTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testAltitudeGradient);
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 testAltitudeGradient();
};
#endif // AircraftPerformanceTests

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*
* 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/>.
*/
#ifndef FG_FLIGHTPLAN_UNIT_TESTS_HXX
#define FG_FLIGHTPLAN_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The flight plan unit tests.
class FlightplanTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(FlightplanTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testRoutePathBasic);
CPPUNIT_TEST(testRoutePathSkipped);
CPPUNIT_TEST(testRoutePathTrivialFlightPlan);
CPPUNIT_TEST(testBasicAirways);
CPPUNIT_TEST(testAirwayNetworkRoute);
CPPUNIT_TEST(testBug1814);
CPPUNIT_TEST(testRoutPathWpt0Midflight);
CPPUNIT_TEST(testRoutePathVec);
CPPUNIT_TEST(testRoutePathFinalLegVQPR15);
CPPUNIT_TEST(testLoadSaveMachRestriction);
CPPUNIT_TEST(testOnlyDiscontinuityRoute);
CPPUNIT_TEST(testBasicDiscontinuity);
CPPUNIT_TEST(testLeadingWPDynamic);
CPPUNIT_TEST(testRadialIntercept);
CPPUNIT_TEST(loadFGFPWithoutDepartureArrival);
CPPUNIT_TEST(loadFGFPWithEmbeddedProcedures);
CPPUNIT_TEST(loadFGFPWithOldProcedures);
CPPUNIT_TEST(loadFGFPWithProcedureIdents);
CPPUNIT_TEST(testCloningBasic);
CPPUNIT_TEST(testCloningFGFP);
CPPUNIT_TEST(testCloningProcedures);
CPPUNIT_TEST(testBug2616);
CPPUNIT_TEST(testRoute);
CPPUNIT_TEST(testViaInsertIntoFP);
CPPUNIT_TEST(testViaInsertIntoRoute);
CPPUNIT_TEST(loadFGFPAsRoute);
// CPPUNIT_TEST(testParseICAORoute);
// CPPUNIT_TEST(testParseICANLowLevelRoute);
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 testRoutePathBasic();
void testRoutePathSkipped();
void testRoutePathTrivialFlightPlan();
void testBasicAirways();
void testAirwayNetworkRoute();
void testParseICAORoute();
void testParseICANLowLevelRoute();
void testBug1814();
void testRoutPathWpt0Midflight();
void testRoutePathVec();
void testRoutePathFinalLegVQPR15();
void testLoadSaveMachRestriction();
void testBasicDiscontinuity();
void testOnlyDiscontinuityRoute();
void testLeadingWPDynamic();
void testRadialIntercept();
void loadFGFPWithoutDepartureArrival();
void loadFGFPWithEmbeddedProcedures();
void loadFGFPWithOldProcedures();
void loadFGFPWithProcedureIdents();
void testCloningBasic();
void testCloningFGFP();
void testCloningProcedures();
void testBug2616();
void testRoute();
void testViaInsertIntoFP();
void testViaInsertIntoRoute();
void loadFGFPAsRoute();
};
#endif // FG_FLIGHTPLAN_UNIT_TESTS_HXX

View File

@@ -0,0 +1,476 @@
#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;
static bool static_haveProcedures = false;
// Set up function for each test.
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>(SGSubsystemMgr::GENERAL);
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 = FlightPlan::create();
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());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
fp.clearAll();
unitTest.assert_equal(fp.getPlanSize(), 0);
unitTest.assert_equal(fp.current, -1);
unitTest.assert_equal(fp.departure, nil);
unitTest.assert_equal(fp.sid, nil);
unitTest.assert_equal(fp.cruiseSpeedKt, 0);
)");
CPPUNIT_ASSERT(ok);
}
void FPNasalTests::testRestrictions()
{
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());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setAltitude(6000, 'above');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_ABOVE, fp1->legAtIndex(3)->altitudeRestriction());
CPPUNIT_ASSERT_EQUAL(6000, fp1->legAtIndex(3)->altitudeFt());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setAltitude(6000, 'below');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_BELOW, fp1->legAtIndex(3)->altitudeRestriction());
CPPUNIT_ASSERT_EQUAL(6000, fp1->legAtIndex(3)->altitudeFt());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setAltitude(6000, 'delete');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_DELETE, fp1->legAtIndex(3)->altitudeRestriction());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setSpeed(250, 'at');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_AT, fp1->legAtIndex(3)->speedRestriction());
CPPUNIT_ASSERT_EQUAL(250, fp1->legAtIndex(3)->speedKts());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setSpeed(250, 'above');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_ABOVE, fp1->legAtIndex(3)->speedRestriction());
CPPUNIT_ASSERT_EQUAL(250, fp1->legAtIndex(3)->speedKts());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setSpeed(250, 'below');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_BELOW, fp1->legAtIndex(3)->speedRestriction());
CPPUNIT_ASSERT_EQUAL(250, fp1->legAtIndex(3)->speedKts());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setSpeed(250, 'delete');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_DELETE, fp1->legAtIndex(3)->speedRestriction());
}
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);
}
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");
unitTest.assert_equal(trans.tp_type, "transition", "Procedure type incorrect");
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(FlightPlan::create());
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"});
}
void FPNasalTests::testApproachTransitionAPI()
{
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.08L");
unitTest.assert(star != nil, "STAR not found");
unitTest.assert_equal(star.id, "RIXE3A.08L", "Incorrect STAR loaded");
fp.star = star;
fp.destination_runway = fp.destination.runway('08L');
var approach = fp.destination.getApproach("ILS08L");
unitTest.assert(approach != nil, "No approach loaded");
var trans = approach.transition('LUL1C');
unitTest.assert(trans != nil, "approach transition not found");
unitTest.assert_equal(trans.id, "LUL1C", "Incorrect approach transition loaded");
unitTest.assert_equal(trans.tp_type, "transition", "Procedure type incorrect");
fp.approach = trans;
unitTest.assert_equal(fp.approach.id, "ILS08L", "Incorrect approach returned");
unitTest.assert_equal(fp.approach_trans.id, "LUL1C", "Incorrect transition returned");
unitTest.assert_equal(fp.approach_trans.tp_type, "transition", "Procedure type incorrect");
)");
CPPUNIT_ASSERT(ok);
auto fp = rm->flightPlan();
CPPUNIT_ASSERT(fp->approach());
CPPUNIT_ASSERT_EQUAL(string{"LUL1C"}, fp->approachTransition()->ident());
CPPUNIT_ASSERT_EQUAL(string{"ILS08L"}, fp->approach()->ident());
}
void FPNasalTests::testApproachTransitionAPIWithCloning()
{
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("EHAM");
fp.star = fp.destination.getStar("REDF1A");
fp.destination_runway = fp.destination.runway('06');
var approach = fp.destination.getApproach("ILS06");
unitTest.assert(approach != nil, "No approach loaded");
var trans = approach.transition('SUG2A');
unitTest.assert(trans != nil, "approach transition not found");
fp.approach = trans;
unitTest.assert_equal(fp.approach.id, "ILS06", "Incorrect approach returned");
unitTest.assert_equal(fp.approach_trans.id, "SUG2A", "Incorrect transition returned");
unitTest.assert_equal(fp.approach_trans.tp_type, "transition", "Procedure type incorrect");
)");
CPPUNIT_ASSERT(ok);
auto fp = rm->flightPlan();
CPPUNIT_ASSERT(fp->approach());
CPPUNIT_ASSERT_EQUAL(string{"SUG2A"}, fp->approachTransition()->ident());
CPPUNIT_ASSERT_EQUAL(string{"ILS06"}, fp->approach()->ident());
auto fp2 = fp->clone("testplan2");
CPPUNIT_ASSERT_EQUAL(string{"ILS06"}, fp2->approach()->ident());
CPPUNIT_ASSERT_EQUAL(string{"SUG2A"}, fp2->approachTransition()->ident());
}
void FPNasalTests::testAirwaysAPI()
{
bool ok = FGTestApi::executeNasal(R"(
var airwayIdent = "L620";
var airwayStore = airway(airwayIdent, "low");
unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found");
unitTest.assert(airwayStore.id == airwayIdent, "Incorrect airway found");
unitTest.assert_equal(airwayStore.level, 'low', "Incorrect airway found");
unitTest.assert_equal(airwayStore.level_code, Airway.LOW, "Incorrect airway found");
airwayIdent = "UL620";
var cln = findNavaidsByID("CLN", "VOR")[0];
airwayStore = airway(airwayIdent, cln);
unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found");
unitTest.assert(airwayStore.id == airwayIdent, "Incorrect airway found");
unitTest.assert_equal(airwayStore.level_code, Airway.HIGH, "Incorrect airway found");
airwayIdent = "J547";
airwayStore = airway(airwayIdent);
unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found");
unitTest.assert(airwayStore.id == airwayIdent, "Incorrect airway found");
)");
CPPUNIT_ASSERT(ok);
ok = FGTestApi::executeNasal(R"(
var airwayIdent = "L620";
var airwayStore = airway(airwayIdent, Airway.LOW);
var cln = findNavaidsByID("CLN", "VOR")[0];
var v1 = createViaTo(airwayIdent, "CLN");
unitTest.assert_equal(v1.wp_type, "via");
unitTest.assert_equal(v1.airway.id, 'L620');
unitTest.assert_equal(v1.airway.level_code, Airway.LOW);
var v2 = createViaTo(airwayStore, "TULIP");
unitTest.assert_equal(v2.wp_type, "via");
unitTest.assert_equal(v2.airway.id, airwayIdent);
var v3 = createViaFromTo(cln, "L620", 'low', "TULIP");
unitTest.assert_equal(v3.airway.id, 'L620');
var v4 = createViaFromTo(cln, "L620", "REDFA");
unitTest.assert_equal(v4.airway.level_code, Airway.LOW);
# test direct API (no Vias)
var wps = airwayStore.viaWaypoints(cln, "TULIP");
unitTest.assert_equal(size(wps), 3);
unitTest.assert_equal(wps[1].wp_ident, 'REDFA');
unitTest.assert_equal(wps[1].airway.ident, 'L620');
)");
CPPUNIT_ASSERT(ok);
}
void FPNasalTests::testTotalDistanceAPI()
{
auto rm = globals->get_subsystem<FGRouteMgr>();
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan();
fp.departure = airportinfo("BIKF");
fp.destination = airportinfo("EGLL");
unitTest.assert_doubles_equal(1025.9, fp.totalDistanceNm, 0.1, "Distance assertion failed");
)");
CPPUNIT_ASSERT(ok);
auto fp = rm->flightPlan();
CPPUNIT_ASSERT_DOUBLES_EQUAL(fp->totalDistanceNm(), 1025.9, 0.1);
}

View File

@@ -0,0 +1,61 @@
/*
* 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(testRestrictions);
CPPUNIT_TEST(testSegfaultWaypointGhost);
CPPUNIT_TEST(testSIDTransitionAPI);
CPPUNIT_TEST(testSTARTransitionAPI);
CPPUNIT_TEST(testApproachTransitionAPI);
CPPUNIT_TEST(testApproachTransitionAPIWithCloning);
CPPUNIT_TEST(testAirwaysAPI);
CPPUNIT_TEST(testTotalDistanceAPI);
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 testRestrictions();
void testSegfaultWaypointGhost();
void testSIDTransitionAPI();
void testSTARTransitionAPI();
void testApproachTransitionAPI();
void testApproachTransitionAPIWithCloning();
void testAirwaysAPI();
void testTotalDistanceAPI();
};

View File

@@ -0,0 +1,36 @@
#include "test_navaids2.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx>
// Set up function for each test.
void NavaidsTests::setUp()
{
FGTestApi::setUp::initTestGlobals("navaids2");
FGTestApi::setUp::initNavDataCache();
}
// Clean up after each test.
void NavaidsTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void NavaidsTests::testBasic()
{
SGGeod egccPos = SGGeod::fromDeg(-2.27, 53.35);
FGNavRecordRef tla = FGNavList::findByFreq(115.7, egccPos);
CPPUNIT_ASSERT_EQUAL(strcmp(tla->get_ident(), "TNT"), 0);
CPPUNIT_ASSERT(tla->ident() == "TNT");
CPPUNIT_ASSERT(tla->name() == "TRENT VOR-DME");
CPPUNIT_ASSERT_EQUAL(tla->get_freq(), 11570);
CPPUNIT_ASSERT_EQUAL(tla->get_range(), 130);
}

View File

@@ -0,0 +1,48 @@
/*
* 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/>.
*/
#ifndef _FG_NAVAIDS_UNIT_TESTS_HXX
#define _FG_NAVAIDS_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The flight plan unit tests.
class NavaidsTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(NavaidsTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBasic();
};
#endif // _FG_NAVAIDS_UNIT_TESTS_HXX

View File

@@ -0,0 +1,989 @@
#include "config.h"
#include "test_routeManager.hxx"
#include <memory>
#include <cstring>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/structure/commands.hxx>
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include "test_suite/FGTestApi/TestPilot.hxx"
#include <Navaids/FlightPlan.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx>
#include <Navaids/routePath.hxx>
// we need a default GPS instrument, hard to test seperately for now
#include <Instrumentation/gps.hxx>
#include <Autopilot/route_mgr.hxx>
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)
{
FlightPlanRef f = FlightPlan::create();
FGTestApi::setUp::populateFPWithNasal(f, depICAO, depRunway, destICAO, destRunway, waypoints);
return f;
}
// Set up function for each test.
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>(SGSubsystemMgr::FDM);
// setup the default GPS, which is needed for waypoint
// sequencing to work
SGPropertyNode_ptr configNode(new SGPropertyNode);
configNode->setStringValue("name", "gps");
configNode->setIntValue("number", 0);
GPS* gps(new GPS(configNode, true /* default mode */));
m_gps = gps;
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation", true)->getChild("gps", 0, true);
// node->setBoolValue("serviceable", true);
// globals->get_props()->setDoubleValue("systems/electrical/outputs/gps", 6.0);
globals->add_subsystem("gps", gps, SGSubsystemMgr::POST_FDM);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
FGTestApi::setUp::initStandardNasal();
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void RouteManagerTests::tearDown()
{
m_gps = nullptr;
FGTestApi::tearDown::shutdownTestGlobals();
}
void RouteManagerTests::setPositionAndStabilise(const SGGeod& g)
{
FGTestApi::setPosition(g);
for (int i=0; i<60; ++i) {
globals->get_subsystem_mgr()->update(0.02);
}
}
void RouteManagerTests::testBasic()
{
//FGTestApi::setUp::logPositionToKML("rm_basic");
FlightPlanRef fp1 = makeTestFP("EGLC", "27", "EHAM", "06",
"CLN IDESI RINIS VALKO RIVER RTM EKROS");
fp1->setIdent("testplan");
fp1->setCruiseFlightLevel(360);
CPPUNIT_ASSERT_EQUAL("RIVER"s, fp1->legAtIndex(5)->waypoint()->ident());
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true);
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "obs");
rm->activate();
CPPUNIT_ASSERT(fp1->isActive());
// Nasal deleagte should have placed GPS into leg mode
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "leg");
CPPUNIT_ASSERT(rmNode->getStringValue("departure/airport") == "EGLC");
CPPUNIT_ASSERT(rmNode->getStringValue("departure/runway") == "27");
CPPUNIT_ASSERT(rmNode->getStringValue("departure/sid") == "");
CPPUNIT_ASSERT(rmNode->getStringValue("departure/name") == "London City");
CPPUNIT_ASSERT(rmNode->getStringValue("destination/airport") == "EHAM");
CPPUNIT_ASSERT(rmNode->getStringValue("destination/runway") == "06");
CPPUNIT_ASSERT_EQUAL(360, rmNode->getIntValue("cruise/flight-level"));
CPPUNIT_ASSERT_EQUAL(false, rmNode->getBoolValue("airborne"));
CPPUNIT_ASSERT_EQUAL(0, rmNode->getIntValue("current-wp"));
auto wp0Node = rmNode->getNode("wp");
CPPUNIT_ASSERT(wp0Node->getStringValue("id") == "EGLC-27");
auto wp1Node = rmNode->getNode("wp[1]");
CPPUNIT_ASSERT(wp1Node->getStringValue("id") == "CLN");
FGPositioned::TypeFilter f{FGPositioned::VOR};
auto clactonVOR = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("CLN", SGGeod::fromDeg(0.0, 51.0), &f));
// verify hold entry course
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
FGTestApi::setPosition(fp1->departureRunway()->geod());
pilot->resetAtPosition(fp1->departureRunway()->geod());
pilot->setSpeedKts(220);
pilot->setCourseTrue(fp1->departureRunway()->headingDeg());
pilot->setTargetAltitudeFtMSL(10000);
bool ok = FGTestApi::runForTimeWithCheck(30.0, [rmNode] () {
if (rmNode->getIntValue("current-wp") == 1) return true;
return false;
});
CPPUNIT_ASSERT(ok);
// continue outbound for some time
pilot->setSpeedKts(250);
FGTestApi::runForTime(30.0);
// turn towards Clacton VOR
pilot->flyDirectTo(clactonVOR->geod());
pilot->setTargetAltitudeFtMSL(30000);
pilot->setSpeedKts(280);
ok = FGTestApi::runForTimeWithCheck(6000.0, [rmNode] () {
if (rmNode->getIntValue("current-wp") == 2) return true;
return false;
});
CPPUNIT_ASSERT(ok);
// short straight leg for testing
pilot->flyHeading(90.0);
pilot->setSpeedKts(330);
FGTestApi::runForTime(30.0);
// let's engage LNAV mode :)
pilot->flyGPSCourse(m_gps);
ok = FGTestApi::runForTimeWithCheck(6000.0, [rmNode] () {
if (rmNode->getIntValue("current-wp") == 5) return true;
return false;
});
CPPUNIT_ASSERT(ok);
// check where we are - should be heading to RIVER from VALKO
CPPUNIT_ASSERT_EQUAL(5, rmNode->getIntValue("current-wp"));
CPPUNIT_ASSERT(wp0Node->getStringValue("id") == "RIVER");
CPPUNIT_ASSERT(wp1Node->getStringValue("id") == "RTM");
// slightly rapid descent
pilot->setTargetAltitudeFtMSL(3000);
ok = FGTestApi::runForTimeWithCheck(6000.0, [rmNode] () {
if (rmNode->getIntValue("current-wp") == 7) return true;
return false;
});
CPPUNIT_ASSERT(ok);
// run until the GPS reverts to OBS mode at the end of the flight plan
ok = FGTestApi::runForTimeWithCheck(6000.0, [gpsNode] () {
if (gpsNode->getStringValue("mode") == "obs") return true;
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(-1, fp1->currentIndex());
CPPUNIT_ASSERT(!fp1->isActive());
CPPUNIT_ASSERT_EQUAL(-1, rmNode->getIntValue("current-wp"));
CPPUNIT_ASSERT_EQUAL(false, rmNode->getBoolValue("active"));
}
void RouteManagerTests::testDefaultSID()
{
FlightPlanRef fp1 = makeTestFP("EGLC", "27", "EHAM", "24",
"CLN IDRID VALKO");
fp1->setIdent("testplan");
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
rmNode->setStringValue("departure/sid", "DEFAULT");
// let's see what we got :)
rm->activate();
CPPUNIT_ASSERT(fp1->isActive());
}
void RouteManagerTests::testDirectToLegOnFlightplanAndResume()
{
// FGTestApi::setUp::logPositionToKML("rm_dto_resume_leg");
// this is very similar to the identiucally name dtest in GPSTests, but relies on the Nasal
// route manager delegate to perform the same task
FlightPlanRef fp1 = makeTestFP("EBBR", "07L", "EGGD", "27",
"NIK COA DVR TAWNY WOD");
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
// FGTestApi::writeFlightPlanToKML(fp1);
auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true);
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "obs");
rm->activate();
CPPUNIT_ASSERT(fp1->isActive());
FGTestApi::setPosition(fp1->departureRunway()->pointOnCenterline(0.0));
FGTestApi::runForTime(10.0); // let the GPS stabilize
CPPUNIT_ASSERT_EQUAL(std::string{"leg"}, gpsNode->getStringValue("mode"));
CPPUNIT_ASSERT_EQUAL(std::string{"EBBR-07L"}, gpsNode->getStringValue("wp/wp[1]/ID"));
CPPUNIT_ASSERT_EQUAL(0, rmNode->getIntValue("current-wp"));
auto wp0Node = rmNode->getNode("wp");
CPPUNIT_ASSERT(wp0Node->getStringValue("id") == "EBBR-07L");
auto wp1Node = rmNode->getNode("wp[1]");
CPPUNIT_ASSERT(wp1Node->getStringValue("id") == "NIK");
// initiate a direct to
SGGeod p2 = fp1->departureRunway()->pointOnCenterline(5.0* SG_NM_TO_METER);
FGTestApi::setPosition(p2);
auto doverVOR = fp1->legAtIndex(3)->waypoint()->source();
double distanceToDover = SGGeodesy::distanceNm(p2, doverVOR->geod());
double bearingToDover = SGGeodesy::courseDeg(p2, doverVOR->geod());
CPPUNIT_ASSERT_EQUAL(std::string{"DVR"}, doverVOR->ident());
gpsNode->setStringValue("scratch/ident", "DVR");
gpsNode->setDoubleValue("scratch/longitude-deg", doverVOR->geod().getLongitudeDeg());
gpsNode->setDoubleValue("scratch/latitude-deg", doverVOR->geod().getLatitudeDeg());
gpsNode->setStringValue("command", "direct");
CPPUNIT_ASSERT_EQUAL(std::string{"dto"}, gpsNode->getStringValue("mode"));
// check that upon reaching DOVER, we sequence to TAWNY and resume leg mode
// this is handled by the default delegate in Nasal
SGGeod posNearDover = SGGeodesy::direct(p2, bearingToDover, (distanceToDover - 8.0) * SG_NM_TO_METER);
FGTestApi::setPosition(posNearDover);
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(posNearDover);
pilot->setSpeedKts(250);
pilot->flyGPSCourse(m_gps);
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp1] () {
if (fp1->currentIndex() == 4) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(std::string{"leg"}, gpsNode->getStringValue("mode"));
}
void RouteManagerTests::testSequenceDiscontinuityAndResume()
{
// FGTestApi::setUp::logPositionToKML("rm_seq_discon_resume_leg");
FlightPlanRef fp1 = makeTestFP("LIRF", "16R", "LEBL", "07L",
"GITRI BALEN MUREN TOSNU");
fp1->insertWayptAtIndex(new Discontinuity(fp1), 3);
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true);
// auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
auto balenLeg = fp1->legAtIndex(2);
auto pos = fp1->pointAlongRoute(2, -8.0); // 8nm before BALEN
FGTestApi::setPosition(pos);
FGTestApi::runForTime(10.0); // let the GPS stabilize
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "obs");
rm->activate();
CPPUNIT_ASSERT(fp1->isActive());
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "leg");
fp1->setCurrentIndex(2);
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(pos);
pilot->setCourseTrue(270);
pilot->setSpeedKts(250);
pilot->flyGPSCourse(m_gps);
bool ok = FGTestApi::runForTimeWithCheck(180.0, [gpsNode] () {
return (gpsNode->getStringValue("mode") == "obs");
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(2, fp1->currentIndex()); // shouldn't sequence
FGTestApi::runForTime(30.0);
// 2nm before MUREN : this should be on the GC course from BALEN
auto pos2 = fp1->pointAlongRoute(4, -6.0);
FGTestApi::setPosition(pos2);
FGTestApi::runForTime(2.0); // let the GPS stabilize
// initiate a direct-to the next real WP
const auto murenPos = fp1->legAtIndex(4)->waypoint()->position();
gpsNode->setStringValue("scratch/ident", "MUREN");
gpsNode->setDoubleValue("scratch/longitude-deg", murenPos.getLongitudeDeg());
gpsNode->setDoubleValue("scratch/latitude-deg", murenPos.getLatitudeDeg());
gpsNode->setStringValue("command", "direct");
CPPUNIT_ASSERT_EQUAL(std::string{"dto"}, gpsNode->getStringValue("mode"));
pilot->resetAtPosition(pos2);
pilot->flyGPSCourse(m_gps);
ok = FGTestApi::runForTimeWithCheck(600.0, [fp1] () {
if (fp1->currentIndex() == 5) {
return true;
}
return false;
});
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(std::string{"leg"}, gpsNode->getStringValue("mode"));
CPPUNIT_ASSERT_EQUAL(5, fp1->currentIndex());
FGTestApi::runForTime(30.0);
}
void RouteManagerTests::testDefaultApproach()
{
}
void RouteManagerTests::testHiddenWaypoints()
{
FlightPlanRef fp1 = makeTestFP("NZCH", "02", "NZAA", "05L",
"ALADA NS WB WN MAMOD KAPTI OH");
fp1->setIdent("testplan");
fp1->setCruiseFlightLevel(360);
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true);
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "obs");
// FIXME: use real Nasal test macros soon
auto testNode = globals->get_props()->getNode("test-data", true);
fp1->legAtIndex(3)->waypoint()->setFlag(WPT_HIDDEN);
// ensure no visual path is generated for hidden waypoints
RoutePath path(fp1);
// no path should be generated between 2 and 3
CPPUNIT_ASSERT(path.pathForIndex(3).empty());
// no path should be generated between 3 and 4
CPPUNIT_ASSERT(path.pathForIndex(4).empty());
CPPUNIT_ASSERT(!path.pathForIndex(5).empty());
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
setprop("/test-data/a", fp.numRemainingWaypoints());
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(fp1->numLegs(), testNode->getIntValue("a"));
rm->activate();
fp1->setCurrentIndex(2);
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
setprop("/test-data/a", fp.numRemainingWaypoints());
setprop("/test-data/b", fp.currentWP(2).id);
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(7, testNode->getIntValue("a"));
CPPUNIT_ASSERT_EQUAL(std::string{"WN"}, testNode->getStringValue("b"));
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
setprop("/test-data/c", fp.currentWP(-1).id);
# ensure invalid offset returns nil
setprop("/test-data/d", fp.currentWP(-100) == nil);
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(std::string{"ALADA"}, testNode->getStringValue("c"));
CPPUNIT_ASSERT_EQUAL(true, testNode->getBoolValue("d"));
}
void RouteManagerTests::testHoldFromNasal()
{
// FGTestApi::setUp::logPositionToKML("rm_hold_from_nasal");
// test that Nasal can set a hold-count (implicitly converting a leg
// to a Hold waypt), configure the hold radial, and exit the hold
FlightPlanRef fp1 = makeTestFP("NZCH", "02", "NZAA", "05L",
"ALADA NS WB WN MAMOD KAPTI OH");
fp1->setIdent("testplan");
fp1->setCruiseFlightLevel(360);
auto rm = globals->get_subsystem<FGRouteMgr>();
rm->setFlightPlan(fp1);
// FGTestApi::writeFlightPlanToKML(fp1);
auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true);
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "obs");
rm->activate();
CPPUNIT_ASSERT(fp1->isActive());
CPPUNIT_ASSERT(gpsNode->getStringValue("mode") == "leg");
SGGeod posEnrouteToWB = fp1->pointAlongRoute(3, -10.0);
FGTestApi::setPositionAndStabilise(posEnrouteToWB);
// sequence everything to the correct wp
fp1->setCurrentIndex(3);
// setup some hold data from Nasal. To make it more challenging,
// do it once the wp is already active
bool ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.hold_count = 4;
leg.hold_heading_radial_deg = 310;
)");
CPPUNIT_ASSERT(ok);
// check the value updated in the leg
CPPUNIT_ASSERT_EQUAL(4, fp1->legAtIndex(3)->holdCount());
// check we converted to a hold
auto wp = fp1->legAtIndex(3)->waypoint();
auto holdWpt = static_cast<flightgear::Hold*>(wp);
CPPUNIT_ASSERT_EQUAL(wp->type(), std::string{"hold"});
CPPUNIT_ASSERT_DOUBLES_EQUAL(310.0, holdWpt->headingRadialDeg(), 0.5);
// establish the test pilot at this position too
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
pilot->resetAtPosition(posEnrouteToWB);
pilot->setSpeedKts(250);
pilot->flyGPSCourse(m_gps);
pilot->setCourseTrue(gpsNode->getDoubleValue("wp/leg-true-course-deg"));
// run for a bit to stabilize everything
FGTestApi::runForTime(5.0);
// check we upgraded to a hold controller internally, and are flying to it :)
auto statusNode = gpsNode->getNode("rnav-controller-status");
CPPUNIT_ASSERT_EQUAL(std::string{"leg-to-hold"}, statusNode->getStringValue());
// check we're on the leg
CPPUNIT_ASSERT_EQUAL(std::string{"NELSON VOR-DME"}, fp1->legAtIndex(2)->waypoint()->source()->name());
auto wbPos = fp1->legAtIndex(3)->waypoint()->position();
auto nsPos = fp1->legAtIndex(2)->waypoint()->position();
const double crsToWB = SGGeodesy::courseDeg(globals->get_aircraft_position(), wbPos);
const double crsNSWB = SGGeodesy::courseDeg(nsPos,wbPos);
CPPUNIT_ASSERT_DOUBLES_EQUAL(crsToWB, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(crsNSWB, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
// fly into the hold, should be teardrop entry
ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () {
if (statusNode->getStringValue() == "entry-teardrop") return true;
return false;
});
CPPUNIT_ASSERT(ok);
ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () {
if (statusNode->getStringValue() == "hold-inbound") return true;
return false;
});
ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () {
if (statusNode->getStringValue() == "hold-outbound") return true;
return false;
});
CPPUNIT_ASSERT(ok);
// half way through the outbound turn
FGTestApi::runForTime(30.0);
ok = FGTestApi::executeNasal(R"(
setprop("/instrumentation/gps/command", "exit-hold");
)");
CPPUNIT_ASSERT(ok);
// no change yet
CPPUNIT_ASSERT_EQUAL(std::string{"hold-outbound"}, statusNode->getStringValue());
// then we fly inbound
ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () {
if (statusNode->getStringValue() == "hold-inbound") return true;
return false;
});
CPPUNIT_ASSERT(ok);
// and then we exit
ok = FGTestApi::runForTimeWithCheck(600.0, [fp1] () {
if (fp1->currentIndex() == 4) return true;
return false;
});
CPPUNIT_ASSERT(ok);
// get back on course
FGTestApi::runForTime(60.0);
}
// check that when loading a GPX, airport waypoints are created
// by the default delegate
// https://sourceforge.net/p/flightgear/codetickets/2227/
void RouteManagerTests::loadGPX()
{
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
SGPath gpxPath = simgear::Dir::current().path() / "test_gpx.gpx";
{
sg_ofstream s(gpxPath);
s << R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" creator="SkyVector" version="1.1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<rte>
<name>KJFK-KBOS</name>
<rtept lat="40.639928" lon="-73.778692">
<name>KJFK</name>
<overfly>false</overfly>
</rtept>
<rtept lat="41.641106" lon="-72.547419">
<name>HFD</name>
<overfly>false</overfly>
</rtept>
<rtept lat="42.362944" lon="-71.006389">
<name>KBOS</name>
<overfly>false</overfly>
</rtept>
</rte>
</gpx>
)";
}
CPPUNIT_ASSERT(f->load(gpxPath));
auto kbos = FGAirport::getByIdent("KBOS");
auto kjfk = FGAirport::getByIdent("KJFK");
CPPUNIT_ASSERT_EQUAL(kjfk, f->departureAirport());
CPPUNIT_ASSERT_EQUAL(static_cast<FGRunway*>(nullptr), f->departureRunway());
CPPUNIT_ASSERT_EQUAL(3, f->numLegs());
CPPUNIT_ASSERT_EQUAL(kbos, f->destinationAirport());
auto wp1 = f->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(std::string{"HFD"}, wp1->waypoint()->ident());
auto wp2 = f->legAtIndex(2);
CPPUNIT_ASSERT_EQUAL(std::string{"KBOS"}, wp2->waypoint()->ident());
}
const std::string flightPlanXMLData =
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>
</route>
</PropertyList>
)";
// The same test as above, but for a file exported from the route manager or online
void RouteManagerTests::loadFGFP()
{
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp.fgfp";
{
sg_ofstream s(fgfpPath);
s << flightPlanXMLData;
}
CPPUNIT_ASSERT(f->load(fgfpPath));
auto eddm = FGAirport::getByIdent("EDDM");
auto eddf = FGAirport::getByIdent("EDDF");
CPPUNIT_ASSERT_EQUAL(eddm, f->departureAirport());
CPPUNIT_ASSERT_EQUAL(eddm->getRunwayByIdent("08R")->ident(), f->departureRunway()->ident());
CPPUNIT_ASSERT_EQUAL(7, f->numLegs());
CPPUNIT_ASSERT_EQUAL(eddf, f->destinationAirport());
auto wp1 = f->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(std::string{"GIVMI"}, wp1->waypoint()->ident());
auto wp2 = f->legAtIndex(6);
CPPUNIT_ASSERT_EQUAL(std::string{"EDDF"}, wp2->waypoint()->ident());
}
void RouteManagerTests::testRouteWithProcedures()
{
if (!static_haveProcedures)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
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)"});
// check it in Nasal too
bool ok = FGTestApi::executeNasal(
R"(
var f = flightplan();
var depEnd = f.getWP(f.firstNonDepartureLeg - 1);
var firstArrival = f.getWP(f.firstArrivalLeg);
var firstApproach = f.getWP(f.firstApproachLeg);
var destRunway = f.getWP(f.destination_runway_leg);
unitTest.assert_equal(depEnd.id, 'CANDR');
var firstEnroute = f.getWP(f.firstNonDepartureLeg );
unitTest.assert_equal(firstEnroute.id, 'TOMYE');
unitTest.assert_equal(firstArrival.id, 'BEDUM');
unitTest.assert_equal(firstApproach.id, 'D070O');
unitTest.assert_equal(destRunway.id, '18R');
)");
CPPUNIT_ASSERT(ok);
}
void RouteManagerTests::testRouteWithApproachProcedures()
{
if (!static_haveProcedures)
return;
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
auto kjfk = FGAirport::findByIdent("KJFK");
auto eddm = FGAirport::findByIdent("EDDM");
f->setDeparture(kjfk->getRunwayByIdent("13L"));
f->setDestination(eddm->getRunwayByIdent("08R"));
f->setSTAR(eddm->findSTARWithIdent("ABGA3A.08R"));
f->setApproach(eddm->findApproachWithIdent("ILS08R"), "NAP08");
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 startOfSTAR = f->legAtIndex(f->indexOfFirstArrivalWaypoint());
CPPUNIT_ASSERT_EQUAL(startOfSTAR->waypoint()->ident(), string{"ABGAS"});
auto endOfSTAR = f->legAtIndex(f->indexOfFirstApproachWaypoint() - 1);
CPPUNIT_ASSERT_EQUAL(endOfSTAR->waypoint()->ident(), string{"MIQ"});
auto startOfApproach = f->legAtIndex(f->indexOfFirstApproachWaypoint());
CPPUNIT_ASSERT_EQUAL(startOfApproach->waypoint()->ident(), string{"NAPSA"});
auto startOfCoreApproach = f->legAtIndex(f->indexOfFirstApproachWaypoint() + 6);
CPPUNIT_ASSERT_EQUAL(startOfCoreApproach->waypoint()->ident(), string{"BEGEN"});
auto landingRunway = f->legAtIndex(f->indexOfDestinationRunwayWaypoint());
CPPUNIT_ASSERT(landingRunway->waypoint()->source() == f->destinationRunway());
// // check it in Nasal too
// bool ok = FGTestApi::executeNasal(
// R"(
// var f = flightplan();
// var depEnd = f.getWP(f.firstNonDepartureLeg - 1);
// var firstArrival = f.getWP(f.firstArrivalLeg);
// var firstApproach = f.getWP(f.firstApproachLeg);
// var destRunway = f.getWP(f.destination_runway_leg);
//
// unitTest.assert_equal(depEnd.id, 'CANDR');
//
// var firstEnroute = f.getWP(f.firstNonDepartureLeg );
// unitTest.assert_equal(firstEnroute.id, 'TOMYE');
//
// unitTest.assert_equal(firstArrival.id, 'BEDUM');
// unitTest.assert_equal(firstApproach.id, 'D070O');
// unitTest.assert_equal(destRunway.id, '18R');
// )");
//
// CPPUNIT_ASSERT(ok);
}
void RouteManagerTests::testsSelectNavaid()
{
// this captures the issue at:
// https://sourceforge.net/p/flightgear/codetickets/2372/
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
auto usss = FGAirport::findByIdent("USSS");
auto eddh = FGAirport::findByIdent("EDDH");
f->setDeparture(usss); // Yekaterinberg
f->setDestination(eddh);
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
rmNode->setStringValue("input", "@INSERT1:UUDD");
rmNode->setStringValue("input", "@INSERT2:UKKM");
rmNode->setStringValue("input", "@INSERT2:IP");
rmNode->setStringValue("input", "@INSERT3:OD");
auto leg = f->legAtIndex(2);
auto wp1 = leg->waypoint();
CPPUNIT_ASSERT_EQUAL(wp1->ident(), string{"IP"});
CPPUNIT_ASSERT_EQUAL(wp1->source()->name(), string{"ZAKHAROVKA NDB"});
CPPUNIT_ASSERT_DOUBLES_EQUAL(227, leg->courseDeg(), 0.5);
CPPUNIT_ASSERT_DOUBLES_EQUAL(59, leg->distanceNm(), 0.5);
leg = f->legAtIndex(3);
auto wp2 = leg->waypoint();
CPPUNIT_ASSERT_EQUAL(wp2->ident(), string{"OD"});
CPPUNIT_ASSERT_EQUAL(wp2->source()->name(), string{"BRYANSK NDB"});
}
void RouteManagerTests::testsSelectWaypoint()
{
// another part of the issue at
// https://sourceforge.net/p/flightgear/codetickets/2372/
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
rmNode->setStringValue("input", "70N,015E");
rmNode->setStringValue("input", "55N,015E");
rmNode->setStringValue("input", "@INSERT1:OSS");
rmNode->setStringValue("input", "@INSERT2:BOR");
auto leg = f->legAtIndex(1);
auto wp1 = leg->waypoint();
CPPUNIT_ASSERT_EQUAL(wp1->ident(), string{"OSS"});
CPPUNIT_ASSERT_EQUAL(wp1->source()->name(), string{"OSTERSUND VOR-DME"});
// CPPUNIT_ASSERT_DOUBLES_EQUAL(227, leg->courseDeg(), 0.5);
// CPPUNIT_ASSERT_DOUBLES_EQUAL(59, leg->distanceNm(), 0.5);
leg = f->legAtIndex(2);
auto wp2 = leg->waypoint();
CPPUNIT_ASSERT_EQUAL(wp2->ident(), string{"BOR"});
CPPUNIT_ASSERT_EQUAL(wp2->source()->name(), string{"BORLANGE VOR-DME"});
}
void RouteManagerTests::testCommandAPI()
{
auto rm = globals->get_subsystem<FGRouteMgr>();
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_2.fgfp";
{
sg_ofstream s(fgfpPath);
s << flightPlanXMLData;
}
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("path", fgfpPath.utf8Str());
CPPUNIT_ASSERT(globals->get_commands()->execute("load-flightplan", args));
}
auto f = rm->flightPlan();
CPPUNIT_ASSERT_EQUAL(7, f->numLegs());
CPPUNIT_ASSERT(!f->isActive());
{
SGPropertyNode_ptr args(new SGPropertyNode);
CPPUNIT_ASSERT(globals->get_commands()->execute("activate-flightplan", args));
}
CPPUNIT_ASSERT(f->isActive());
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setIntValue("index", 3);
CPPUNIT_ASSERT(globals->get_commands()->execute("set-active-waypt", args));
}
CPPUNIT_ASSERT_EQUAL(3, f->currentIndex());
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setIntValue("index", 4);
args->setStringValue("navaid", "WLD");
// let's build an offset waypoint for fun
args->setDoubleValue("offset-nm", 10.0);
args->setDoubleValue("radial", 30);
CPPUNIT_ASSERT(globals->get_commands()->execute("insert-waypt", args));
}
auto waldaWpt = f->legAtIndex(4)->waypoint();
auto waldaVOR = waldaWpt->source();
CPPUNIT_ASSERT_EQUAL(string{"WALDA VOR-DME"}, waldaVOR->name());
auto d = SGGeodesy::distanceNm(waldaVOR->geod(), waldaWpt->position());
CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, d, 0.1);
}
void RouteManagerTests::testRMBug2616()
{
auto edty = FGAirport::findByIdent("EDTY"s);
edty->testSuiteInjectProceduresXML(SGPath::fromUtf8(FG_TEST_SUITE_DATA) / "EDTY.procedures.xml");
auto ils28Approach = edty->findApproachWithIdent("ILS28"s);
CPPUNIT_ASSERT(ils28Approach);
auto rm = globals->get_subsystem<FGRouteMgr>();
auto f = rm->flightPlan();
f->clearLegs();
auto edds = FGAirport::findByIdent("EDDS");
f->setDeparture(edds->getRunwayByIdent("25"));
f->setDestination(edty->getRunwayByIdent("28"));
f->setApproach(ils28Approach);
CPPUNIT_ASSERT(f->destinationRunway()->ident() == "28"s);
CPPUNIT_ASSERT(f->approach()->ident() == "ILS28"s);
rm->activate();
CPPUNIT_ASSERT(f->isActive());
}
void RouteManagerTests::testsSelectWaypoint2()
{
auto rm = globals->get_subsystem<FGRouteMgr>();
FlightPlanRef f = FlightPlan::create();
rm->setFlightPlan(f);
auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true);
rmNode->setStringValue("input", "UAAA");
rmNode->setStringValue("input", "EDDH");
rmNode->setStringValue("input", "@INSERT1:ALM");
auto leg = f->legAtIndex(1);
auto wp1 = leg->waypoint();
CPPUNIT_ASSERT_EQUAL(wp1->ident(), string{"ALM"});
// CPPUNIT_ASSERT_EQUAL(wp1->source()->name(), string{"ALMATY VOR-DME"});
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2019 James Turner
*
* 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/>.
*/
#ifndef _FG_ROUTE_MANAGER_UNIT_TESTS_HXX
#define _FG_ROUTE_MANAGER_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
class SGGeod;
class GPS;
// The flight plan unit tests.
class RouteManagerTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(RouteManagerTests);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testDefaultSID);
CPPUNIT_TEST(testDefaultApproach);
CPPUNIT_TEST(testDirectToLegOnFlightplanAndResume);
CPPUNIT_TEST(testHoldFromNasal);
CPPUNIT_TEST(testSequenceDiscontinuityAndResume);
CPPUNIT_TEST(testHiddenWaypoints);
CPPUNIT_TEST(loadGPX);
CPPUNIT_TEST(loadFGFP);
CPPUNIT_TEST(testRouteWithProcedures);
CPPUNIT_TEST(testRouteWithApproachProcedures);
CPPUNIT_TEST(testsSelectNavaid);
CPPUNIT_TEST(testCommandAPI);
CPPUNIT_TEST(testRMBug2616);
CPPUNIT_TEST(testsSelectWaypoint);
CPPUNIT_TEST(testsSelectWaypoint2);
CPPUNIT_TEST_SUITE_END();
// void setPositionAndStabilise(FGNavRadio* r, const SGGeod& g);
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
void setPositionAndStabilise(const SGGeod& g);
// The tests.
void testBasic();
void testDefaultSID();
void testDefaultApproach();
void testDirectToLegOnFlightplanAndResume();
void testHoldFromNasal();
void testSequenceDiscontinuityAndResume();
void testHiddenWaypoints();
void loadGPX();
void loadFGFP();
void testRouteWithProcedures();
void testRouteWithApproachProcedures();
void testsSelectNavaid();
void testCommandAPI();
void testsSelectWaypoint();
void testRMBug2616();
void testsSelectWaypoint2();
private:
GPS* m_gps = nullptr;
};
#endif // _FG_ROUTE_MANAGER_UNIT_TESTS_HXX

View File

@@ -0,0 +1,14 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: (C) 2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "test_swiftAircraftManager.hxx"
#include "test_swiftService.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwiftAircraftManagerTest, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwiftServiceTest, "Unit tests");

View File

@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: (C) 2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "test_swiftAircraftManager.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "Network/Swift/SwiftAircraftManager.h"
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
void SwiftAircraftManagerTest::setUp()
{
FGTestApi::setUp::initTestGlobals("SwiftService");
}
void SwiftAircraftManagerTest::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
std::vector<SGSharedPtr<FGAIBase>> SwiftAircraftManagerTest::getAIList()
{
return globals->get_subsystem<FGAIManager>()->get_ai_list();
}
void SwiftAircraftManagerTest::testAircraftManager()
{
globals->add_new_subsystem<FGAIManager>(SGSubsystemMgr::POST_FDM);
globals->get_subsystem<FGAIManager>()->bind();
globals->get_subsystem<FGAIManager>()->init();
FGSwiftAircraftManager acm;
acm.addPlane("BER123", "PATH_TO_MODEL");
CPPUNIT_ASSERT_EQUAL(globals->get_subsystem<FGAIManager>()->get_ai_list().size(), (size_t)1);
acm.addPlane("BAW123", "PATH_TO_MODEL");
CPPUNIT_ASSERT_EQUAL(globals->get_subsystem<FGAIManager>()->get_ai_list().size(), (size_t)2);
for (auto& aircraft : getAIList()) {
CPPUNIT_ASSERT(!aircraft->getDie());
}
acm.removeAllPlanes();
for (auto& aircraft : getAIList()) {
CPPUNIT_ASSERT(aircraft->getDie());
}
acm.addPlane("BER123", "PATH_TO_MODEL");
CPPUNIT_ASSERT(!getAIList()[2]->getDie());
acm.removePlane("BER123");
CPPUNIT_ASSERT(getAIList()[2]->getDie());
// Test position updates
acm.addPlane("SAS123", "PATH_TO_MODEL");
SGGeod position;
position.setLatitudeDeg(50.0);
position.setLongitudeDeg(6.0);
position.setElevationM(1024);
acm.updatePlanes({{"SAS123", position, SGVec3d(1.0, 2.0, 3.0), 200, false}});
CPPUNIT_ASSERT_EQUAL(fgGetString("/ai/models/swift[3]/callsign"), std::string("SAS123"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/pitch-deg"), 1.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/roll-deg"), 2.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/true-heading-deg"), 3.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/latitude-deg"), 50.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/longitude-deg"), 6.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/velocities/true-airspeed-kt"), 200, 0.1);
position.setLatitudeDeg(20.0);
position.setLongitudeDeg(4.0);
acm.updatePlanes({{"SAS123", position, SGVec3d(5.0, 6.0, 7.0), 400, false}});
CPPUNIT_ASSERT_EQUAL(fgGetString("/ai/models/swift[3]/callsign"), std::string("SAS123"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/pitch-deg"), 5.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/roll-deg"), 6.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/true-heading-deg"), 7.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/latitude-deg"), 20.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/longitude-deg"), 4.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/velocities/true-airspeed-kt"), 400, 0.1);
// Update another aircraft
acm.addPlane("DAL123", "PATH_TO_MODEL");
position.setLatitudeDeg(-20.0);
position.setLongitudeDeg(5.0);
acm.updatePlanes({{"DAL123", position, SGVec3d(1.0, 1.0, 1.0), 250, false}});
CPPUNIT_ASSERT_EQUAL(fgGetString("/ai/models/swift[4]/callsign"), std::string("DAL123"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/orientation/pitch-deg"), 1.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/orientation/roll-deg"), 1.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/orientation/true-heading-deg"), 1.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/position/latitude-deg"), -20.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/position/longitude-deg"), 5.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[4]/velocities/true-airspeed-kt"), 250, 0.1);
CPPUNIT_ASSERT_EQUAL(fgGetString("/ai/models/swift[3]/callsign"), std::string("SAS123"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/pitch-deg"), 5.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/roll-deg"), 6.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/orientation/true-heading-deg"), 7.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/latitude-deg"), 20.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/position/longitude-deg"), 4.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/ai/models/swift[3]/velocities/true-airspeed-kt"), 400, 0.1);
}

View File

@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: (C) 2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef FLIGHTGEAR_TEST_SWIFTAIRCRAFTMANAGER_H
#define FLIGHTGEAR_TEST_SWIFTAIRCRAFTMANAGER_H
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <AIModel/AIBase.hxx>
class SwiftAircraftManagerTest : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(SwiftAircraftManagerTest);
CPPUNIT_TEST(testAircraftManager);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// Test
void testAircraftManager();
// Helper
std::vector<SGSharedPtr<FGAIBase>> getAIList();
};
#endif //FLIGHTGEAR_TEST_SWIFTAIRCRAFTMANAGER_H

View File

@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: (C) 2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "test_swiftService.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "Network/Swift/service.h"
#include <Main/fg_props.hxx>
void SwiftServiceTest::setUp()
{
FGTestApi::setUp::initTestGlobals("SwiftService");
// Setup properties
fgSetBool("/sim/freeze/master", true);
fgSetDouble("/position/latitude-deg", 50.12);
fgSetDouble("/position/longitude-deg", 6.3);
fgSetDouble("/position/altitude-ft", 12000.0);
fgSetDouble("/position/altitude-agl-ft", 1020.0);
fgSetDouble("/velocities/groundspeed-kt", 242.0);
fgSetDouble("/orientation/pitch-deg", 3.0);
fgSetDouble("/orientation/roll-deg", 1.0);
fgSetDouble("/orientation/heading-deg", 230.0);
fgSetBool("/gear/gear/wow", false);
fgSetDouble("/instrumentation/comm/frequencies/selected-mhz", 122.8);
fgSetDouble("/instrumentation/comm/frequencies/standby-mhz", 135.65);
fgSetDouble("/instrumentation/comm[1]/frequencies/selected-mhz", 121.5);
fgSetDouble("/instrumentation/comm[1]/frequencies/standby-mhz", 118.3);
fgSetInt("/instrumentation/transponder/id-code", 1234);
fgSetInt("/instrumentation/transponder/inputs/knob-mode", 1);
fgSetBool("/instrumentation/transponder/ident", true);
fgSetBool("/controls/lighting/beacon", true);
fgSetBool("/controls/lighting/landing-lights", false);
fgSetBool("/controls/lighting/nav-lights", true);
fgSetBool("/controls/lighting/strobe", true);
fgSetBool("/controls/lighting/taxi-light", false);
fgSetBool("/instrumentation/altimeter/serviceable", true);
fgSetDouble("/instrumentation/altimeter/pressure-alt-ft", 24000.0);
fgSetDouble("/surface-positions/flap-pos-norm", 0.0);
fgSetDouble("/gear/gear/position-norm", 0.7);
fgSetDouble("/surface-positions/speedbrake-pos-norm", 0.4);
fgSetString("/sim/aircraft", "glider");
fgSetDouble("/position/ground-elev-m", 778.0);
fgSetDouble("/velocities/speed-east-fps", 20.0);
fgSetDouble("/velocities/speed-down-fps", -30.0);
fgSetDouble("/velocities/speed-north-fps", -10.2);
fgSetDouble("/orientation/roll-rate-degps", 1.0);
fgSetDouble("/orientation/pitch-rate-degps", 0.0);
fgSetDouble("/orientation/yaw-rate-degps", -2.0);
fgSetDouble("/instrumentation/comm/volume", 42.0);
fgSetDouble("/instrumentation/comm[1]/volume", 100.0);
}
void SwiftServiceTest::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
void SwiftServiceTest::testService()
{
FGSwiftBus::CService service;
CPPUNIT_ASSERT(service.isPaused());
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getLatitude(), 50.12, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getLongitude(), 6.3, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getAltitudeMSL(), 12000.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getHeightAGL(), 1020.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getGroundSpeed(), 242.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getPitch(), 3.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getRoll(), 1.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getTrueHeading(), 230.0, 0.1);
CPPUNIT_ASSERT(!service.getAllWheelsOnGround());
CPPUNIT_ASSERT_EQUAL(service.getCom1Active(), 122800);
CPPUNIT_ASSERT_EQUAL(service.getCom1Standby(), 135650);
CPPUNIT_ASSERT_EQUAL(service.getCom2Active(), 121500);
CPPUNIT_ASSERT_EQUAL(service.getCom2Standby(), 118300);
CPPUNIT_ASSERT_EQUAL(service.getTransponderCode(), 1234);
CPPUNIT_ASSERT_EQUAL(service.getTransponderMode(), 1);
CPPUNIT_ASSERT(service.getTransponderIdent());
CPPUNIT_ASSERT(service.getBeaconLightsOn());
CPPUNIT_ASSERT(!service.getLandingLightsOn());
CPPUNIT_ASSERT(service.getNavLightsOn());
CPPUNIT_ASSERT(service.getStrobeLightsOn());
CPPUNIT_ASSERT(!service.getTaxiLightsOn());
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getFlapsDeployRatio(), 0.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getGearDeployRatio(), 0.7, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getSpeedBrakeRatio(), 0.4, 0.1);
CPPUNIT_ASSERT_EQUAL(service.getAircraftName(), std::string("glider"));
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getGroundElevation(), 778.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getVelocityX(), 20.0 * SG_FEET_TO_METER, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getVelocityY(), -30.0 * SG_FEET_TO_METER * -1, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getVelocityZ(), -10.2 * SG_FEET_TO_METER, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getRollRate(), 1.0 * SG_DEGREES_TO_RADIANS, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getPitchRate(), 0.0 * SG_DEGREES_TO_RADIANS, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getYawRate(), -2.0 * SG_DEGREES_TO_RADIANS, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getCom1Volume(), 42.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getCom2Volume(), 100.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getPressAlt(), 24000.0, 0.1);
fgSetBool("/instrumentation/altimeter/serviceable", false);
// Fallback if altimeter is not serviceable
CPPUNIT_ASSERT_DOUBLES_EQUAL(service.getPressAlt(), service.getAltitudeMSL(), 0.1);
// Test setter
service.setCom1Active(128550);
CPPUNIT_ASSERT_EQUAL(service.getCom1Active(), 128550);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/instrumentation/comm/frequencies/selected-mhz"), 128.550, 0.1);
service.setCom1Standby(128650);
CPPUNIT_ASSERT_EQUAL(service.getCom1Standby(), 128650);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/instrumentation/comm/frequencies/standby-mhz"), 128.650, 0.1);
service.setCom2Active(121900);
CPPUNIT_ASSERT_EQUAL(service.getCom2Active(), 121900);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz"), 121.900, 0.1);
service.setCom2Standby(121600);
CPPUNIT_ASSERT_EQUAL(service.getCom2Standby(), 121600);
CPPUNIT_ASSERT_DOUBLES_EQUAL(fgGetDouble("/instrumentation/comm[1]/frequencies/standby-mhz"), 121.600, 0.1);
service.setTransponderCode(2000);
CPPUNIT_ASSERT_EQUAL(service.getTransponderCode(), 2000);
CPPUNIT_ASSERT_EQUAL(fgGetInt("/instrumentation/transponder/id-code"), 2000);
service.setTransponderMode(0);
CPPUNIT_ASSERT_EQUAL(service.getTransponderMode(), 0);
CPPUNIT_ASSERT_EQUAL(fgGetInt("/instrumentation/transponder/inputs/knob-mode"), 0);
}

View File

@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: (C) 2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef FLIGHTGEAR_TEST_SWIFTSERVICE_H
#define FLIGHTGEAR_TEST_SWIFTSERVICE_H
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
class SwiftServiceTest : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(SwiftServiceTest);
CPPUNIT_TEST(testService);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// Test
void testService();
};
#endif //FLIGHTGEAR_TEST_SWIFTSERVICE_H

View File

@@ -0,0 +1,14 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testNasalSys.cxx
${CMAKE_CURRENT_SOURCE_DIR}/testGC.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/testNasalSys.hxx
${CMAKE_CURRENT_SOURCE_DIR}/testGC.hxx
PARENT_SCOPE
)

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016 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/>.
*/
#include "testNasalSys.hxx"
#include "testGC.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NasalSysTests, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NasalGCTests, "Unit tests");

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2020 James Turner
*
* 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/>.
*/
#include "testGC.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include <Scripting/NasalSys.hxx>
#include <Main/FGInterpolator.hxx>
extern bool global_nasalMinimalInit;
// Set up function for each test.
void NasalGCTests::setUp()
{
FGTestApi::setUp::initTestGlobals("NasalGC");
fgInitAllowedPaths();
auto nasalNode = globals->get_props()->getNode("nasal", true);
globals->add_subsystem("prop-interpolator", new FGInterpolator, SGSubsystemMgr::INIT);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
global_nasalMinimalInit = true;
globals->add_new_subsystem<FGNasalSys>(SGSubsystemMgr::INIT);
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void NasalGCTests::tearDown()
{
global_nasalMinimalInit = false;
FGTestApi::tearDown::shutdownTestGlobals();
}
// Test test
void NasalGCTests::testDummy()
{
bool ok = FGTestApi::executeNasal(R"(
var foo = {
"name": "PFD-Test",
"size": [512, 512],
"view": [768, 1024],
"mipmapping": 1
};
globals.foo1 = foo;
)");
CPPUNIT_ASSERT(ok);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 James Turner
*
* 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>
class NasalGCTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(NasalGCTests);
CPPUNIT_TEST(testDummy);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testDummy();
};

View File

@@ -0,0 +1,243 @@
/*
* Copyright (C) 2016 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/>.
*/
#include "testNasalSys.hxx"
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <simgear/structure/commands.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include <Scripting/NasalSys.hxx>
#include <Main/FGInterpolator.hxx>
// Set up function for each test.
void NasalSysTests::setUp()
{
FGTestApi::setUp::initTestGlobals("NasalSys");
FGTestApi::setUp::initNavDataCache();
fgInitAllowedPaths();
globals->get_props()->getNode("nasal", true);
globals->add_subsystem("prop-interpolator", new FGInterpolator, SGSubsystemMgr::INIT);
globals->get_subsystem_mgr()->bind();
globals->get_subsystem_mgr()->init();
globals->add_new_subsystem<FGNasalSys>(SGSubsystemMgr::INIT);
globals->get_subsystem_mgr()->postinit();
}
// Clean up after each test.
void NasalSysTests::tearDown()
{
FGTestApi::tearDown::shutdownTestGlobals();
}
// Test test
void NasalSysTests::testStructEquality()
{
bool ok = FGTestApi::executeNasal(R"(
var foo = {
"name": "Bob",
"size": [512, 512],
"mipmapping": 1.9
};
var bar = {
"name": "Bob",
"size": [512, 512],
"mipmapping": 1.9
};
unitTest.assert_equal(foo, bar);
append(bar.size, "Wowow");
unitTest.assert(unitTest.equal(foo, bar) == 0);
append(foo.size, "Wowow");
unitTest.assert_equal(foo, bar);
foo.wibble = 99.1;
unitTest.assert(unitTest.equal(foo, bar) == 0);
bar.wibble = 99;
unitTest.assert(unitTest.equal(foo, bar) == 0);
bar.wibble = 99.1;
unitTest.assert_equal(foo, bar);
)");
CPPUNIT_ASSERT(ok);
}
void NasalSysTests::testCommands()
{
auto nasalSys = globals->get_subsystem<FGNasalSys>();
nasalSys->getAndClearErrorList();
fgSetInt("/foo/test", 7);
bool ok = FGTestApi::executeNasal(R"(
var f = func {
var i = getprop('/foo/test');
setprop('foo/test', i + 4);
};
addcommand('do-foo', f);
var ok = fgcommand('do-foo');
unitTest.assert(ok);
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(11, fgGetInt("/foo/test"));
SGPropertyNode_ptr args(new SGPropertyNode);
ok = globals->get_commands()->execute("do-foo", args);
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(15, fgGetInt("/foo/test"));
ok = FGTestApi::executeNasal(R"(
var g = func { print('fail'); };
addcommand('do-foo', g);
)");
CPPUNIT_ASSERT(ok);
auto errors = nasalSys->getAndClearErrorList();
CPPUNIT_ASSERT_EQUAL(errors.size(), static_cast<size_t>(1));
// old command should still be registered and work
ok = globals->get_commands()->execute("do-foo", args);
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(19, fgGetInt("/foo/test"));
ok = FGTestApi::executeNasal(R"(
removecommand('do-foo');
)");
CPPUNIT_ASSERT(ok);
ok = FGTestApi::executeNasal(R"(
var ok = fgcommand('do-foo');
unitTest.assert(!ok);
)");
CPPUNIT_ASSERT(ok);
errors = nasalSys->getAndClearErrorList();
CPPUNIT_ASSERT_EQUAL(0UL, (unsigned long) errors.size());
// should fail, command is removed
ok = globals->get_commands()->execute("do-foo", args);
CPPUNIT_ASSERT(!ok);
CPPUNIT_ASSERT_EQUAL(19, fgGetInt("/foo/test"));
}
void NasalSysTests::testAirportGhost()
{
auto nasalSys = globals->get_subsystem<FGNasalSys>();
nasalSys->getAndClearErrorList();
bool ok = FGTestApi::executeNasal(R"(
var apt = airportinfo('LFBD');
var taxiways = apt.taxiways;
unitTest.assert_equal(size(taxiways), 0);
)");
CPPUNIT_ASSERT(ok);
}
// https://sourceforge.net/p/flightgear/codetickets/2246/
void NasalSysTests::testCompileLarge()
{
// auto nasalSys = globals->get_subsystem<FGNasalSys>();
// nasalSys->getAndClearErrorList();
//
//
// string code = "var foo = 0;\n";
// for (int i=0; i<14; ++i) {
// code = code + code;
// }
//
// nasalSys->parseAndRun(code);
// bool ok = FGTestApi::executeNasal(R"(
//var try_compile = func(code) {
// call(compile, [code], nil,nil,var err=[]);
// return size(err);
//}
//
//var expression = "var foo = 0;\n";
//var code = "";
//
//for(var i=0;i<=10000;i+=1) {
// code ~= expression;
// if (try_compile(code) == 1) {
// print("Error compiling, LOC count is:", i+1);
// break;
// }
//}
// )");
// CPPUNIT_ASSERT(ok);
}
void NasalSysTests::testRoundFloor()
{
auto nasalSys = globals->get_subsystem<FGNasalSys>();
nasalSys->getAndClearErrorList();
bool ok = FGTestApi::executeNasal(R"(
unitTest.assert_equal(math.round(121266, 1000), 121000);
unitTest.assert_equal(math.round(121.1234, 0.01), 121.12);
unitTest.assert_equal(math.round(121266, 10), 121270);
unitTest.assert_equal(math.floor(121766, 1000), 121000);
unitTest.assert_equal(math.floor(121.1299, 0.01), 121.12);
# floor towards lower value
unitTest.assert_equal(math.floor(-121.1229, 0.01), -121.13);
# truncate towards zero
unitTest.assert_equal(math.trunc(-121.1229, 0.01), -121.12);
unitTest.assert_equal(math.trunc(-121.1299, 0.01), -121.12);
)");
CPPUNIT_ASSERT(ok);
}
void NasalSysTests::testRange()
{
auto nasalSys = globals->get_subsystem<FGNasalSys>();
nasalSys->getAndClearErrorList();
bool ok = FGTestApi::executeNasal(R"(
unitTest.assert_equal(range(5), [0, 1, 2, 3, 4]);
unitTest.assert_equal(range(2, 8), [2, 3, 4, 5, 6, 7]);
unitTest.assert_equal(range(2, 10, 3), [2, 5, 8]);
)");
CPPUNIT_ASSERT(ok);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 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/>.
*/
#ifndef _FG_NASALSYS_UNIT_TESTS_HXX
#define _FG_NASALSYS_UNIT_TESTS_HXX
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
// The unit tests of the FGNasalSys subsystem.
class NasalSysTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(NasalSysTests);
CPPUNIT_TEST(testStructEquality);
CPPUNIT_TEST(testCommands);
CPPUNIT_TEST(testAirportGhost);
CPPUNIT_TEST(testCompileLarge);
CPPUNIT_TEST(testRoundFloor);
CPPUNIT_TEST(testRange);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testStructEquality();
void testCommands();
void testAirportGhost();
void testCompileLarge();
void testRoundFloor();
void testRange();
};
#endif // _FG_NASALSYS_UNIT_TESTS_HXX

View File

@@ -0,0 +1,14 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test-mktime.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_Views.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test-mktime.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_Views.hxx
PARENT_SCOPE
)

Some files were not shown because too many files have changed in this diff Show More