diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8eeff612..3788c534 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,25 +12,53 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - Checks: - runs-on: self-hosted + # This workflow contains a single job called "build" + checks: + # The type of runner that the job will run on + runs-on: ubuntu-latest timeout-minutes: 25 + + # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - name: setup environment - run: - sudo ./scripts/setup-all.sh - - name: Codestyle - run: - scripts/codestyle.sh - - name: AddressSanitizer + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - name: prepare environment + run: sudo apt install git && + git submodule update --init + + - name: checks run: - scripts/asan.sh - - name: ThreadSanitizer + docker run -v `pwd`:`pwd` -w `pwd` --cap-add=SYS_PTRACE -t rusdevops/bootstrap-cpp scripts/checks.sh + + + tests: + runs-on: ubuntu-latest + timeout-minutes: 25 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - name: prepare environment + run: sudo apt install git && + git submodule update --init + + - name: tests run: - scripts/tsan.sh - - name: Coverage + docker run -v `pwd`:`pwd` -w `pwd` -t rusdevops/bootstrap-cpp scripts/tests.sh + + + quality: + runs-on: ubuntu-latest + timeout-minutes: 25 + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - name: prepare environment + run: sudo apt install git && + git submodule update --init + + - name: quality run: - scripts/coverage.sh + docker run -v `pwd`:`pwd` -w `pwd` -t rusdevops/bootstrap-cpp scripts/coverage.sh diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 690fc79c..87d9beaa 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -18,7 +18,7 @@ jobs: - name: Build packages id: build_packages run: | - docker run -v `pwd`:`pwd` -w `pwd` --cap-add=SYS_PTRACE -t rusdevops/toolbox-cpp scripts/package.sh + docker run -v `pwd`:`pwd` -w `pwd` --cap-add=SYS_PTRACE -t rusdevops/bootstrap-cpp scripts/package.sh cd _artifacts DEB_PACKAGE_NAME=$(ls *.deb | tail -n 1) RPM_PACKAGE_NAME=$(ls *.rpm | tail -n 1) @@ -56,4 +56,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: _artifacts/${{ steps.build_packages.outputs.rpm_package_filename }} asset_name: ${{ steps.build_packages.outputs.rpm_package_filename }} - asset_content_type: application/zip + asset_content_type: application/zip \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index d882e34d..7655cf47 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ +[submodule "tools/polly"] + path = tools/polly + url = https://github.com/bmstu-iu8-cpp-sem-3/polly [submodule "tools/coverage"] path = tools/coverage url = https://github.com/bmstu-iu8-cpp-sem-3/infra-code-coverage diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..6960e91b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +os: linux +language: minimal +services: + - docker + +jobs: + include: + - name: "checks" + script: + - docker run -v `pwd`:`pwd` -w `pwd` --cap-add=SYS_PTRACE -t rusdevops/bootstrap-cpp scripts/checks.sh + - name: "tests" + script: + - docker run -v `pwd`:`pwd` -w `pwd` -t rusdevops/bootstrap-cpp scripts/tests.sh + - name: "quality" + script: + - docker run -v `pwd`:`pwd` -w `pwd` -t rusdevops/bootstrap-cpp scripts/coverage.sh + # The bot has gone... + # - docker run --env-file .env -v `pwd`:`pwd` -w `pwd` -t rusdevops/bootstrap-cpp scripts/duplication.sh + +notifications: + email: false + +branches: + - only: + - master diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f249565..71618f2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.12) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -26,20 +26,22 @@ string(APPEND CMAKE_CXX_FLAGS " -pedantic -Werror -Wall -Wextra") string(APPEND CMAKE_CXX_FLAGS " -Wno-unused-command-line-argument") string(APPEND CMAKE_CXX_FLAGS " -Wshadow -Wnon-virtual-dtor") -# new versions of cmake contain gtest search files -# commented out to increase the build speed. -# hunter_add_package(GTest) +hunter_add_package(GTest) find_package(GTest CONFIG REQUIRED) +hunter_add_package(nlohmann_json) +find_package(nlohmann_json CONFIG REQUIRED) add_library(${PROJECT_NAME} STATIC # enum your files and delete this comment - ${CMAKE_CURRENT_SOURCE_DIR}/sources/example.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/sources/source.cpp + include/Student.cpp include/Student.hpp) +target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json) + add_executable(demo ${CMAKE_CURRENT_SOURCE_DIR}/demo/main.cpp ) - +target_link_libraries(demo nlohmann_json::nlohmann_json) target_include_directories(${PROJECT_NAME} PUBLIC "$" "$" @@ -48,9 +50,11 @@ target_include_directories(${PROJECT_NAME} PUBLIC target_link_libraries(demo ${PROJECT_NAME}) if(BUILD_TESTS) + include_directories(${CMAKE_SOURCE_DIR}/jsonExamples) + add_definitions(-D_JSON_DIR="${CMAKE_SOURCE_DIR}/jsonExamples") add_executable(tests # TODO: enum your files and delete this comment - ${CMAKE_CURRENT_SOURCE_DIR}/tests/example.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test.cpp ) target_link_libraries(tests ${PROJECT_NAME} GTest::gtest_main) enable_testing() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 332b0c14..91f2a10b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,7 @@ export LAB_KEYWORD=yyyyyyy export LAB_NAME=lab-${LAB_NUMBER}-${LAB_KEYWORD} git clone https://github.com/bmstu-iu8-cpp-sem-3/${LAB_NAME} cd ${LAB_NAME} +git remote rename origin template git remote add template https://github.com/bmstu-iu8-cpp-sem-3/lab-xx-template.git git fetch template master git branch -f template template/master diff --git a/Dockerfile b/Dockerfile index 506f5caa..cfaf739e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM ubuntu:21.04 -LABEL version="0.1" +# rusdevops/bootstrap-cpp image +FROM ubuntu:20.04 LABEL maintainer="rusdevops@gmail.com" ENV DEBIAN_FRONTEND noninteractive RUN apt -y update && \ - apt -y install clang g++ cmake libgtest-dev libgmock-dev git \ - python3 python3-pip gcovr lcov doxygen && \ - python3 -m pip install cpplint requests gitpython && \ + apt -y install software-properties-common doxygen rpm g++-7 curl llvm g++ lcov gcovr cmake python3-pip clang git && \ + add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + pip3 install cpplint gitpython requests && \ apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/LICENSE b/LICENSE index 450ee339..a39ce46d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2021 The ISC Authors. +Copyright (c) 2016-2020 The ISC Authors. All rights reserved. diff --git a/README.md b/README.md index 1afae16e..641c3b81 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,10 @@ using nlohmann::json; void from_json(const json& j, student_t& s) { - s.name = get_name(j.at("group")); + s.name = get_name(j.at("name")); s.group = get_group(j.at("group")); s.avg = get_avg(j.at("avg")); - s.debt = get_group(j.at("debt")); + s.debt = get_debt(j.at("debt")); } auto get_name(const json& j) -> std::string { @@ -128,7 +128,7 @@ auto get_avg(const json& j) -> std::any { auto get_group(const json& j) -> std::any { if (j.is_string()) - return = j.get(); + return j.get(); else return j.get(); } diff --git a/demo/main.cpp b/demo/main.cpp index c30d9733..e0eb204d 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,5 +1,8 @@ -#include +#include "header.hpp" -int main() { - example(); -} \ No newline at end of file +int main(int argc, char** argv) { + auto data = getJSON(argc, argv); + auto students = parseJSON(data); + print(students, std::cout); + return 0; +} diff --git a/include/Student.cpp b/include/Student.cpp new file mode 100644 index 00000000..d5a48397 --- /dev/null +++ b/include/Student.cpp @@ -0,0 +1,125 @@ +#include "header.hpp" +#include +#include + +Student::Student(std::string _name, std::any _group, + std::any _avg, std::any _debt) { + name = std::move(_name); + group = std::move(_group); + avg = std::move(_avg); + debt = std::move(_debt); +} +bool anyCompare(std::any a1, std::any a2) +{ + if (a1.type() != a2.type()) + return false; + if (a1.type() == typeid(std::string)) + return std::any_cast(a1)== std::any_cast(a2); + if (a1.type() == typeid(nullptr)) + return true; + if (a1.type() == typeid(double)) + return std::any_cast(a1) == std::any_cast(a2); + if (a1.type() == typeid(size_t)) + return std::any_cast(a1) == std::any_cast(a2); + if (a1.type() == typeid(std::vector)) + return + std::any_cast>(a1) + == std::any_cast>(a2); + return false; +} +bool Student::operator==(const Student& student) const +{ + bool n = name == student.name; + bool g = anyCompare(group, student.group); + bool a = anyCompare(avg, student.avg); + bool d = anyCompare(debt, student.debt); + return n && g && a && d; +} + +Student::Student() = default; + +auto get_name(const json& j) -> std::string { + return j.get(); +} +auto get_debt(const json& j) -> std::any { + if (j.is_null()) + return nullptr; + else if (j.is_string()) + return j.get(); + else + return j.get>(); +} + +auto get_avg(const json& j) -> std::any { + if (j.is_null()) + return nullptr; + else if (j.is_string()) + return j.get(); + else if (j.is_number_float()) + return j.get(); + else + return j.get(); +} + +auto get_group(const json& j) -> std::any { + if (j.is_string()) + return j.get(); + else + return j.get(); +} +void from_json(const json& j, Student& s) { + s.name = get_name(j.at("name")); + s.group = get_group(j.at("group")); + s.avg = get_avg(j.at("avg")); + s.debt = get_debt(j.at("debt")); +} +std::string toString(std::any& item) +{ + std::stringstream ss; + if (item.type() == typeid(std::nullptr_t)) + ss << "null"; + else if (item.type() == typeid(std::string)) + ss << std::any_cast(item); + else if (item.type() == typeid(double)) + ss << std::any_cast(item); + else if (item.type() == typeid(std::vector)) + ss << std::any_cast >(item).size(); + else if (item.type() == typeid(size_t)) + ss << std::any_cast(item); + else ss << "unknown"; + return ss.str(); +} +void print(std::string s1, std::string s2, + std::string s3, std::string s4, std::ostream& os) +{ + os << "| " << std::setw(20) << std::left << s1 << "| " + << std::setw(10) << std::left << s2 << "| " + << std::setw(10) << std::left << s3 << "| " + << std::setw(10) << std::left << s4 << "|\n" + << "|---------------------|-----------|-----------|-----------|\n"; +} +void print(Student& student, std::ostream& os) +{ + print(student.name, + toString(student.group), + toString(student.avg), + toString(student.debt), os); +} +void print(std::vector& students, std::ostream& os) +{ + print("name", "group", "avg", "debt", os); + for (Student& student : students) { + print(student, os); + } +} +std::vector parseJSON(json& data) +{ + std::vector students; + for (auto const& item : data.at("items")) + { + Student student1; + from_json(item, student1); + students.push_back(student1); + } + return students; +} diff --git a/include/header.hpp b/include/header.hpp new file mode 100644 index 00000000..fb4f28a8 --- /dev/null +++ b/include/header.hpp @@ -0,0 +1,38 @@ +#ifndef INCLUDE_HEADER_HPP_ +#define INCLUDE_HEADER_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +using nlohmann::json; + +// Объявления, связанные с JSON +json getJSON(int argc, char** argv); + +// Объявления, связанные со Student +struct Student { + public: + Student(); + Student(std::string name, std::any group, std::any avg, std::any debt); + bool operator==(const Student& student) const; + + std::string name; + std::any group; + std::any avg; + std::any debt; +}; + +void from_json(const json& j, Student& s); +void print(Student& student, std::ostream& os); +void print(std::string s1, std::string s2, std::string s3, std::string s4, std::ostream& os); +void print(std::vector& students, std::ostream& os); +std::vector parseJSON(json& data); + + +#endif // INCLUDE_HEADER_HPP_ + diff --git a/json/students.json b/json/students.json new file mode 100644 index 00000000..02ae0243 --- /dev/null +++ b/json/students.json @@ -0,0 +1,25 @@ +{ + "items": [ + { + "name": "Chuck Norris", + "group": "1", + "avg": "4.25", + "debt": null + }, + { + "name": "Jan Clode Van Damme", + "group": 31, + "avg": 4, + "debt": "C++" + }, + { + "name": "Terminator Ivanovich", + "group": "SM6-111", + "avg": 5, + "debt": "C++" + } + ], + "_meta": { + "count": 3 + } +} \ No newline at end of file diff --git a/scripts/checks.sh b/scripts/checks.sh index bbdaed06..11551c99 100755 --- a/scripts/checks.sh +++ b/scripts/checks.sh @@ -1,14 +1,24 @@ #!/usr/bin/env bash -pushd $(git rev-parse --show-toplevel) - set -e +declare -r FILTER=-build/c++11,-runtime/references,\ +-whitespace/braces,-whitespace/indent,-whitespace/comments,-build/include_order + +find ./include/ ./scripts/ ./sources/ -name "*.cpp" -or -name "*.hpp" -or -name ".h" | xargs cpplint --filter=$FILTER + export CTEST_OUTPUT_ON_FAILURE=true # address sanitizer -./scripts/asan.sh - +#CMAKE_LINKER_OPTS="-DCMAKE_EXE_LINKER='-fuse-ld=gold'" +CMAKE_CONFIG_OPTS="-DHUNTER_CONFIGURATION_TYPES=Debug -DCMAKE_BUILD_TYPE=Debug" +CMAKE_TOOLCHAIN_OPTS="-DCMAKE_TOOLCHAIN_FILE='$(pwd)/tools/polly/sanitize-address-cxx17-pic.cmake'" +CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS" +cmake -H. -B_builds/sanitize-address-cxx17 $CMAKE_OPTS +cmake --build _builds/sanitize-address-cxx17 +./_builds/sanitize-address-cxx17/tests # thread sanitizer -./scripts/tsan.sh - -popd +CMAKE_TOOLCHAIN_OPTS="-DCMAKE_TOOLCHAIN_FILE='$(pwd)/tools/polly/sanitize-thread-cxx17-pic.cmake'" +CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS" +cmake -H. -B_builds/sanitize-thread-cxx17 $CMAKE_OPTS +cmake --build _builds/sanitize-thread-cxx17 +./_builds/sanitize-thread-cxx17/tests diff --git a/scripts/coverage.sh b/scripts/coverage.sh index c1b2e532..1e58d155 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -1,11 +1,12 @@ #!/usr/bin/env bash -pushd $(git rev-parse --show-toplevel) - set -e +export GTEST_COLOR=1 export CTEST_OUTPUT_ON_FAILURE=true +CMAKE_LINKER_OPTS="-DCMAKE_EXE_LINKER='-fuse-ld=gold'" CMAKE_CONFIG_OPTS="-DHUNTER_CONFIGURATION_TYPES=Debug -DCMAKE_BUILD_TYPE=Debug" +CMAKE_TOOLCHAIN_OPTS="-DCMAKE_TOOLCHAIN_FILE='$(pwd)/tools/polly/gcc-pic-cxx17.cmake'" CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS" cmake -H. -B_builds $CMAKE_OPTS -DBUILD_COVERAGE=ON @@ -13,7 +14,3 @@ cmake --build _builds cmake --build _builds --target test cmake --build _builds --target gcov gcovr -r . - -rm -r _builds - -popd diff --git a/scripts/package.sh b/scripts/package.sh index 423a5dbb..996ffbae 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,22 +1,17 @@ #!/usr/bin/env bash -pushd $(git rev-parse --show-toplevel) - set -e CMAKE_LINKER_OPTS="-DCMAKE_EXE_LINKER='-fuse-ld=gold'" -CPACK_CONFIG_OPTS="-DCPACK_OUTPUT_FILE_PREFIX=_artifacts -DCPACK_GENERATOR=DEB" +CPACK_CONFIG_OPTS="-DCPACK_OUTPUT_FILE_PREFIX=$(pwd)/_artifacts -DCPACK_GENERATOR=DEB" CMAKE_CONFIG_OPTS="-DHUNTER_CONFIGURATION_TYPES=Release -DCMAKE_BUILD_TYPE=Release -DBUILD_DOCS=ON" -CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CPACK_CONFIG_OPTS" +CMAKE_TOOLCHAIN_OPTS="-DCMAKE_TOOLCHAIN_FILE='$(pwd)/tools/polly/gcc-pic-cxx17.cmake'" +CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS $CPACK_CONFIG_OPTS" cmake -H. -B_builds $CMAKE_OPTS cmake --build _builds --target docs cmake --build _builds --target package -CPACK_CONFIG_OPTS="-DCPACK_OUTPUT_FILE_PREFIX=_artifacts -DCPACK_GENERATOR=RPM" +CPACK_CONFIG_OPTS="-DCPACK_OUTPUT_FILE_PREFIX=$(pwd)/_artifacts -DCPACK_GENERATOR=RPM" CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS $CPACK_CONFIG_OPTS" cmake -H. -B_builds $CMAKE_OPTS cmake --build _builds --target package -# artifacts are left in _artifacts, and it should no be removed -rm -r _builds - -popd diff --git a/scripts/tests.sh b/scripts/tests.sh index f3eee27c..8d16d7d3 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -1,16 +1,11 @@ #!/usr/bin/env bash -pushd $(git rev-parse --show-toplevel) - set -e export GTEST_COLOR=1 CMAKE_LINKER_OPTS="-DCMAKE_EXE_LINKER='-fuse-ld=gold'" CMAKE_CONFIG_OPTS="-DHUNTER_CONFIGURATION_TYPES=Debug -DCMAKE_BUILD_TYPE=Debug" -CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS" +CMAKE_TOOLCHAIN_OPTS="-DCMAKE_TOOLCHAIN_FILE='$(pwd)/tools/polly/gcc-pic-cxx17.cmake'" +CMAKE_OPTS="$CMAKE_LINKER_OPTS $CMAKE_CONFIG_OPTS $CMAKE_TOOLCHAIN_OPTS" cmake -H. -B_builds $CMAKE_OPTS cmake --build _builds cmake --build _builds --target test -- ARGS="--verbose" - -rm -r _builds - -popd diff --git a/sources/source.cpp b/sources/source.cpp new file mode 100644 index 00000000..1e381e5b --- /dev/null +++ b/sources/source.cpp @@ -0,0 +1,31 @@ +#include + +json getJSON(int argc, char** argv) { + // Проверка на наличие аргумента с путем к файлу + if (argc < 2) { + throw std::runtime_error{"The file path was not passed"}; + } + + // Чтение JSON файла + std::ifstream jsonFile(argv[1]); + if (!jsonFile) { + throw std::runtime_error{"unable to open json: " + std::string(argv[1])}; + } + + // Парсинг JSON + json data = json::parse(jsonFile, nullptr, false); + if (data.is_discarded()) { + throw std::runtime_error{"Failed to parse JSON"}; + } + + // Проверка структуры JSON + if (!data.contains("items") || !data["items"].is_array()) { + throw std::runtime_error{"The 'items' array must be contained in the file"}; + } + if (!data.contains("_meta") || data["items"].size() != data["_meta"].value("count", 0u)) { + throw std::runtime_error{"_meta value does not match the array size"}; + } + + return data; +} + diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 00000000..622205ca --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,60 @@ +#include +#include "header.hpp" + +const char* kJsonData = R"({ + "items": [ + { + "name": "Chuck Norris", + "group": "1", + "avg": "4.25", + "debt": null + }, + { + "name": "Jan Clode Van Damme", + "group": 31, + "avg": 4, + "debt": "C++" + }, + { + "name": "Terminator Ivanovich", + "group": "SM6-111", + "avg": 5, + "debt": "C++" + } + ], + "_meta": { + "count": 3 + } +})"; + +// Тестовый класс +class ParseJSONTest : public ::testing::Test { + protected: + nlohmann::json json_data; + void SetUp() override { + json_data = nlohmann::json::parse(kJsonData); + } +}; + +TEST_F(ParseJSONTest, ParsesCorrectNumberOfStudents) { + auto students = parseJSON(json_data["items"]); + EXPECT_EQ(students.size(), 3u) << "Should have parsed 3 students."; +} + +TEST_F(ParseJSONTest, ParsesStudentsCorrectly) { + auto students = parseJSON(json_data["items"]); + EXPECT_EQ(students[0].name, "Chuck Norris") << "First student name should be Chuck Norris."; + EXPECT_EQ(students[0].group.type(), typeid(std::string)) << "Group should be of type string for first student."; + EXPECT_EQ(std::any_cast(students[0].group), "1") << "Group should be '1' for first student."; + EXPECT_TRUE(students[0].debt.type() == typeid(nullptr)) << "Debt should be null for first student."; + + EXPECT_EQ(students[1].name, "Jan Clode Van Damme") << "Second student name should be Jan Clode Van Damme."; + EXPECT_EQ(students[1].group.type(), typeid(int)) << "Group should be of type int for second student."; + EXPECT_EQ(std::any_cast(students[1].group), 31) << "Group should be 31 for second student."; + EXPECT_EQ(std::any_cast(students[1].debt), "C++") << "Debt should be 'C++' for second student."; + + EXPECT_EQ(students[2].name, "Terminator Ivanovich") << "Third student name should be Terminator Ivanovich."; + EXPECT_EQ(students[2].group.type(), typeid(std::string)) << "Group should be of type string for third student."; + EXPECT_EQ(std::any_cast(students[2].group), "SM6-111") << "Group should be 'SM6-111' for third student."; + EXPECT_EQ(std::any_cast(students[2].debt), "C++") << "Debt should be 'C++' for third student."; +}