text: #include "opentelemetry/ext/zpages/tracez_processor.h"
  
  #include <gtest/gtest.h>
  
  #include <thread>
  
  #include "opentelemetry/ext/zpages/threadsafe_span_data.h"
  #include "opentelemetry/nostd/span.h"
  #include "opentelemetry/sdk/resource/resource.h"
  #include "opentelemetry/sdk/trace/tracer.h"
  
  using namespace opentelemetry::sdk::trace;
  using namespace opentelemetry::ext::zpages;
  
  //////////////////////////////////// TEST HELPER FUNCTIONS //////////////////////////////
  
  /*
   * Helper function uses the current processor to update spans contained in completed_spans
   * and running_spans. completed_spans contains all spans (cumulative), unless marked otherwise
   */
  void UpdateSpans(std::shared_ptr<TracezSpanProcessor> &processor,
                   std::vector<std::unique_ptr<ThreadsafeSpanData>> &completed,
                   std::unordered_set<ThreadsafeSpanData *> &running,
                   bool store_only_new_completed = false)
  {
    auto spans = processor->GetSpanSnapshot();
    running    = spans.running;
    if (store_only_new_completed)
    {
      completed.clear();
      completed = std::move(spans.completed);
    }
    else
    {
      std::move(spans.completed.begin(), spans.completed.end(),
                std::inserter(completed, completed.end()));
    }
    spans.completed.clear();
  }
  
  /*
   * Returns true if all the span names in the name vector within the given range appears in
   * at least the same frequency as they do in running_spans.
   *
   * If no start value is given, start at index 0
   * If no end value is given, end at name vector end
   * If 1-1 correspondance marked, return true if completed has all names in same frequency,
   * no more or less
   */
  bool ContainsNames(const std::vector<std::string> &names,
                     std::unordered_set<ThreadsafeSpanData *> &running,
                     size_t name_start              = 0,
                     size_t name_end                = 0,
                     bool one_to_one_correspondence = false)
  {
    if (name_end == 0)
      name_end = names.size();
  
    size_t num_names = name_end - name_start;
  
    if (num_names > running.size() ||  // More names than spans, can't have all names
        (one_to_one_correspondence && num_names != running.size()))
    {
      return false;
    }
    std::vector<bool> is_contained(num_names, false);
  
    // Mark all names that are contained only once
    // in the order they appear
    for (auto &span : running)
    {
      for (unsigned int i = 0; i < num_names; i++)
      {
        if (span->GetName() == names[name_start + i] && !is_contained[i])
        {
          is_contained[i] = true;
          break;
        }
      }
    }
  
    for (auto &&b : is_contained)
      if (!b)
        return false;
  
    return true;
  }
  
  /*
   * Returns true if all the span names in the nam vector within the given range appears in
   * at least the same frequency as they do in completed_spans
   *
   * If no start value is given, start at index 0
   * If no end value is given, end at name vector end
   * If 1-1 correspondance marked, return true if completed has all names in same frequency,
   * no more or less
   */
  bool ContainsNames(const std::vector<std::string> &names,
                     std::vector<std::unique_ptr<ThreadsafeSpanData>> &completed,
                     size_t name_start              = 0,
                     size_t name_end                = 0,
                     bool one_to_one_correspondence = false)
  {
  
    if (name_end == 0)
      name_end = names.size();
  
    size_t num_names = name_end - name_start;
  
    if (num_names > completed.size() || (one_to_one_correspondence && num_names != completed.size()))
    {
      return false;
    }
    std::vector<bool> is_contained(num_names, false);
  
    for (auto &span : completed)
    {
      for (unsigned int i = 0; i < num_names; i++)
      {
        if (span->GetName() == names[name_start + i] && !is_contained[i])
        {
          is_contained[i] = true;
          break;
        }
      }
    }
  
    for (auto &&b : is_contained)
      if (!b)
        return false;
  
    return true;
  }
  
  /*
   * Helper function calls GetSpanSnapshot() i times and does nothing with it
   * otherwise. Used for testing thread safety
   */
  void GetManySnapshots(std::shared_ptr<TracezSpanProcessor> &processor, int i)
  {
    for (; i > 0; i--)
      processor->GetSpanSnapshot();
  }
  
  /*
   * Helper function that creates i spans, which are added into the passed
   * in vector. Used for testing thread safety
   */
  void StartManySpans(
      std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> &spans,
      std::shared_ptr<opentelemetry::trace::Tracer> tracer,
      int i)
  {
    for (; i > 0; i--)
      spans.push_back(tracer->StartSpan("span"));
  }
  
  /*
   * Helper function that ends all spans in the passed in span vector. Used
   * for testing thread safety
   */
  void EndAllSpans(std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> &spans)
  {
    for (auto &span : spans)
      span->End();
  }
  
  //////////////////////////////// TEST FIXTURE //////////////////////////////////////
  
  /*
   * Reduce code duplication by having single area with shared setup code
   */
  class TracezProcessor : public ::testing::Test
  {
  protected:
    void SetUp() override
    {
      processor     = std::shared_ptr<TracezSpanProcessor>(new TracezSpanProcessor());
      auto resource = opentelemetry::sdk::resource::Resource::Create({});
  
      tracer     = std::shared_ptr<opentelemetry::trace::Tracer>(new Tracer(processor, resource));
      auto spans = processor->GetSpanSnapshot();
      running    = spans.running;
      completed  = std::move(spans.completed);
  
      span_names = {"s0", "s2", "s1", "s1", "s"};
    }
  
    std::shared_ptr<TracezSpanProcessor> processor;
    std::shared_ptr<opentelemetry::trace::Tracer> tracer;
  
    std::vector<std::string> span_names;
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> span_vars;
  
    std::unordered_set<ThreadsafeSpanData *> running;
    std::vector<std::unique_ptr<ThreadsafeSpanData>> completed;
  };
  
  ///////////////////////////////////////// TESTS ///////////////////////////////////
  
  /*
   * Test if both span containers are empty when no spans exist or are added.
   * Ensures no rogue spans appear in the containers somehow.
   */
  TEST_F(TracezProcessor, NoSpans)
  {
    auto recordable = processor->MakeRecordable();
  
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 0);
  }
  
  /*
   * Test if a single span moves from running to completed at expected times.
   * All completed spans are stored. Ensures basic functionality and that accumulation
   * can happen
   */
  TEST_F(TracezProcessor, OneSpanCumulative)
  {
    auto span = tracer->StartSpan(span_names[0]);
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 1, true));
    EXPECT_EQ(running.size(), 1);
    EXPECT_EQ(completed.size(), 0);
  
    span->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true));
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 1);
  }
  
  /*
   * Test if multiple spans move from running to completed at  expected times. Check if
   * all are in a container, either running/completed during checks. Ensures basic functionality
   * and that accumulation can happen for many spans
   * All completed spans are stored.
   */
  TEST_F(TracezProcessor, MultipleSpansCumulative)
  {
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 0);
  
    // Start and store spans using span_names
    for (const auto &name : span_names)
      span_vars.push_back(tracer->StartSpan(name));
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), span_names.size());
    EXPECT_EQ(completed.size(), 0);
  
    // End all spans
    for (auto &span : span_vars)
      span->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, completed));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), span_names.size());
  }
  
  /*
   * Test if multiple spans move from running to completed at expected times,
   * running/completed spans are split. Middle spans end first. Ensures basic functionality
   * and that accumulation can happen for many spans even spans that start and end non-
   * sequentially. All completed spans are stored.
   */
  TEST_F(TracezProcessor, MultipleSpansMiddleSplitCumulative)
  {
    for (const auto &name : span_names)
      span_vars.push_back(tracer->StartSpan(name));
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), span_names.size());
    EXPECT_EQ(completed.size(), 0);
  
    // End 4th span
    span_vars[3]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 3));    // s0 s2 s1
    EXPECT_TRUE(ContainsNames(span_names, running, 4));       // + s
    EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4));  // s1
    EXPECT_EQ(running.size(), 4);
    EXPECT_EQ(completed.size(), 1);
  
    // End 2nd span
    span_vars[1]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 1));    // s0
    EXPECT_TRUE(ContainsNames(span_names, running, 2, 3));    // + s1
    EXPECT_TRUE(ContainsNames(span_names, running, 4));       // + s
    EXPECT_TRUE(ContainsNames(span_names, completed, 1, 2));  // s2
    EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4));  // s1
    EXPECT_EQ(running.size(), 3);
    EXPECT_EQ(completed.size(), 2);
  
    // End 3rd span (last middle span)
    span_vars[2]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 1));    // s0
    EXPECT_TRUE(ContainsNames(span_names, running, 4));       // + s
    EXPECT_TRUE(ContainsNames(span_names, completed, 1, 4));  // s2 s1 s1
    EXPECT_EQ(running.size(), 2);
    EXPECT_EQ(completed.size(), 3);
  
    // End remaining Spans
    span_vars[0]->End();
    span_vars[4]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, completed));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 5);
  }
  
  /*
   * Test if multiple spans move from running to completed at expected times,
   * running/completed spans are split.  Ensures basic functionality and that
   * accumulation can happen for many spans even spans that start and end non-
   * sequentially. All completed spans are stored.
   */
  TEST_F(TracezProcessor, MultipleSpansOuterSplitCumulative)
  {
    for (const auto &name : span_names)
      span_vars.push_back(tracer->StartSpan(name));
  
    // End last span
    span_vars[4]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 4));  // s0 s2 s1 s1
    EXPECT_TRUE(ContainsNames(span_names, completed, 4));   // s
    EXPECT_EQ(running.size(), 4);
    EXPECT_EQ(completed.size(), 1);
  
    // End first span
    span_vars[0]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 1, 4));    // s2 s1 s1
    EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1));  // s0
    EXPECT_TRUE(ContainsNames(span_names, completed, 4));     // s
    EXPECT_EQ(running.size(), 3);
    EXPECT_EQ(completed.size(), 2);
  
    // End remaining Spans
    for (int i = 1; i < 4; i++)
      span_vars[i]->End();
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, completed));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 5);
  }
  
  /*
   * Test if a single span moves from running to completed at expected times.
   * Ensure correct behavior even when spans are discarded. Only new completed
   * spans are stored.
   */
  TEST_F(TracezProcessor, OneSpanNewOnly)
  {
    auto span = tracer->StartSpan(span_names[0]);
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 1, true));
    EXPECT_EQ(running.size(), 1);
    EXPECT_EQ(completed.size(), 0);
  
    span->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true));
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 1);
  
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 0);
  }
  
  /*
   * Test if multiple spans move from running to completed at expected times,
   * running/completed spans are split. Middle spans end first. Ensure correct
   * behavior even when multiple spans are discarded, even when span starting and
   * ending is non-sequential. Only new completed spans are stored.
   */
  TEST_F(TracezProcessor, MultipleSpansMiddleSplitNewOnly)
  {
    for (const auto &name : span_names)
      span_vars.push_back(tracer->StartSpan(name));
    UpdateSpans(processor, completed, running);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 5, true));  // s0 s2 s1 s1 s
    EXPECT_EQ(running.size(), span_names.size());
    EXPECT_EQ(completed.size(), 0);
  
    // End 4th span
    span_vars[3]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 3));          // s0 s2 s1
    EXPECT_TRUE(ContainsNames(span_names, running, 4));             // + s
    EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4, true));  // s1
    EXPECT_EQ(running.size(), 4);
    EXPECT_EQ(completed.size(), 1);
  
    // End 2nd and 3rd span
    span_vars[1]->End();
    span_vars[2]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 1));          // s0
    EXPECT_TRUE(ContainsNames(span_names, running, 4));             // + s
    EXPECT_TRUE(ContainsNames(span_names, completed, 1, 3, true));  // s2 s1
    EXPECT_EQ(running.size(), 2);
    EXPECT_EQ(completed.size(), 2);
  
    // End remaining Spans
    span_vars[0]->End();
    span_vars[4]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1));  // s0
    EXPECT_TRUE(ContainsNames(span_names, completed, 4));     // s
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 2);
  
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 0);
  }
  
  /*
   * Test if multiple spans move from running to completed at expected times,
   * running/completed spans are split. Ensure correct behavior even when
   * multiple spans are discarded, even when span starting and ending is
   * non-sequential. Only new completed spans are stored.
   */
  TEST_F(TracezProcessor, MultipleSpansOuterSplitNewOnly)
  {
    for (const auto &name : span_names)
      span_vars.push_back(tracer->StartSpan(name));
  
    // End last span
    span_vars[4]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 0, 4, true));    // s0 s2 s1 s1
    EXPECT_TRUE(ContainsNames(span_names, completed, 4, 5, true));  // s
    EXPECT_EQ(running.size(), 4);
    EXPECT_EQ(completed.size(), 1);
  
    // End first span
    span_vars[0]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, running, 1, 4, true));    // s2 s1 s1
    EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true));  // s0
    EXPECT_EQ(running.size(), 3);
    EXPECT_EQ(completed.size(), 1);
  
    // End remaining middle spans
    for (int i = 1; i < 4; i++)
      span_vars[i]->End();
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_TRUE(ContainsNames(span_names, completed, 1, 4, true));  // s2 s1 s1
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 3);
  
    UpdateSpans(processor, completed, running, true);
  
    EXPECT_EQ(running.size(), 0);
    EXPECT_EQ(completed.size(), 0);
  }
  
  /*
   * Test for ForceFlush and Shutdown code coverage, which do nothing.
   */
  TEST_F(TracezProcessor, FlushShutdown)
  {
    auto pre_running_sz   = running.size();
    auto pre_completed_sz = completed.size();
  
    EXPECT_TRUE(processor->ForceFlush());
    EXPECT_TRUE(processor->Shutdown());
  
    UpdateSpans(processor, completed, running);
  
    EXPECT_EQ(pre_running_sz, running.size());
    EXPECT_EQ(pre_completed_sz, completed.size());
  }
  
  /*
   * Test for thread safety when many spans start at the same time.
   */
  TEST_F(TracezProcessor, RunningThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans1;
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans2;
  
    std::thread start1(StartManySpans, std::ref(spans1), tracer, 500);
    std::thread start2(StartManySpans, std::ref(spans2), tracer, 500);
  
    start1.join();
    start2.join();
  
    EndAllSpans(spans1);
    EndAllSpans(spans2);
  }
  
  /*
   * Test for thread safety when many spans end at the same time
   */
  TEST_F(TracezProcessor, CompletedThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans1;
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans2;
  
    StartManySpans(spans1, tracer, 500);
    StartManySpans(spans2, tracer, 500);
  
    std::thread end1(EndAllSpans, std::ref(spans1));
    std::thread end2(EndAllSpans, std::ref(spans2));
  
    end1.join();
    end2.join();
  }
  
  /*
   * Test for thread safety when many snapshots are grabbed at the same time.
   */
  TEST_F(TracezProcessor, SnapshotThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans;
  
    std::thread snap1(GetManySnapshots, std::ref(processor), 500);
    std::thread snap2(GetManySnapshots, std::ref(processor), 500);
  
    snap1.join();
    snap2.join();
  
    StartManySpans(spans, tracer, 500);
  
    std::thread snap3(GetManySnapshots, std::ref(processor), 500);
    std::thread snap4(GetManySnapshots, std::ref(processor), 500);
  
    snap3.join();
    snap4.join();
  
    EndAllSpans(spans);
  }
  
  /*
   * Test for thread safety when many spans start while others are ending.
   */
  TEST_F(TracezProcessor, RunningCompletedThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans1;
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans2;
  
    StartManySpans(spans1, tracer, 500);
  
    std::thread start(StartManySpans, std::ref(spans2), tracer, 500);
    std::thread end(EndAllSpans, std::ref(spans1));
  
    start.join();
    end.join();
  
    EndAllSpans(spans2);
  }
  
  /*
   * Test for thread safety when many span start while snapshots are being grabbed.
   */
  TEST_F(TracezProcessor, RunningSnapshotThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans;
  
    std::thread start(StartManySpans, std::ref(spans), tracer, 500);
    std::thread snapshots(GetManySnapshots, std::ref(processor), 500);
  
    start.join();
    snapshots.join();
  
    EndAllSpans(spans);
  }
  
  /*
   * Test for thread safety when many spans end while snapshots are being grabbed.
   */
  TEST_F(TracezProcessor, SnapshotCompletedThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans;
  
    StartManySpans(spans, tracer, 500);
  
    std::thread snapshots(GetManySnapshots, std::ref(processor), 500);
    std::thread end(EndAllSpans, std::ref(spans));
  
    snapshots.join();
    end.join();
  }
  
  /*
   * Test for thread safety when many spans start and end while snapshots are being grabbed.
   */
  TEST_F(TracezProcessor, RunningSnapshotCompletedThreadSafety)
  {
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans1;
    std::vector<opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>> spans2;
  
    StartManySpans(spans1, tracer, 500);
  
    std::thread start(StartManySpans, std::ref(spans2), tracer, 500);
    std::thread snapshots(GetManySnapshots, std::ref(processor), 500);
    std::thread end(EndAllSpans, std::ref(spans1));
  
    start.join();
    snapshots.join();
    end.join();
  
    EndAllSpans(spans2);
  }
  