Files
edgetx-gx12/radio/src/tests/modelslist_labels.cpp
T

178 lines
6.9 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
// Custom-screen and topbar-widget layout data (CustomScreenData /
// TopBarPersistentData) is stored per-process in static globals rather than
// inside ModelData itself (see ModelData::getScreenData()/getTopbarData()),
// because that data can be large and only the currently loaded model's copy
// needs to be resident. Any code that reads or writes a *full* ModelData
// buffer -- as opposed to the header-only PartialModel -- for a model other
// than the one currently loaded therefore risks reading/writing those
// globals on behalf of the wrong model.
//
// Label management (renaming/adding/removing labels) routinely touches
// models other than the currently loaded one. These tests exercise that
// label-management path through its public ModelMap API and assert the
// currently loaded model's screen/topbar data is left alone, regardless of
// what label edits are made to other models on disk.
#include "gtests.h"
#include "location.h"
#include <filesystem>
#include "storage/modelslist.h"
#include "storage/sdcard_common.h"
#include "storage/sdcard_yaml.h"
#include "storage/yaml/yaml_datastructs.h"
#if defined(COLORLCD)
namespace fs = std::filesystem;
class ModelMapFsTest : public ::testing::Test
{
protected:
fs::path scratchDir;
void SetUp() override
{
scratchDir = fs::temp_directory_path() /
fs::path("edgetx-gtest-modelslist-labels");
std::error_code ec;
fs::remove_all(scratchDir, ec);
fs::create_directories(scratchDir / "MODELS", ec);
ASSERT_FALSE(ec) << "could not create scratch MODELS directory";
simuFatfsSetPaths(scratchDir.string().c_str(), nullptr);
modelslist.clear();
modelslabels.clear();
memclear(&g_model, sizeof(g_model));
}
void TearDown() override
{
modelslist.clear();
modelslabels.clear();
simuFatfsSetPaths(TESTS_PATH, nullptr);
std::error_code ec;
fs::remove_all(scratchDir, ec);
}
// Writes a standalone model YAML file to <scratchDir>/MODELS/<filename>
// with the given label list and screen/topbar marker values. This must be
// called *before* the active model's own screen/topbar markers are set on
// g_model: since getScreenData()/getTopbarData() are shared globals, using
// them here to build this "other" model's fixture is itself writing
// through the same storage g_model uses, exactly like the code under test
// does. The test bodies set the active model's markers afterwards, which
// simulates that model actually being loaded, and is what the assertions
// check survives the label edit untouched.
void writeFixtureModel(const char* filename, const char* labels,
const char* screenLayoutId, const char* widgetName)
{
ModelData model;
memclear(&model, sizeof(model));
// A real model always has a name; an entirely-default header (no name,
// no labels, zeroed modelId) is omitted from the written YAML altogether
// as a compaction optimisation, which would leave this fixture without
// a "header:" section at all. Giving it a name keeps the fixture
// realistic and avoids that unrelated edge case.
strAppend(model.header.name, filename, LEN_MODEL_NAME);
strAppend(model.header.labels, labels, LABELS_LENGTH - 1);
model.getScreenData(0)->LayoutId = screenLayoutId;
model.getTopbarData()->zones[0].widgetName = widgetName;
char path[256];
getModelPath(path, filename);
ASSERT_EQ(writeFileYaml(path, get_modeldata_nodes(), (uint8_t*)&model, 0),
(const char*)nullptr)
<< "failed writing fixture model " << filename;
}
};
TEST_F(ModelMapFsTest, RenamingLabelOnOtherModelLeavesActiveScreenDataUntouched)
{
// Seed a second, non-active model on disk with a label and screen/topbar
// data distinct from the active model's.
writeFixtureModel("model0002.yml", "Foo", "OtherLayout", "OtherWidget");
ModelCell* other = modelslist.addModel("model0002.yml", false);
ASSERT_NE(other, nullptr);
ASSERT_FALSE(modelslabels.addLabelToModel("Foo", other, false));
// Now "load" the active model: register its cell and mark it current, and
// set its screen/topbar data to its own values.
ModelCell* active = modelslist.addModel("model0001.yml", false);
ASSERT_NE(active, nullptr);
modelslist.setCurrentModel(active);
g_model.getScreenData(0)->LayoutId = "ActiveLayout";
g_model.getTopbarData()->zones[0].widgetName = "ActiveWidget";
// Rename a label that only exists on the other, non-active model.
modelslabels.renameLabel("Foo", "Bar");
// The active model's screen/topbar data must be unaffected by editing an
// unrelated model's labels.
EXPECT_STREQ(g_model.getScreenData(0)->LayoutId.c_str(), "ActiveLayout");
EXPECT_STREQ(g_model.getTopbarData()->zones[0].widgetName.c_str(),
"ActiveWidget");
// The other model's file on disk should reflect the renamed label. Read
// back only the header (PartialModel), not a full ModelData, so this
// verification step doesn't itself touch the shared screen/topbar globals.
PartialModel partial;
memclear(&partial, sizeof(partial));
readModelYaml("model0002.yml", (uint8_t*)&partial, sizeof(PartialModel));
EXPECT_STREQ(partial.header.labels, "Bar");
}
TEST_F(ModelMapFsTest,
AddingLabelToOtherModelWithFileUpdateLeavesActiveScreenDataUntouched)
{
writeFixtureModel("model0002.yml", "", "OtherLayout", "OtherWidget");
ModelCell* other = modelslist.addModel("model0002.yml", false);
ASSERT_NE(other, nullptr);
ModelCell* active = modelslist.addModel("model0001.yml", false);
ASSERT_NE(active, nullptr);
modelslist.setCurrentModel(active);
g_model.getScreenData(0)->LayoutId = "ActiveLayout";
g_model.getTopbarData()->zones[0].widgetName = "ActiveWidget";
// update=true drives ModelMap::updateModelFile(), the second call site
// that reads/writes a non-active model's file on disk.
EXPECT_FALSE(modelslabels.addLabelToModel("Baz", other, true));
EXPECT_STREQ(g_model.getScreenData(0)->LayoutId.c_str(), "ActiveLayout");
EXPECT_STREQ(g_model.getTopbarData()->zones[0].widgetName.c_str(),
"ActiveWidget");
PartialModel partial;
memclear(&partial, sizeof(partial));
readModelYaml("model0002.yml", (uint8_t*)&partial, sizeof(PartialModel));
EXPECT_STREQ(partial.header.labels, "Baz");
}
#endif // defined(COLORLCD)