Skip to content

File test_progress_tracker.cpp

File List > lib > utilities > tests > test_progress_tracker.cpp

Go to the documentation of this file

#include <gtest/gtest.h>

#include <fstream>
#include <string>
#include <vector>

#include "utilities/filesystem.hpp"
#include "utilities/progress_tracker.hpp"
#include "utilities/trace_recorder.hpp"

namespace {

void run_loop(ProgressTracker& tracker) {
  for (int i = 0; i < 10; i++) {
    ProgressTracker trackerA =
        tracker.subtracker(i / 10.0, (i + 1) / 10.0, "loop " + std::to_string(i));
  }
}

}  // namespace

class RecordingProgressBar : public ProgressBar {
  std::vector<double> m_updates;

 public:
  const std::vector<double>& updates() const { return m_updates; }

 protected:
  void update_progress(double progress) override {
    m_updates.push_back(progress);
    ProgressBar::update_progress(progress);
  }
};

TEST(ProgressTracker, ProgressTracker) {
  ProgressTracker tracker1;
  EXPECT_EQ(tracker1.proportion(), 0.0);

  tracker1.set_proportion(0.1);
  EXPECT_EQ(tracker1.proportion(), 0.1);

  {
    ProgressTracker trackerA(tracker1.subtracker(0.2, 0.6, "test A"));
    EXPECT_EQ(tracker1.proportion(), 0.2);

    trackerA.set_proportion(0.25);
    EXPECT_DOUBLE_EQ(tracker1.proportion(), 0.3);
  }

  EXPECT_DOUBLE_EQ(tracker1.proportion(), 0.6);

  {
    ProgressTracker trackerB = tracker1.subtracker(0.7, 0.9, "test B");

    trackerB.set_proportion(0.5);
    EXPECT_DOUBLE_EQ(tracker1.proportion(), 0.8);
  }

  {
    ProgressTracker trackerC = tracker1.subtracker(0.9, 0.9, "test C");

    trackerC.set_proportion(0.5);
    EXPECT_DOUBLE_EQ(tracker1.proportion(), 0.9);
  }

  EXPECT_DOUBLE_EQ(tracker1.proportion(), 0.9);
}

TEST(ProgressTracker, ForLoop) {
  ProgressTracker tracker1;
  EXPECT_EQ(tracker1.proportion(), 0.0);

  for (int i = 0; i < 10; i++) {
    ProgressTracker trackerA =
        tracker1.subtracker(i / 10.0, (i + 1) / 10.0, "loop " + std::to_string(i));
  }

  ProgressBar bar;
  ProgressTracker loop_tracker(&bar);
  run_loop(loop_tracker);
}

TEST(ProgressTracker, BackwardsSubtrackerUpdateFails) {
  ProgressTracker root;
  ProgressTracker child = root.subtracker(0.77, 0.78, "child");

  child.set_proportion(0.99);
  EXPECT_THROW(child.set_proportion(0.55), std::runtime_error);
}

TEST(ProgressTracker, ParallelRowProgressIsMonotonic) {
  RecordingProgressBar bar;
  ProgressTracker root(&bar);
  ProgressTracker map_tracker = root.subtracker(0.0, 1.0, "map");

  constexpr size_t ROWS = 512;
#pragma omp parallel for
  for (size_t i = 0; i < ROWS; ++i) {
    (void)i;
    map_tracker.report_parallel_progress(static_cast<double>(i + 1) / ROWS);
  }

  for (size_t i = 1; i < bar.updates().size(); ++i) {
    EXPECT_GE(bar.updates()[i], bar.updates()[i - 1]) << "progress went backwards at index " << i;
  }
  EXPECT_DOUBLE_EQ(root.proportion(), 1.0);
}

TEST(ProgressTracker, ProgressScope) {
  ProgressTracker root;
  ProgressTracker child = SUBTRACKER(0.2, 0.8, root);
  child.set_proportion(0.5);
  EXPECT_DOUBLE_EQ(root.proportion(), 0.5);
}

TEST(ProgressTracker, ParentSetProportionBlockedWhileChildActive) {
  ProgressTracker root;
  ProgressTracker child = root.subtracker(0.2, 0.6, "child scope");
  EXPECT_THROW(root.set_proportion(0.3), std::runtime_error);
  child.set_proportion(1.0);
}

TEST(ProgressTracker, StartTrackerBeginsDeferredSubtracker) {
  ProgressTracker root;
  ProgressTracker child = SUBTRACKER(0.2, 0.8, root);
  START_TRACKER(child, "reading test.gpkg");
  child.set_proportion(0.5);
  EXPECT_DOUBLE_EQ(root.proportion(), 0.5);
}

