|  | //===- 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 |