| //===- llvm/unittest/Telemetry/TelemetryTest.cpp - Telemetry unittests ---===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Telemetry/Telemetry.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Error.h" |
| #include "gtest/gtest.h" |
| #include <optional> |
| #include <vector> |
| |
| namespace llvm { |
| namespace telemetry { |
| // Testing parameters. |
| // |
| // These are set by each test to force certain outcomes. |
| struct TestContext { |
| // Controlling whether there is vendor plugin. In "real" implementation, the |
| // plugin-registration framework will handle the overrides but for tests, we |
| // just use a bool flag to decide which function to call. |
| bool HasVendorPlugin = false; |
| |
| // This field contains data emitted by the framework for later |
| // verification by the tests. |
| std::string Buffer = ""; |
| |
| // The expected Uuid generated by the fake tool. |
| std::string ExpectedUuid = ""; |
| }; |
| |
| class StringSerializer : public Serializer { |
| public: |
| const std::string &getString() { return Buffer; } |
| |
| Error init() override { |
| if (Started) |
| return createStringError("Serializer already in use"); |
| Started = true; |
| Buffer.clear(); |
| return Error::success(); |
| } |
| |
| void write(StringRef KeyName, bool Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, StringRef Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, int Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, long Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, long long Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, unsigned int Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, unsigned long Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void write(StringRef KeyName, unsigned long long Value) override { |
| writeHelper(KeyName, Value); |
| } |
| |
| void beginObject(StringRef KeyName) override { |
| Children.push_back(std::string("\n")); |
| ChildrenNames.push_back(KeyName.str()); |
| } |
| |
| void endObject() override { |
| assert(!Children.empty() && !ChildrenNames.empty()); |
| std::string ChildBuff = Children.back(); |
| std::string Name = ChildrenNames.back(); |
| Children.pop_back(); |
| ChildrenNames.pop_back(); |
| writeHelper(Name, ChildBuff); |
| } |
| |
| Error finalize() override { |
| assert(Children.empty() && ChildrenNames.empty()); |
| if (!Started) |
| return createStringError("Serializer not currently in use"); |
| Started = false; |
| return Error::success(); |
| } |
| |
| private: |
| template <typename T> void writeHelper(StringRef Name, T Value) { |
| assert(Started && "serializer not started"); |
| if (Children.empty()) |
| Buffer.append((Name + ":" + Twine(Value) + "\n").str()); |
| else |
| Children.back().append((Name + ":" + Twine(Value) + "\n").str()); |
| } |
| |
| bool Started = false; |
| std::string Buffer; |
| std::vector<std::string> Children; |
| std::vector<std::string> ChildrenNames; |
| }; |
| |
| namespace vendor { |
| struct VendorConfig : public Config { |
| VendorConfig(bool Enable) : Config(Enable) {} |
| std::optional<std::string> makeSessionId() override { |
| static int seed = 0; |
| return std::to_string(seed++); |
| } |
| }; |
| |
| std::shared_ptr<Config> getTelemetryConfig(const TestContext &Ctxt) { |
| return std::make_shared<VendorConfig>(/*EnableTelemetry=*/true); |
| } |
| |
| class TestStorageDestination : public Destination { |
| public: |
| TestStorageDestination(TestContext *Ctxt) : CurrentContext(Ctxt) {} |
| |
| Error receiveEntry(const TelemetryInfo *Entry) override { |
| if (Error Err = serializer.init()) |
| return Err; |
| |
| Entry->serialize(serializer); |
| if (Error Err = serializer.finalize()) |
| return Err; |
| |
| CurrentContext->Buffer.append(serializer.getString()); |
| return Error::success(); |
| } |
| |
| StringLiteral name() const override { return "TestDestination"; } |
| |
| private: |
| TestContext *CurrentContext; |
| StringSerializer serializer; |
| }; |
| |
| struct StartupInfo : public TelemetryInfo { |
| std::string ToolName; |
| std::map<std::string, std::string> MetaData; |
| |
| void serialize(Serializer &serializer) const override { |
| TelemetryInfo::serialize(serializer); |
| serializer.write("ToolName", ToolName); |
| serializer.write("MetaData", MetaData); |
| } |
| }; |
| |
| struct ExitInfo : public TelemetryInfo { |
| int ExitCode; |
| std::string ExitDesc; |
| void serialize(Serializer &serializer) const override { |
| TelemetryInfo::serialize(serializer); |
| serializer.write("ExitCode", ExitCode); |
| serializer.write("ExitDesc", ExitDesc); |
| } |
| }; |
| |
| class TestManager : public Manager { |
| public: |
| static std::unique_ptr<TestManager> |
| createInstance(Config *Config, TestContext *CurrentContext) { |
| if (!Config->EnableTelemetry) |
| return nullptr; |
| CurrentContext->ExpectedUuid = *(Config->makeSessionId()); |
| std::unique_ptr<TestManager> Ret = std::make_unique<TestManager>( |
| CurrentContext, CurrentContext->ExpectedUuid); |
| |
| // Add a destination. |
| Ret->addDestination( |
| std::make_unique<TestStorageDestination>(CurrentContext)); |
| |
| return Ret; |
| } |
| |
| TestManager(TestContext *Ctxt, std::string Id) |
| : CurrentContext(Ctxt), SessionId(Id) {} |
| |
| Error preDispatch(TelemetryInfo *Entry) override { |
| Entry->SessionId = SessionId; |
| (void)CurrentContext; |
| return Error::success(); |
| } |
| |
| std::string getSessionId() { return SessionId; } |
| |
| private: |
| TestContext *CurrentContext; |
| const std::string SessionId; |
| }; |
| } // namespace vendor |
| |
| std::shared_ptr<Config> getTelemetryConfig(const TestContext &Ctxt) { |
| if (Ctxt.HasVendorPlugin) |
| return vendor::getTelemetryConfig(Ctxt); |
| |
| return std::make_shared<Config>(false); |
| } |
| |
| #if LLVM_ENABLE_TELEMETRY |
| #define TELEMETRY_TEST(suite, test) TEST(suite, test) |
| #else |
| #define TELEMETRY_TEST(suite, test) TEST(DISABLED_##suite, test) |
| #endif |
| |
| TELEMETRY_TEST(TelemetryTest, TelemetryDisabled) { |
| TestContext Context; |
| Context.HasVendorPlugin = false; |
| |
| std::shared_ptr<Config> Config = getTelemetryConfig(Context); |
| auto Manager = vendor::TestManager::createInstance(Config.get(), &Context); |
| EXPECT_EQ(nullptr, Manager); |
| } |
| |
| TELEMETRY_TEST(TelemetryTest, TelemetryEnabled) { |
| const std::string ToolName = "TelemetryTestTool"; |
| |
| // Preset some params. |
| TestContext Context; |
| Context.HasVendorPlugin = true; |
| Context.Buffer.clear(); |
| |
| std::shared_ptr<Config> Config = getTelemetryConfig(Context); |
| auto Manager = vendor::TestManager::createInstance(Config.get(), &Context); |
| |
| EXPECT_STREQ(Manager->getSessionId().c_str(), Context.ExpectedUuid.c_str()); |
| |
| vendor::StartupInfo S; |
| S.ToolName = ToolName; |
| S.MetaData["a"] = "A"; |
| S.MetaData["b"] = "B"; |
| |
| Error startupEmitStatus = Manager->dispatch(&S); |
| EXPECT_FALSE(startupEmitStatus); |
| std::string ExpectedBuffer = |
| "SessionId:0\nToolName:TelemetryTestTool\nMetaData:\na:A\nb:B\n\n"; |
| EXPECT_EQ(ExpectedBuffer, Context.Buffer); |
| Context.Buffer.clear(); |
| |
| vendor::ExitInfo E; |
| E.ExitCode = 0; |
| E.ExitDesc = "success"; |
| Error exitEmitStatus = Manager->dispatch(&E); |
| EXPECT_FALSE(exitEmitStatus); |
| ExpectedBuffer = "SessionId:0\nExitCode:0\nExitDesc:success\n"; |
| EXPECT_EQ(ExpectedBuffer, Context.Buffer); |
| } |
| |
| } // namespace telemetry |
| } // namespace llvm |