TEST(ProgressTracker, StartTrackerNamesRoot) {
  const fs::path path = fs::temp_directory_path() / "blaze_scope_stack_test.json";
  blaze::trace::RecordTrace trace(path);
  ProgressTracker root;
  START_TRACKER(root, "using 8 threads for processing");
  const std::string stack = blaze::trace::format_active_scopes();
  EXPECT_NE(stack.find("Using 8 threads for processing"), std::string::npos);
}

TEST(ProgressTracker, RootScopeCompletesBeforeTraceWrite) {
  const fs::path path = fs::temp_directory_path() / "blaze_progress_trace_test.json";
  {
    blaze::trace::RecordTrace trace(path);
    ProgressTracker root;
    START_TRACKER(root, "using 8 threads for processing");
  }

  std::ifstream in(path);
  ASSERT_TRUE(in.is_open());
  const std::string json((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());

  size_t begin_count = 0;
  size_t end_count = 0;
  for (size_t pos = 0; (pos = json.find("\"ph\":\"B\"", pos)) != std::string::npos; ++pos) {
    ++begin_count;
  }
  for (size_t pos = 0; (pos = json.find("\"ph\":\"E\"", pos)) != std::string::npos; ++pos) {
    ++end_count;
  }
  EXPECT_EQ(begin_count, end_count);
  EXPECT_GE(begin_count, 1u);
  EXPECT_NE(json.find("\"name\":\"Using 8 threads for processing\""), std::string::npos);

  std::error_code ec;
  fs::remove(path, ec);
}

TEST(ProgressTracker, StartTrackerPatchesCallSiteInTrace) {
  const fs::path path = fs::temp_directory_path() / "blaze_callsite_trace_test.json";
  {
    blaze::trace::RecordTrace trace(path);
    ProgressTracker root;
    ProgressTracker child = SUBTRACKER(0.0, 1.0, root);
    START_TRACKER(child, "reading test.gpkg");
    child.set_proportion(1.0);
  }

  std::ifstream in(path);
  ASSERT_TRUE(in.is_open());
  const std::string json((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
  EXPECT_NE(json.find("\"name\":\"Reading test.gpkg\""), std::string::npos);
  EXPECT_NE(json.find("\"call_function\""), std::string::npos);
  EXPECT_NE(json.find("StartTrackerPatchesCallSiteInTrace"), std::string::npos);

  std::error_code ec;
  fs::remove(path, ec);
}

TEST(ProgressTracker, StartTrackerRecordsCallSiteInScopeStack) {
  const fs::path path = fs::temp_directory_path() / "blaze_scope_stack_callsite_test.json";
  blaze::trace::RecordTrace trace(path);
  ProgressTracker root;
  ProgressTracker child = SUBTRACKER(0.0, 1.0, root);
  START_TRACKER(child, "reading test.gpkg");
  const std::string stack = blaze::trace::format_active_scopes();
  EXPECT_NE(stack.find("Reading test.gpkg"), std::string::npos);
  EXPECT_NE(stack.find("called from"), std::string::npos);
  EXPECT_NE(stack.find("StartTrackerRecordsCallSiteInScopeStack"), std::string::npos);
  child.set_proportion(1.0);
}

TEST(ProgressTracker, SiblingSubtrackersRequireSequentialLifetime) {
  ProgressTracker root;
  {
    ProgressTracker first = root.subtracker(0.0, 0.4, "first");
    first.set_proportion(1.0);
  }
  ProgressTracker second = root.subtracker(0.4, 0.8, "second");
  second.set_proportion(1.0);
  EXPECT_DOUBLE_EQ(root.proportion(), 0.8);
}

TEST(ProgressTracker, ProgressScopeStackOnFailure) {
  const fs::path path = fs::temp_directory_path() / "blaze_scope_stack_failure_test.json";
  blaze::trace::RecordTrace trace(path);
  ProgressTracker root;
  ProgressTracker child = root.subtracker(0.0, 1.0, "tracked scope");
  try {
    root.set_proportion(0.5);
    FAIL() << "expected set_proportion to fail";
  } catch (const std::runtime_error& error) {
    EXPECT_NE(std::string(error.what()).find("Tracked scope"), std::string::npos);
    EXPECT_NE(std::string(error.what()).find("Active progress scopes"), std::string::npos);
  }
  child.set_proportion(1.0);
}