slic3r_jobs_tests.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #include "catch2/catch.hpp"
  2. #include <chrono>
  3. #include <thread>
  4. #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
  5. #include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
  6. #include "slic3r/GUI/Jobs/ProgressIndicator.hpp"
  7. struct Progress: Slic3r::ProgressIndicator {
  8. int range = 100;
  9. int pr = 0;
  10. std::string statustxt;
  11. void set_range(int r) override { range = r; }
  12. void set_cancel_callback(CancelFn = CancelFn()) override {}
  13. void set_progress(int p) override { pr = p; }
  14. void set_status_text(const char *txt) override { statustxt = txt; }
  15. int get_range() const override { return range; }
  16. };
  17. using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >;
  18. TEMPLATE_LIST_TEST_CASE("Empty worker should not block when queried for idle", "[Jobs]", TestClasses) {
  19. TestType worker{std::make_unique<Progress>()};
  20. worker.wait_for_idle();
  21. REQUIRE(worker.is_idle());
  22. }
  23. TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
  24. TestType worker{std::make_unique<Progress>()};
  25. REQUIRE(worker.is_idle());
  26. worker.wait_for_current_job();
  27. worker.process_events();
  28. REQUIRE(worker.is_idle());
  29. }
  30. TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) {
  31. TestType worker{std::make_unique<Progress>()};
  32. worker.push(nullptr);
  33. REQUIRE(worker.is_idle());
  34. }
  35. TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) {
  36. using namespace Slic3r;
  37. using namespace Slic3r::GUI;
  38. TestType worker{std::make_unique<Progress>(), "worker_thread"};
  39. queue_job(worker, [&worker](Job::Ctl &ctl) {
  40. ctl.call_on_main_thread([&worker] {
  41. REQUIRE(!worker.is_idle());
  42. }).wait();
  43. });
  44. // make sure that the job starts BEFORE the worker.wait_for_idle() is called
  45. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  46. worker.wait_for_idle();
  47. REQUIRE(worker.is_idle());
  48. }
  49. TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) {
  50. using namespace Slic3r;
  51. using namespace Slic3r::GUI;
  52. auto pri = std::make_shared<Progress>();
  53. TestType worker{pri};
  54. queue_job(worker, [](Job::Ctl &ctl){
  55. for (int s = 0; s <= 100; ++s) {
  56. ctl.update_status(s, "Running");
  57. }
  58. });
  59. worker.wait_for_idle();
  60. REQUIRE(pri->pr == 100);
  61. REQUIRE(pri->statustxt == "Running");
  62. }
  63. TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) {
  64. using namespace Slic3r;
  65. using namespace Slic3r::GUI;
  66. auto pri = std::make_shared<Progress>();
  67. TestType worker{pri};
  68. queue_job(
  69. worker,
  70. [](Job::Ctl &ctl) {
  71. for (int s = 0; s <= 100; ++s) {
  72. std::this_thread::sleep_for(std::chrono::milliseconds(10));
  73. ctl.update_status(s, "Running");
  74. if (ctl.was_canceled())
  75. break;
  76. }
  77. },
  78. [](bool cancelled, std::exception_ptr &) { // finalize
  79. REQUIRE(cancelled == true);
  80. });
  81. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  82. worker.cancel();
  83. worker.wait_for_current_job();
  84. REQUIRE(pri->pr != 100);
  85. }
  86. TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) {
  87. using namespace Slic3r;
  88. using namespace Slic3r::GUI;
  89. auto pri = std::make_shared<Progress>();
  90. TestType worker{pri};
  91. std::array<bool, 4> jobres = {false, false, false, false};
  92. queue_job(worker, [&jobres](Job::Ctl &) {
  93. jobres[0] = true;
  94. // FIXME: the long wait time is needed to prevent fail in MSVC
  95. // where the sleep_for function is behaving stupidly.
  96. // see https://developercommunity.visualstudio.com/t/bogus-stdthis-threadsleep-for-implementation/58530
  97. std::this_thread::sleep_for(std::chrono::seconds(1));
  98. });
  99. queue_job(worker, [&jobres](Job::Ctl &) {
  100. jobres[1] = true;
  101. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  102. });
  103. queue_job(worker, [&jobres](Job::Ctl &) {
  104. jobres[2] = true;
  105. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  106. });
  107. queue_job(worker, [&jobres](Job::Ctl &) {
  108. jobres[3] = true;
  109. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  110. });
  111. // wait until the first job's half time to be sure, the cancel is made
  112. // during the first job's execution.
  113. std::this_thread::sleep_for(std::chrono::milliseconds(500));
  114. worker.cancel_all();
  115. REQUIRE(jobres[0] == true);
  116. REQUIRE(jobres[1] == false);
  117. REQUIRE(jobres[2] == false);
  118. REQUIRE(jobres[3] == false);
  119. }
  120. TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) {
  121. using namespace Slic3r;
  122. using namespace Slic3r::GUI;
  123. auto pri = std::make_shared<Progress>();
  124. TestType worker{pri};
  125. queue_job(
  126. worker, [](Job::Ctl &) { throw std::runtime_error("test"); },
  127. [](bool /*canceled*/, std::exception_ptr &eptr) {
  128. REQUIRE(eptr != nullptr);
  129. try {
  130. std::rethrow_exception(eptr);
  131. } catch (std::runtime_error &e) {
  132. REQUIRE(std::string(e.what()) == "test");
  133. }
  134. eptr = nullptr;
  135. });
  136. worker.wait_for_idle();
  137. REQUIRE(worker.is_idle());
  138. }