From f4aa5f06347b9a3b1f7150ead8e1049d25ff954b Mon Sep 17 00:00:00 2001 From: Wojciech Piwocha Date: Thu, 18 Jun 2026 16:29:41 +0200 Subject: [PATCH 1/3] tests: added parser tests --- tests/unit_tests/parser_unit_test.cpp | 375 ++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 tests/unit_tests/parser_unit_test.cpp diff --git a/tests/unit_tests/parser_unit_test.cpp b/tests/unit_tests/parser_unit_test.cpp new file mode 100644 index 0000000..ddab6e1 --- /dev/null +++ b/tests/unit_tests/parser_unit_test.cpp @@ -0,0 +1,375 @@ +#include + +#include +#include +#include +#include + +#include "neuronide.pb.h" +#include "parser/Parser.hpp" +#include "scene/Scene.hpp" +#include "scene/SceneObject.hpp" +#include "scene/components/BlinkComponent.hpp" + +// Pomocnicze funkcje do tworzenia tymczasowych plików .pb +namespace { + +std::string writeTempProto(const NeuronIDE::Scene& scene, const std::string& filename) { + const std::string path = (std::filesystem::temp_directory_path() / filename).string(); + std::ofstream out(path, std::ios::binary | std::ios::trunc); + EXPECT_TRUE(out.is_open()) << "Nie mozna otworzyc pliku tymczasowego: " << path; + scene.SerializeToOstream(&out); + return path; +} + +NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProject", + const std::string& objectName = "ObiektA", + bool isVisible = true, + double blinkFrequency = 1.5) { + NeuronIDE::Scene scene; + scene.set_project_name(projectName); + + auto* obj = scene.add_scene_objects(); + obj->set_name(objectName); + obj->set_is_visible(isVisible); + + auto* comp = obj->add_components(); + auto* blinker = comp->mutable_blinker(); + blinker->set_blink_frequency_hz(blinkFrequency); + + return scene; +} + +} // namespace + + +// Grupa: Parser -- otwieranie pliku +TEST(ParserFileTest, ThrowsWhenFileDoesNotExist) { + Parser parser; + EXPECT_THROW(parser.parse("/nonexistent/path/scene.pb"), std::runtime_error); +} + + +TEST(ParserFileTest, ReturnsNonNullSceneForValidFile) { + auto scene = buildSimpleScene(); + const std::string path = writeTempProto(scene, "valid_scene.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_NE(result, nullptr); +} + + +// Grupa: Parser -- nazwa projektu (Scene.project_name) +TEST(ParserSceneNameTest, SetsProjectName) { + auto scene = buildSimpleScene("MojProjekt"); + const std::string path = writeTempProto(scene, "name_test.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(result->getExperimentName(), "MojProjekt"); +} + +TEST(ParserSceneNameTest, EmptyProjectNameIsPreserved) { + auto scene = buildSimpleScene(""); + const std::string path = writeTempProto(scene, "empty_name.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(result->getExperimentName(), ""); +} + + +// Grupa: Parser -- liczba obiektow sceny +TEST(ParserObjectCountTest, EmptySceneHasNoObjects) { + NeuronIDE::Scene protoScene; + const std::string path = writeTempProto(protoScene, "no_objects.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_TRUE(result->getObjects().empty()); +} + +TEST(ParserObjectCountTest, SingleObjectIsLoaded) { + auto scene = buildSimpleScene("P", "Obj1"); + const std::string path = writeTempProto(scene, "one_object.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(result->getObjects().size(), 1u); +} + +TEST(ParserObjectCountTest, MultipleObjectsAreAllLoaded) { + NeuronIDE::Scene scene; + scene.set_project_name("Multi"); + for (int i = 0; i < 5; ++i) { + auto* obj = scene.add_scene_objects(); + obj->set_name("Obj" + std::to_string(i)); + obj->set_is_visible(true); + auto* comp = obj->add_components(); + comp->mutable_blinker()->set_blink_frequency_hz(static_cast(i)); + } + const std::string path = writeTempProto(scene, "multi_objects.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(result->getObjects().size(), 5u); +} + + +// Grupa: Parser -- atrybuty SceneObject +TEST(ParserSceneObjectTest, ObjectNameIsCorrect) { + auto scene = buildSimpleScene("P", "MojaRakieta"); + const std::string path = writeTempProto(scene, "obj_name.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(result->getObjects()[0]->name, "MojaRakieta"); +} + +TEST(ParserSceneObjectTest, ObjectIsVisibleWhenTrue) { + auto scene = buildSimpleScene("P", "Obj", true); + const std::string path = writeTempProto(scene, "obj_visible_true.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_TRUE(result->getObjects()[0]->isVisible); +} + +TEST(ParserSceneObjectTest, ObjectIsHiddenWhenFalse) { + auto scene = buildSimpleScene("P", "Obj", false); + const std::string path = writeTempProto(scene, "obj_visible_false.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_FALSE(result->getObjects()[0]->isVisible); +} + +TEST(ParserSceneObjectTest, ObjectsPreserveInsertionOrder) { + NeuronIDE::Scene scene; + scene.set_project_name("Order"); + const std::vector names = {"Alpha", "Beta", "Gamma"}; + for (const auto& n : names) { + auto* obj = scene.add_scene_objects(); + obj->set_name(n); + } + const std::string path = writeTempProto(scene, "order_test.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), names.size()); + for (size_t i = 0; i < names.size(); ++i) { + EXPECT_EQ(result->getObjects()[i]->name, names[i]); + } +} + + +// Grupa: Parser -- Transform +TEST(ParserTransformTest, TransformFieldsAreParsedCorrectly) { + NeuronIDE::Scene scene; + scene.set_project_name("TransformTest"); + auto* obj = scene.add_scene_objects(); + obj->set_name("Sprite"); + obj->set_is_visible(true); + + auto* tra = obj->mutable_transform(); + tra->set_x(10.5); + tra->set_y(20.25); + tra->set_width(64.0); + tra->set_height(128.0); + tra->set_rotation(45.0); + + const std::string path = writeTempProto(scene, "transform_test.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), 1u); + + const auto& t = result->getObjects()[0]->transform; + EXPECT_DOUBLE_EQ(t.posX, 10.5); + EXPECT_DOUBLE_EQ(t.posY, 20.25); + EXPECT_DOUBLE_EQ(t.width, 64.0); + EXPECT_DOUBLE_EQ(t.height, 128.0); + EXPECT_DOUBLE_EQ(t.rotation, 45.0); +} + +TEST(ParserTransformTest, DefaultTransformIsZeroWhenNotProvided) { + NeuronIDE::Scene scene; + scene.set_project_name("NoTransform"); + auto* obj = scene.add_scene_objects(); + obj->set_name("Bezpozycyjny"); + obj->set_is_visible(true); + + const std::string path = writeTempProto(scene, "no_transform.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), 1u); + + const auto& t = result->getObjects()[0]->transform; + EXPECT_DOUBLE_EQ(t.posX, 0.0); + EXPECT_DOUBLE_EQ(t.posY, 0.0); + EXPECT_DOUBLE_EQ(t.width, 0.0); + EXPECT_DOUBLE_EQ(t.height, 0.0); + EXPECT_DOUBLE_EQ(t.rotation, 0.0); +} + +TEST(ParserTransformTest, NegativeTransformValuesAreAccepted) { + NeuronIDE::Scene scene; + scene.set_project_name("NegTrans"); + auto* obj = scene.add_scene_objects(); + obj->set_name("Ujemny"); + obj->set_is_visible(true); + auto* tra = obj->mutable_transform(); + tra->set_x(-50.0); + tra->set_y(-100.0); + tra->set_rotation(-90.0); + + const std::string path = writeTempProto(scene, "neg_transform.pb"); + + Parser parser; + auto result = parser.parse(path); + const auto& t = result->getObjects()[0]->transform; + EXPECT_DOUBLE_EQ(t.posX, -50.0); + EXPECT_DOUBLE_EQ(t.posY, -100.0); + EXPECT_DOUBLE_EQ(t.rotation, -90.0); +} + + +// Grupa: Parser -- komponenty (BlinkComponent) +TEST(ParserComponentTest, ObjectWithNoComponentsHasEmptyComponentList) { + NeuronIDE::Scene scene; + scene.set_project_name("NoComp"); + auto* obj = scene.add_scene_objects(); + obj->set_name("PustyObiekt"); + + const std::string path = writeTempProto(scene, "no_components.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_TRUE(result->getObjects()[0]->components.empty()); +} + +TEST(ParserComponentTest, BlinkComponentIsCreated) { + auto scene = buildSimpleScene("P", "Mrugacz", true, 2.0); + const std::string path = writeTempProto(scene, "blink_comp.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), 1u); + EXPECT_EQ(result->getObjects()[0]->components.size(), 1u); + EXPECT_NE(result->getObjects()[0]->components[0], nullptr); +} + +TEST(ParserComponentTest, BlinkComponentIsCorrectType) { + auto scene = buildSimpleScene("P", "Blinker", true, 3.0); + const std::string path = writeTempProto(scene, "blink_type.pb"); + + Parser parser; + auto result = parser.parse(path); + auto* raw = result->getObjects()[0]->components[0].get(); + EXPECT_NE(dynamic_cast(raw), nullptr); +} + +TEST(ParserComponentTest, UnknownComponentTypeIsIgnored) { + NeuronIDE::Scene scene; + scene.set_project_name("UnknownComp"); + auto* obj = scene.add_scene_objects(); + obj->set_name("Obiekt"); + obj->set_is_visible(true); + obj->add_components(); // pusty Component, brak oneof + + const std::string path = writeTempProto(scene, "unknown_comp.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_TRUE(result->getObjects()[0]->components.empty()); +} + +TEST(ParserComponentTest, DuplicateComponentTypeThrows) { + NeuronIDE::Scene scene; + scene.set_project_name("DupComp"); + auto* obj = scene.add_scene_objects(); + obj->set_name("Zduplikowany"); + obj->set_is_visible(true); + + obj->add_components()->mutable_blinker()->set_blink_frequency_hz(1.0); + obj->add_components()->mutable_blinker()->set_blink_frequency_hz(2.0); + + const std::string path = writeTempProto(scene, "dup_comp.pb"); + + Parser parser; + EXPECT_THROW(parser.parse(path), std::runtime_error); +} + +TEST(ParserComponentTest, MultipleObjectsEachHaveTheirOwnComponents) { + NeuronIDE::Scene scene; + scene.set_project_name("IndepComp"); + for (int i = 0; i < 3; ++i) { + auto* obj = scene.add_scene_objects(); + obj->set_name("Obj" + std::to_string(i)); + obj->set_is_visible(true); + obj->add_components()->mutable_blinker()->set_blink_frequency_hz(static_cast(i + 1)); + } + const std::string path = writeTempProto(scene, "indep_comp.pb"); + + Parser parser; + auto result = parser.parse(path); + for (const auto& obj : result->getObjects()) { + EXPECT_EQ(obj->components.size(), 1u); + } +} + + +// Grupa: Parser -- scenariusze brzegowe +TEST(ParserEdgeCaseTest, ObjectWithEmptyNameIsParsed) { + NeuronIDE::Scene scene; + scene.set_project_name("EmptyObjName"); + auto* obj = scene.add_scene_objects(); + obj->set_name(""); + obj->set_is_visible(true); + + const std::string path = writeTempProto(scene, "empty_obj_name.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), 1u); + EXPECT_EQ(result->getObjects()[0]->name, ""); +} + +TEST(ParserEdgeCaseTest, LargeNumberOfObjectsIsHandled) { + NeuronIDE::Scene scene; + scene.set_project_name("LargeScene"); + const int N = 500; + for (int i = 0; i < N; ++i) { + auto* obj = scene.add_scene_objects(); + obj->set_name("O" + std::to_string(i)); + obj->set_is_visible(i % 2 == 0); + obj->add_components()->mutable_blinker()->set_blink_frequency_hz(static_cast(i)); + } + const std::string path = writeTempProto(scene, "large_scene.pb"); + + Parser parser; + auto result = parser.parse(path); + EXPECT_EQ(static_cast(result->getObjects().size()), N); +} + +TEST(ParserEdgeCaseTest, BlinkFrequencyZeroIsValid) { + auto scene = buildSimpleScene("P", "ZeroHz", true, 0.0); + const std::string path = writeTempProto(scene, "zero_freq.pb"); + + Parser parser; + auto result = parser.parse(path); + ASSERT_EQ(result->getObjects().size(), 1u); + EXPECT_EQ(result->getObjects()[0]->components.size(), 1u); +} + +TEST(ParserEdgeCaseTest, ParseReturnsDifferentObjectEachCall) { + auto scene = buildSimpleScene(); + const std::string path = writeTempProto(scene, "two_calls.pb"); + + Parser parser; + auto r1 = parser.parse(path); + auto r2 = parser.parse(path); + EXPECT_NE(r1.get(), r2.get()); +} \ No newline at end of file From dfdaa9d7fecfada89eecd939ee083999ef1071f3 Mon Sep 17 00:00:00 2001 From: Wojciech Piwocha Date: Mon, 22 Jun 2026 08:53:57 +0200 Subject: [PATCH 2/3] fix: used clang-format on file --- tests/unit_tests/parser_unit_test.cpp | 53 ++++++++++++--------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/tests/unit_tests/parser_unit_test.cpp b/tests/unit_tests/parser_unit_test.cpp index ddab6e1..e97ce60 100644 --- a/tests/unit_tests/parser_unit_test.cpp +++ b/tests/unit_tests/parser_unit_test.cpp @@ -22,14 +22,14 @@ std::string writeTempProto(const NeuronIDE::Scene& scene, const std::string& fil return path; } -NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProject", - const std::string& objectName = "ObiektA", - bool isVisible = true, - double blinkFrequency = 1.5) { - NeuronIDE::Scene scene; - scene.set_project_name(projectName); +NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProject", + const std::string& objectName = "ObiektA", bool isVisible = true, + double blinkFrequency = 1.5) { + NeuronIDE::Scene scene; + scene.set_project_name(projectName + ); - auto* obj = scene.add_scene_objects(); + auto* obj = scene.add_scene_objects(); obj->set_name(objectName); obj->set_is_visible(isVisible); @@ -42,16 +42,14 @@ NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProje } // namespace - // Grupa: Parser -- otwieranie pliku TEST(ParserFileTest, ThrowsWhenFileDoesNotExist) { Parser parser; EXPECT_THROW(parser.parse("/nonexistent/path/scene.pb"), std::runtime_error); } - TEST(ParserFileTest, ReturnsNonNullSceneForValidFile) { - auto scene = buildSimpleScene(); + auto scene = buildSimpleScene(); const std::string path = writeTempProto(scene, "valid_scene.pb"); Parser parser; @@ -59,10 +57,9 @@ TEST(ParserFileTest, ReturnsNonNullSceneForValidFile) { ASSERT_NE(result, nullptr); } - // Grupa: Parser -- nazwa projektu (Scene.project_name) TEST(ParserSceneNameTest, SetsProjectName) { - auto scene = buildSimpleScene("MojProjekt"); + auto scene = buildSimpleScene("MojProjekt"); const std::string path = writeTempProto(scene, "name_test.pb"); Parser parser; @@ -71,7 +68,7 @@ TEST(ParserSceneNameTest, SetsProjectName) { } TEST(ParserSceneNameTest, EmptyProjectNameIsPreserved) { - auto scene = buildSimpleScene(""); + auto scene = buildSimpleScene(""); const std::string path = writeTempProto(scene, "empty_name.pb"); Parser parser; @@ -79,7 +76,6 @@ TEST(ParserSceneNameTest, EmptyProjectNameIsPreserved) { EXPECT_EQ(result->getExperimentName(), ""); } - // Grupa: Parser -- liczba obiektow sceny TEST(ParserObjectCountTest, EmptySceneHasNoObjects) { NeuronIDE::Scene protoScene; @@ -91,7 +87,7 @@ TEST(ParserObjectCountTest, EmptySceneHasNoObjects) { } TEST(ParserObjectCountTest, SingleObjectIsLoaded) { - auto scene = buildSimpleScene("P", "Obj1"); + auto scene = buildSimpleScene("P", "Obj1"); const std::string path = writeTempProto(scene, "one_object.pb"); Parser parser; @@ -116,10 +112,9 @@ TEST(ParserObjectCountTest, MultipleObjectsAreAllLoaded) { EXPECT_EQ(result->getObjects().size(), 5u); } - // Grupa: Parser -- atrybuty SceneObject TEST(ParserSceneObjectTest, ObjectNameIsCorrect) { - auto scene = buildSimpleScene("P", "MojaRakieta"); + auto scene = buildSimpleScene("P", "MojaRakieta"); const std::string path = writeTempProto(scene, "obj_name.pb"); Parser parser; @@ -128,7 +123,7 @@ TEST(ParserSceneObjectTest, ObjectNameIsCorrect) { } TEST(ParserSceneObjectTest, ObjectIsVisibleWhenTrue) { - auto scene = buildSimpleScene("P", "Obj", true); + auto scene = buildSimpleScene("P", "Obj", true); const std::string path = writeTempProto(scene, "obj_visible_true.pb"); Parser parser; @@ -137,7 +132,7 @@ TEST(ParserSceneObjectTest, ObjectIsVisibleWhenTrue) { } TEST(ParserSceneObjectTest, ObjectIsHiddenWhenFalse) { - auto scene = buildSimpleScene("P", "Obj", false); + auto scene = buildSimpleScene("P", "Obj", false); const std::string path = writeTempProto(scene, "obj_visible_false.pb"); Parser parser; @@ -163,7 +158,6 @@ TEST(ParserSceneObjectTest, ObjectsPreserveInsertionOrder) { } } - // Grupa: Parser -- Transform TEST(ParserTransformTest, TransformFieldsAreParsedCorrectly) { NeuronIDE::Scene scene; @@ -227,15 +221,14 @@ TEST(ParserTransformTest, NegativeTransformValuesAreAccepted) { const std::string path = writeTempProto(scene, "neg_transform.pb"); - Parser parser; - auto result = parser.parse(path); - const auto& t = result->getObjects()[0]->transform; + Parser parser; + auto result = parser.parse(path); + const auto& t = result->getObjects()[0]->transform; EXPECT_DOUBLE_EQ(t.posX, -50.0); EXPECT_DOUBLE_EQ(t.posY, -100.0); EXPECT_DOUBLE_EQ(t.rotation, -90.0); } - // Grupa: Parser -- komponenty (BlinkComponent) TEST(ParserComponentTest, ObjectWithNoComponentsHasEmptyComponentList) { NeuronIDE::Scene scene; @@ -251,7 +244,7 @@ TEST(ParserComponentTest, ObjectWithNoComponentsHasEmptyComponentList) { } TEST(ParserComponentTest, BlinkComponentIsCreated) { - auto scene = buildSimpleScene("P", "Mrugacz", true, 2.0); + auto scene = buildSimpleScene("P", "Mrugacz", true, 2.0); const std::string path = writeTempProto(scene, "blink_comp.pb"); Parser parser; @@ -262,7 +255,7 @@ TEST(ParserComponentTest, BlinkComponentIsCreated) { } TEST(ParserComponentTest, BlinkComponentIsCorrectType) { - auto scene = buildSimpleScene("P", "Blinker", true, 3.0); + auto scene = buildSimpleScene("P", "Blinker", true, 3.0); const std::string path = writeTempProto(scene, "blink_type.pb"); Parser parser; @@ -309,7 +302,8 @@ TEST(ParserComponentTest, MultipleObjectsEachHaveTheirOwnComponents) { auto* obj = scene.add_scene_objects(); obj->set_name("Obj" + std::to_string(i)); obj->set_is_visible(true); - obj->add_components()->mutable_blinker()->set_blink_frequency_hz(static_cast(i + 1)); + obj->add_components()->mutable_blinker()->set_blink_frequency_hz( + static_cast(i + 1)); } const std::string path = writeTempProto(scene, "indep_comp.pb"); @@ -320,7 +314,6 @@ TEST(ParserComponentTest, MultipleObjectsEachHaveTheirOwnComponents) { } } - // Grupa: Parser -- scenariusze brzegowe TEST(ParserEdgeCaseTest, ObjectWithEmptyNameIsParsed) { NeuronIDE::Scene scene; @@ -355,7 +348,7 @@ TEST(ParserEdgeCaseTest, LargeNumberOfObjectsIsHandled) { } TEST(ParserEdgeCaseTest, BlinkFrequencyZeroIsValid) { - auto scene = buildSimpleScene("P", "ZeroHz", true, 0.0); + auto scene = buildSimpleScene("P", "ZeroHz", true, 0.0); const std::string path = writeTempProto(scene, "zero_freq.pb"); Parser parser; @@ -365,7 +358,7 @@ TEST(ParserEdgeCaseTest, BlinkFrequencyZeroIsValid) { } TEST(ParserEdgeCaseTest, ParseReturnsDifferentObjectEachCall) { - auto scene = buildSimpleScene(); + auto scene = buildSimpleScene(); const std::string path = writeTempProto(scene, "two_calls.pb"); Parser parser; From 14ef6a79ed7729d3f5a9909ce19e35f82be14c3c Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 22 Jun 2026 12:21:10 +0200 Subject: [PATCH 3/3] refactor: use stringstream in tests instead of ifstream and move some tests to component_tests --- include/parser/Parser.hpp | 1 + src/parser/Parser.cpp | 17 +- tests/component_tests/CMakeLists.txt | 1 + .../component_tests/parser_component_test.cpp | 34 ++++ tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/parser_unit_test.cpp | 186 ++++-------------- tests/utils/ParserTestUtils.hpp | 40 ++++ 7 files changed, 133 insertions(+), 147 deletions(-) create mode 100644 tests/component_tests/parser_component_test.cpp create mode 100644 tests/utils/ParserTestUtils.hpp diff --git a/include/parser/Parser.hpp b/include/parser/Parser.hpp index 26bf0dd..7153a0c 100644 --- a/include/parser/Parser.hpp +++ b/include/parser/Parser.hpp @@ -19,6 +19,7 @@ class Parser { Parser() = default; std::shared_ptr parse(const std::string& filePath); + std::shared_ptr parseStream(std::istream& stream); private: static std::shared_ptr buildSceneObject(const NeuronIDE::SceneObject& protoObj); diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index 235dba1..54d68bf 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -11,15 +11,24 @@ #include "scene/components/ComponentRegistry.hpp" std::shared_ptr Parser::parse(const std::string& filePath) { - NeuronIDE::Scene protoScene; - std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Parser: cannot open file: " + filePath); } - if (!protoScene.ParseFromIstream(&file)) { - throw std::runtime_error("Parser: failed to parse protobuf from: " + filePath); + try { + return parseStream(file); + } catch (const std::runtime_error& e) { + throw std::runtime_error(std::string("Parser: failed to parse file ") + filePath + " - " + + e.what()); + } +} + +std::shared_ptr Parser::parseStream(std::istream& stream) { + NeuronIDE::Scene protoScene; + + if (!protoScene.ParseFromIstream(&stream)) { + throw std::runtime_error("Parser: failed to parse protobuf from stream"); } auto scene = std::make_shared<::Scene>(); diff --git a/tests/component_tests/CMakeLists.txt b/tests/component_tests/CMakeLists.txt index 998cfe3..4ec7a3e 100644 --- a/tests/component_tests/CMakeLists.txt +++ b/tests/component_tests/CMakeLists.txt @@ -3,6 +3,7 @@ file(GLOB COMP_TEST_SOURCES "*.cpp") if(COMP_TEST_SOURCES) add_executable(component_tests ${COMP_TEST_SOURCES}) target_link_libraries(component_tests PRIVATE gtest_main runtime_core) + target_include_directories(component_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) gtest_discover_tests(component_tests PROPERTIES LABELS "component") endif() \ No newline at end of file diff --git a/tests/component_tests/parser_component_test.cpp b/tests/component_tests/parser_component_test.cpp new file mode 100644 index 0000000..5bc0020 --- /dev/null +++ b/tests/component_tests/parser_component_test.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include +#include +#include +#include + +#include "parser/Parser.hpp" +#include "scene/Scene.hpp" +#include "scene/SceneObject.hpp" +#include "scene/components/BlinkComponent.hpp" +#include "utils/ParserTestUtils.hpp" + +TEST(ParserFileTest, ThrowsWhenFileDoesNotExist) { + Parser parser; + EXPECT_THROW(parser.parse("/nonexistent/path/scene.pb"), std::runtime_error); +} + +TEST(ParserFileTest, ReturnsNonNullSceneForValidFile) { + auto scene = utils::buildSimpleScene(); + const std::string path = (std::filesystem::temp_directory_path() / "valid_scene.pb").string(); + { + std::ofstream out(path, std::ios::binary | std::ios::trunc); + ASSERT_TRUE(out.is_open()); + scene.SerializeToOstream(&out); + } + + Parser parser; + auto result = parser.parse(path); + ASSERT_NE(result, nullptr); + + std::filesystem::remove(path); +} \ No newline at end of file diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index cddb417..040706c 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -3,6 +3,7 @@ file(GLOB UNIT_TEST_SOURCES "*.cpp") if(UNIT_TEST_SOURCES) add_executable(unit_tests ${UNIT_TEST_SOURCES}) target_link_libraries(unit_tests PRIVATE gtest_main runtime_core) + target_include_directories(unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) gtest_discover_tests(unit_tests PROPERTIES LABELS "unit") endif() \ No newline at end of file diff --git a/tests/unit_tests/parser_unit_test.cpp b/tests/unit_tests/parser_unit_test.cpp index e97ce60..b425a4e 100644 --- a/tests/unit_tests/parser_unit_test.cpp +++ b/tests/unit_tests/parser_unit_test.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,88 +11,31 @@ #include "scene/Scene.hpp" #include "scene/SceneObject.hpp" #include "scene/components/BlinkComponent.hpp" - -// Pomocnicze funkcje do tworzenia tymczasowych plików .pb -namespace { - -std::string writeTempProto(const NeuronIDE::Scene& scene, const std::string& filename) { - const std::string path = (std::filesystem::temp_directory_path() / filename).string(); - std::ofstream out(path, std::ios::binary | std::ios::trunc); - EXPECT_TRUE(out.is_open()) << "Nie mozna otworzyc pliku tymczasowego: " << path; - scene.SerializeToOstream(&out); - return path; -} - -NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProject", - const std::string& objectName = "ObiektA", bool isVisible = true, - double blinkFrequency = 1.5) { - NeuronIDE::Scene scene; - scene.set_project_name(projectName - ); - - auto* obj = scene.add_scene_objects(); - obj->set_name(objectName); - obj->set_is_visible(isVisible); - - auto* comp = obj->add_components(); - auto* blinker = comp->mutable_blinker(); - blinker->set_blink_frequency_hz(blinkFrequency); - - return scene; -} - -} // namespace - -// Grupa: Parser -- otwieranie pliku -TEST(ParserFileTest, ThrowsWhenFileDoesNotExist) { - Parser parser; - EXPECT_THROW(parser.parse("/nonexistent/path/scene.pb"), std::runtime_error); -} - -TEST(ParserFileTest, ReturnsNonNullSceneForValidFile) { - auto scene = buildSimpleScene(); - const std::string path = writeTempProto(scene, "valid_scene.pb"); - - Parser parser; - auto result = parser.parse(path); - ASSERT_NE(result, nullptr); -} +#include "utils/ParserTestUtils.hpp" // Grupa: Parser -- nazwa projektu (Scene.project_name) TEST(ParserSceneNameTest, SetsProjectName) { - auto scene = buildSimpleScene("MojProjekt"); - const std::string path = writeTempProto(scene, "name_test.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("MojProjekt"); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(result->getExperimentName(), "MojProjekt"); } TEST(ParserSceneNameTest, EmptyProjectNameIsPreserved) { - auto scene = buildSimpleScene(""); - const std::string path = writeTempProto(scene, "empty_name.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene(""); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(result->getExperimentName(), ""); } // Grupa: Parser -- liczba obiektow sceny TEST(ParserObjectCountTest, EmptySceneHasNoObjects) { - NeuronIDE::Scene protoScene; - const std::string path = writeTempProto(protoScene, "no_objects.pb"); - - Parser parser; - auto result = parser.parse(path); + NeuronIDE::Scene protoScene; + auto result = utils::parseProtoScene(protoScene); EXPECT_TRUE(result->getObjects().empty()); } TEST(ParserObjectCountTest, SingleObjectIsLoaded) { - auto scene = buildSimpleScene("P", "Obj1"); - const std::string path = writeTempProto(scene, "one_object.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "Obj1"); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(result->getObjects().size(), 1u); } @@ -105,38 +49,26 @@ TEST(ParserObjectCountTest, MultipleObjectsAreAllLoaded) { auto* comp = obj->add_components(); comp->mutable_blinker()->set_blink_frequency_hz(static_cast(i)); } - const std::string path = writeTempProto(scene, "multi_objects.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(result->getObjects().size(), 5u); } // Grupa: Parser -- atrybuty SceneObject TEST(ParserSceneObjectTest, ObjectNameIsCorrect) { - auto scene = buildSimpleScene("P", "MojaRakieta"); - const std::string path = writeTempProto(scene, "obj_name.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "MojaRakieta"); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(result->getObjects()[0]->name, "MojaRakieta"); } TEST(ParserSceneObjectTest, ObjectIsVisibleWhenTrue) { - auto scene = buildSimpleScene("P", "Obj", true); - const std::string path = writeTempProto(scene, "obj_visible_true.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "Obj", true); + auto result = utils::parseProtoScene(scene); EXPECT_TRUE(result->getObjects()[0]->isVisible); } TEST(ParserSceneObjectTest, ObjectIsHiddenWhenFalse) { - auto scene = buildSimpleScene("P", "Obj", false); - const std::string path = writeTempProto(scene, "obj_visible_false.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "Obj", false); + auto result = utils::parseProtoScene(scene); EXPECT_FALSE(result->getObjects()[0]->isVisible); } @@ -148,10 +80,7 @@ TEST(ParserSceneObjectTest, ObjectsPreserveInsertionOrder) { auto* obj = scene.add_scene_objects(); obj->set_name(n); } - const std::string path = writeTempProto(scene, "order_test.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), names.size()); for (size_t i = 0; i < names.size(); ++i) { EXPECT_EQ(result->getObjects()[i]->name, names[i]); @@ -173,10 +102,7 @@ TEST(ParserTransformTest, TransformFieldsAreParsedCorrectly) { tra->set_height(128.0); tra->set_rotation(45.0); - const std::string path = writeTempProto(scene, "transform_test.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), 1u); const auto& t = result->getObjects()[0]->transform; @@ -194,10 +120,7 @@ TEST(ParserTransformTest, DefaultTransformIsZeroWhenNotProvided) { obj->set_name("Bezpozycyjny"); obj->set_is_visible(true); - const std::string path = writeTempProto(scene, "no_transform.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), 1u); const auto& t = result->getObjects()[0]->transform; @@ -219,10 +142,7 @@ TEST(ParserTransformTest, NegativeTransformValuesAreAccepted) { tra->set_y(-100.0); tra->set_rotation(-90.0); - const std::string path = writeTempProto(scene, "neg_transform.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); const auto& t = result->getObjects()[0]->transform; EXPECT_DOUBLE_EQ(t.posX, -50.0); EXPECT_DOUBLE_EQ(t.posY, -100.0); @@ -236,31 +156,22 @@ TEST(ParserComponentTest, ObjectWithNoComponentsHasEmptyComponentList) { auto* obj = scene.add_scene_objects(); obj->set_name("PustyObiekt"); - const std::string path = writeTempProto(scene, "no_components.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); EXPECT_TRUE(result->getObjects()[0]->components.empty()); } TEST(ParserComponentTest, BlinkComponentIsCreated) { - auto scene = buildSimpleScene("P", "Mrugacz", true, 2.0); - const std::string path = writeTempProto(scene, "blink_comp.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "Mrugacz", true, 2.0); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), 1u); EXPECT_EQ(result->getObjects()[0]->components.size(), 1u); EXPECT_NE(result->getObjects()[0]->components[0], nullptr); } TEST(ParserComponentTest, BlinkComponentIsCorrectType) { - auto scene = buildSimpleScene("P", "Blinker", true, 3.0); - const std::string path = writeTempProto(scene, "blink_type.pb"); - - Parser parser; - auto result = parser.parse(path); - auto* raw = result->getObjects()[0]->components[0].get(); + auto scene = utils::buildSimpleScene("P", "Blinker", true, 3.0); + auto result = utils::parseProtoScene(scene); + auto* raw = result->getObjects()[0]->components[0].get(); EXPECT_NE(dynamic_cast(raw), nullptr); } @@ -272,10 +183,7 @@ TEST(ParserComponentTest, UnknownComponentTypeIsIgnored) { obj->set_is_visible(true); obj->add_components(); // pusty Component, brak oneof - const std::string path = writeTempProto(scene, "unknown_comp.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); EXPECT_TRUE(result->getObjects()[0]->components.empty()); } @@ -289,10 +197,11 @@ TEST(ParserComponentTest, DuplicateComponentTypeThrows) { obj->add_components()->mutable_blinker()->set_blink_frequency_hz(1.0); obj->add_components()->mutable_blinker()->set_blink_frequency_hz(2.0); - const std::string path = writeTempProto(scene, "dup_comp.pb"); + std::stringstream ss; + scene.SerializeToOstream(&ss); Parser parser; - EXPECT_THROW(parser.parse(path), std::runtime_error); + EXPECT_THROW(parser.parseStream(ss), std::runtime_error); } TEST(ParserComponentTest, MultipleObjectsEachHaveTheirOwnComponents) { @@ -305,10 +214,7 @@ TEST(ParserComponentTest, MultipleObjectsEachHaveTheirOwnComponents) { obj->add_components()->mutable_blinker()->set_blink_frequency_hz( static_cast(i + 1)); } - const std::string path = writeTempProto(scene, "indep_comp.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); for (const auto& obj : result->getObjects()) { EXPECT_EQ(obj->components.size(), 1u); } @@ -322,10 +228,7 @@ TEST(ParserEdgeCaseTest, ObjectWithEmptyNameIsParsed) { obj->set_name(""); obj->set_is_visible(true); - const std::string path = writeTempProto(scene, "empty_obj_name.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), 1u); EXPECT_EQ(result->getObjects()[0]->name, ""); } @@ -340,29 +243,26 @@ TEST(ParserEdgeCaseTest, LargeNumberOfObjectsIsHandled) { obj->set_is_visible(i % 2 == 0); obj->add_components()->mutable_blinker()->set_blink_frequency_hz(static_cast(i)); } - const std::string path = writeTempProto(scene, "large_scene.pb"); - - Parser parser; - auto result = parser.parse(path); + auto result = utils::parseProtoScene(scene); EXPECT_EQ(static_cast(result->getObjects().size()), N); } TEST(ParserEdgeCaseTest, BlinkFrequencyZeroIsValid) { - auto scene = buildSimpleScene("P", "ZeroHz", true, 0.0); - const std::string path = writeTempProto(scene, "zero_freq.pb"); - - Parser parser; - auto result = parser.parse(path); + auto scene = utils::buildSimpleScene("P", "ZeroHz", true, 0.0); + auto result = utils::parseProtoScene(scene); ASSERT_EQ(result->getObjects().size(), 1u); EXPECT_EQ(result->getObjects()[0]->components.size(), 1u); } TEST(ParserEdgeCaseTest, ParseReturnsDifferentObjectEachCall) { - auto scene = buildSimpleScene(); - const std::string path = writeTempProto(scene, "two_calls.pb"); + auto scene = utils::buildSimpleScene(); + std::stringstream ss1; + scene.SerializeToOstream(&ss1); + std::stringstream ss2; + scene.SerializeToOstream(&ss2); Parser parser; - auto r1 = parser.parse(path); - auto r2 = parser.parse(path); + auto r1 = parser.parseStream(ss1); + auto r2 = parser.parseStream(ss2); EXPECT_NE(r1.get(), r2.get()); } \ No newline at end of file diff --git a/tests/utils/ParserTestUtils.hpp b/tests/utils/ParserTestUtils.hpp new file mode 100644 index 0000000..0f1c391 --- /dev/null +++ b/tests/utils/ParserTestUtils.hpp @@ -0,0 +1,40 @@ +#ifndef PARSER_TEST_UTILS_HPP +#define PARSER_TEST_UTILS_HPP + +#include +#include +#include + +#include "neuronide.pb.h" +#include "parser/Parser.hpp" +#include "scene/Scene.hpp" + +namespace utils { + +inline NeuronIDE::Scene buildSimpleScene(const std::string& projectName = "TestProject", + const std::string& objectName = "ObiektA", + bool isVisible = true, double blinkFrequency = 1.5) { + NeuronIDE::Scene scene; + scene.set_project_name(projectName); + + auto* obj = scene.add_scene_objects(); + obj->set_name(objectName); + obj->set_is_visible(isVisible); + + auto* comp = obj->add_components(); + auto* blinker = comp->mutable_blinker(); + blinker->set_blink_frequency_hz(blinkFrequency); + + return scene; +} + +inline std::shared_ptr parseProtoScene(const NeuronIDE::Scene& scene) { + std::stringstream ss; + scene.SerializeToOstream(&ss); + Parser parser; + return parser.parseStream(ss); +} + +} // namespace utils + +#endif // PARSER_TEST_UTILS_HPP