diff options
author | Klaus Aehlig <klaus.aehlig@huawei.com> | 2022-02-22 17:03:21 +0100 |
---|---|---|
committer | Klaus Aehlig <klaus.aehlig@huawei.com> | 2022-02-22 17:03:21 +0100 |
commit | 619def44c1cca9f3cdf63544d5f24f2c7a7d9b77 (patch) | |
tree | 01868de723cb82c86842f33743fa7b14e24c1fa3 /test/buildtool/logging | |
download | justbuild-619def44c1cca9f3cdf63544d5f24f2c7a7d9b77.tar.gz |
Initial self-hosting commit
This is the initial version of our tool that is able to
build itself. In can be bootstrapped by
./bin/bootstrap.py
Co-authored-by: Oliver Reiche <oliver.reiche@huawei.com>
Co-authored-by: Victor Moreno <victor.moreno1@huawei.com>
Diffstat (limited to 'test/buildtool/logging')
-rw-r--r-- | test/buildtool/logging/TARGETS | 26 | ||||
-rw-r--r-- | test/buildtool/logging/log_sink_file.test.cpp | 99 | ||||
-rw-r--r-- | test/buildtool/logging/logger.test.cpp | 322 |
3 files changed, 447 insertions, 0 deletions
diff --git a/test/buildtool/logging/TARGETS b/test/buildtool/logging/TARGETS new file mode 100644 index 00000000..3eec70cc --- /dev/null +++ b/test/buildtool/logging/TARGETS @@ -0,0 +1,26 @@ +{ "logger": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["logger"] + , "srcs": ["logger.test.cpp"] + , "deps": + [ ["@", "catch2", "", "catch2"] + , ["test", "catch-main"] + , ["src/buildtool/logging", "logging"] + ] + , "stage": ["test", "buildtool", "logging"] + } +, "log_sink_file": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["log_sink_file"] + , "srcs": ["log_sink_file.test.cpp"] + , "deps": + [ ["@", "catch2", "", "catch2"] + , ["test", "catch-main"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/file_system", "file_system_manager"] + ] + , "stage": ["test", "buildtool", "logging"] + } +, "TESTS": + {"type": "install", "tainted": ["test"], "deps": ["log_sink_file", "logger"]} +}
\ No newline at end of file diff --git a/test/buildtool/logging/log_sink_file.test.cpp b/test/buildtool/logging/log_sink_file.test.cpp new file mode 100644 index 00000000..19c0bd7b --- /dev/null +++ b/test/buildtool/logging/log_sink_file.test.cpp @@ -0,0 +1,99 @@ +#include <fstream> +#include <thread> + +#include "catch2/catch.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/buildtool/logging/log_config.hpp" +#include "src/buildtool/logging/log_sink_cmdline.hpp" +#include "src/buildtool/logging/log_sink_file.hpp" + +[[nodiscard]] static auto NumberOfLines(std::filesystem::path const& file_path) + -> int { + std::ifstream file(file_path); + std::string line{}; + int number_of_lines{}; + while (std::getline(file, line)) { + ++number_of_lines; + } + return number_of_lines; +} + +[[nodiscard]] static auto GetLines(std::filesystem::path const& file_path) + -> std::vector<std::string> { + std::ifstream file(file_path); + std::string line{}; + std::vector<std::string> lines{}; + while (std::getline(file, line)) { + lines.push_back(line); + } + return lines; +} + +TEST_CASE("LogSinkFile", "[logging]") { + LogConfig::SetSinks({LogSinkCmdLine::CreateFactory(false /*no color*/)}); + + // cleanup + std::string filename{"test/test.log"}; + CHECK(FileSystemManager::RemoveFile(filename)); + REQUIRE(not FileSystemManager::IsFile(filename)); + + // create test log file + REQUIRE(FileSystemManager::WriteFile("somecontent\n", filename)); + REQUIRE(FileSystemManager::IsFile(filename)); + CHECK(NumberOfLines(filename) == 1); + + SECTION("Overwrite mode") { + LogSinkFile sink{filename, LogSinkFile::Mode::Overwrite}; + + sink.Emit(nullptr, LogLevel::Info, "first"); + sink.Emit(nullptr, LogLevel::Info, "second"); + sink.Emit(nullptr, LogLevel::Info, "third"); + + // read file and check line numbers + CHECK(NumberOfLines(filename) == 3); + } + + SECTION("Append mode") { + LogSinkFile sink{filename, LogSinkFile::Mode::Append}; + + sink.Emit(nullptr, LogLevel::Info, "first"); + sink.Emit(nullptr, LogLevel::Info, "second"); + sink.Emit(nullptr, LogLevel::Info, "third"); + + // read file and check line numbers + CHECK(NumberOfLines(filename) == 4); + } + + SECTION("Thread-safety") { + int const num_threads = 20; + LogSinkFile sink{filename, LogSinkFile::Mode::Append}; + + // start threads, each emitting a log message + std::vector<std::thread> threads{}; + for (int id{}; id < num_threads; ++id) { + threads.emplace_back( + [&](int tid) { + sink.Emit(nullptr, + LogLevel::Info, + "this is thread " + std::to_string(tid)); + }, + id); + } + + // wait for threads to finish + for (auto& thread : threads) { + thread.join(); + } + + // read file and check line numbers + auto lines = GetLines(filename); + CHECK(lines.size() == num_threads + 1); + + // check for corrupted content + using Catch::Matchers::Contains; + for (auto const& line : lines) { + CHECK_THAT(line, + Contains("somecontent") or Contains("this is thread ")); + } + } +} diff --git a/test/buildtool/logging/logger.test.cpp b/test/buildtool/logging/logger.test.cpp new file mode 100644 index 00000000..329b69b0 --- /dev/null +++ b/test/buildtool/logging/logger.test.cpp @@ -0,0 +1,322 @@ +#include <atomic> +#include <string> +#include <unordered_map> + +#include "catch2/catch.hpp" +#include "src/buildtool/logging/log_config.hpp" +#include "src/buildtool/logging/log_sink.hpp" +#include "src/buildtool/logging/logger.hpp" + +// Stores prints from test sink instances +class TestPrints { + public: + static void Print(int sink_id, std::string const& print) noexcept { + prints_[sink_id].push_back(print); + } + [[nodiscard]] static auto Read(int sink_id) noexcept + -> std::vector<std::string> { + return prints_[sink_id]; + } + + static void Clear() noexcept { + prints_.clear(); + counter_ = 0; + } + + static auto GetId() noexcept -> int { return counter_++; } + + private: + static inline std::atomic<int> counter_{}; + static inline std::unordered_map<int, std::vector<std::string>> prints_{}; +}; + +// Test sink, prints to TestPrints depending on its own instance id. +class LogSinkTest : public ILogSink { + public: + static auto CreateFactory() -> LogSinkFactory { + return [] { return std::make_shared<LogSinkTest>(); }; + } + + LogSinkTest() noexcept { id_ = TestPrints::GetId(); } + + void Emit(Logger const* logger, + LogLevel level, + std::string const& msg) const noexcept final { + auto prefix = LogLevelToString(level); + + if (logger != nullptr) { + prefix += " (" + logger->Name() + ")"; + } + + TestPrints::Print(id_, prefix + ": " + msg); + } + + private: + int id_{}; +}; + +class OneGlobalSinkFixture { + public: + OneGlobalSinkFixture() { + TestPrints::Clear(); + LogConfig::SetLogLimit(LogLevel::Info); + LogConfig::SetSinks({LogSinkTest::CreateFactory()}); + } +}; + +class TwoGlobalSinksFixture : public OneGlobalSinkFixture { + public: + TwoGlobalSinksFixture() { + LogConfig::AddSink(LogSinkTest::CreateFactory()); + } +}; + +TEST_CASE_METHOD(OneGlobalSinkFixture, + "Global static logger with one sink", + "[logger]") { + // logs should be forwarded to sink instance: 0 + int instance = 0; + + // create log outside of log limit + Logger::Log(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance).empty()); + + SECTION("create log within log limit") { + Logger::Log(LogLevel::Info, "second"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 1); + CHECK(prints[0] == "INFO: second"); + + SECTION("increase log limit create log within log limit") { + LogConfig::SetLogLimit(LogLevel::Trace); + Logger::Log(LogLevel::Trace, "third"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 2); + CHECK(prints[1] == "TRACE: third"); + + SECTION("log via lambda function") { + Logger::Log(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 3); + CHECK(prints[2] == "TRACE: forth"); + } + } + } +} + +TEST_CASE_METHOD(OneGlobalSinkFixture, + "Local named logger using one global sink", + "[logger]") { + // create logger with sink instances from global LogConfig + Logger logger("TestLogger"); + + // logs should be forwarded to same sink instance as before: 0 + int instance = 0; + + // create log outside of log limit + logger.Emit(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance).empty()); + + SECTION("create log within log limit") { + logger.Emit(LogLevel::Info, "second"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 1); + CHECK(prints[0] == "INFO (TestLogger): second"); + + SECTION("increase log limit create log within log limit") { + logger.SetLogLimit(LogLevel::Trace); + logger.Emit(LogLevel::Trace, "third"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 2); + CHECK(prints[1] == "TRACE (TestLogger): third"); + + SECTION("log via lambda function") { + logger.Emit(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 3); + CHECK(prints[2] == "TRACE (TestLogger): forth"); + } + } + } +} + +TEST_CASE_METHOD(OneGlobalSinkFixture, + "Local named logger with its own sink instance" + "[logger]") { + // create logger with separate sink instance + Logger logger("OwnSinkLogger", {LogSinkTest::CreateFactory()}); + + // logs should be forwarded to new sink instance: 1 + int instance = 1; + + // create log outside of log limit + logger.Emit(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance).empty()); + + SECTION("create log within log limit") { + logger.Emit(LogLevel::Info, "second"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 1); + CHECK(prints[0] == "INFO (OwnSinkLogger): second"); + + SECTION("increase log limit create log within log limit") { + logger.SetLogLimit(LogLevel::Trace); + logger.Emit(LogLevel::Trace, "third"); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 2); + CHECK(prints[1] == "TRACE (OwnSinkLogger): third"); + + SECTION("log via lambda function") { + logger.Emit(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints = TestPrints::Read(instance); + REQUIRE(prints.size() == 3); + CHECK(prints[2] == "TRACE (OwnSinkLogger): forth"); + } + } + } +} + +TEST_CASE_METHOD(TwoGlobalSinksFixture, + "Global static logger with two sinks", + "[logger]") { + // logs should be forwarded to sink instances: 0 and 1 + int instance1 = 0; + int instance2 = 1; + + // create log outside of log limit + Logger::Log(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance1).empty()); + CHECK(TestPrints::Read(instance2).empty()); + + SECTION("create log within log limit") { + Logger::Log(LogLevel::Info, "second"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 1); + REQUIRE(prints2.size() == 1); + CHECK(prints1[0] == "INFO: second"); + CHECK(prints2[0] == "INFO: second"); + + SECTION("increase log limit create log within log limit") { + LogConfig::SetLogLimit(LogLevel::Trace); + Logger::Log(LogLevel::Trace, "third"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 2); + REQUIRE(prints2.size() == 2); + CHECK(prints1[1] == "TRACE: third"); + CHECK(prints2[1] == "TRACE: third"); + + SECTION("log via lambda function") { + Logger::Log(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 3); + REQUIRE(prints2.size() == 3); + CHECK(prints1[2] == "TRACE: forth"); + CHECK(prints2[2] == "TRACE: forth"); + } + } + } +} + +TEST_CASE_METHOD(TwoGlobalSinksFixture, + "Local named logger using two global sinks", + "[logger]") { + // create logger with sink instances from global LogConfig + Logger logger("TestLogger"); + + // logs should be forwarded to same sink instances: 0 and 1 + int instance1 = 0; + int instance2 = 1; + + // create log outside of log limit + logger.Emit(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance1).empty()); + CHECK(TestPrints::Read(instance2).empty()); + + SECTION("create log within log limit") { + logger.Emit(LogLevel::Info, "second"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 1); + REQUIRE(prints2.size() == 1); + CHECK(prints1[0] == "INFO (TestLogger): second"); + CHECK(prints2[0] == "INFO (TestLogger): second"); + + SECTION("increase log limit create log within log limit") { + logger.SetLogLimit(LogLevel::Trace); + logger.Emit(LogLevel::Trace, "third"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 2); + REQUIRE(prints2.size() == 2); + CHECK(prints1[1] == "TRACE (TestLogger): third"); + CHECK(prints2[1] == "TRACE (TestLogger): third"); + + SECTION("log via lambda function") { + logger.Emit(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 3); + REQUIRE(prints2.size() == 3); + CHECK(prints1[2] == "TRACE (TestLogger): forth"); + CHECK(prints2[2] == "TRACE (TestLogger): forth"); + } + } + } +} + +TEST_CASE_METHOD(TwoGlobalSinksFixture, + "Local named logger with its own two sink instances", + "[logger]") { + // create logger with separate sink instances + Logger logger("OwnSinkLogger", + {LogSinkTest::CreateFactory(), LogSinkTest::CreateFactory()}); + + // logs should be forwarded to new sink instances: 2 and 3 + int instance1 = 2; + int instance2 = 3; + + // create log outside of log limit + logger.Emit(LogLevel::Trace, "first"); + CHECK(TestPrints::Read(instance1).empty()); + CHECK(TestPrints::Read(instance2).empty()); + + SECTION("create log within log limit") { + logger.Emit(LogLevel::Info, "second"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 1); + REQUIRE(prints2.size() == 1); + CHECK(prints1[0] == "INFO (OwnSinkLogger): second"); + CHECK(prints2[0] == "INFO (OwnSinkLogger): second"); + + SECTION("increase log limit create log within log limit") { + logger.SetLogLimit(LogLevel::Trace); + logger.Emit(LogLevel::Trace, "third"); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 2); + REQUIRE(prints2.size() == 2); + CHECK(prints1[1] == "TRACE (OwnSinkLogger): third"); + CHECK(prints2[1] == "TRACE (OwnSinkLogger): third"); + + SECTION("log via lambda function") { + logger.Emit(LogLevel::Trace, + [] { return std::string{"forth"}; }); + auto prints1 = TestPrints::Read(instance1); + auto prints2 = TestPrints::Read(instance2); + REQUIRE(prints1.size() == 3); + REQUIRE(prints2.size() == 3); + CHECK(prints1[2] == "TRACE (OwnSinkLogger): forth"); + CHECK(prints2[2] == "TRACE (OwnSinkLogger): forth"); + } + } + } +} |