How can a Gtest mock functions to imitate a race between two functions?

Viewed 32

I have a function called resize which changes the size of a shared object. I have another function called getsize which gets the size of that shared object. I want to introduce a race between resize and getsize where I need a delay in exactly a particular line of resize and that particular point I want resize to sleep and getsize to be called and getsize to run at that particular time. Is there any way I can achieve this using gtest MOCK facility?

Resize(int newsize) {
  resizeint(newsize);
  // Want to introduce delay here and call Getsize().
  // This will simulate cache inconsistency.
  // Getsize() will return old size even though shared object is resized.
  writenewsizetocache();
}
int Getsize() {
  int size = readsizefromlocalcahce();
  return size;
}
1 Answers

One way to do this is to wrap your functions inside a class SizeWrapper and introduce a private variable like resize_delay_ that mimics the delay that you want and assign to it in the constructor.

You can then instantiate SizeWrapper with 0 for the case that you don't want that delay and with a positive number for the case that you want to insert the delay.

See below:


// Global size, representing the cache.
// Normally, this should be atomic or protected by mutex.
int global_size;

class SizeWrapper {
 private:
  void resizeint(int newsize) { temporary_size_ = newsize; }
  void writenewsizetocache() { global_size = temporary_size_; }
  int readsizefromlocalcahce() { return global_size; }
  int temporary_size_;
  int resize_delay_;

 public:
  SizeWrapper(int resize_delay)
      : resize_delay_(resize_delay), temporary_size_(0) {}
  void Resize(int newsize) {
    resizeint(newsize);
    // Want to introduce delay here and call Getsize().
    // This will simulate cache inconsistency.
    // Getsize() will return old size even though shared object is resized.
    if (resize_delay_ > 0) {
      std::this_thread::sleep_for(std::chrono::milliseconds(resize_delay_));
    }

    writenewsizetocache();
  }

  int Getsize() {
    int size;
    size = readsizefromlocalcahce();
    return size;
  }
};

TEST(SizeWrapperTest, GetSizeBeforeResize) {
  global_size = 0;

  int resize_write_value = 10;
  int getsize_read_value = 0;

  // getsize delay is larger than resize delay.
  const int resize_delay = 100;
  const int getsize_delay = 200;

  SizeWrapper size_wrapper(resize_delay);

  auto t1 = std::thread([&size_wrapper, resize_write_value] {
    size_wrapper.Resize(resize_write_value);
  });

  auto t2 = std::thread([&size_wrapper, &getsize_read_value, getsize_delay] {
    // Read the size with some delay.
    std::this_thread::sleep_for(std::chrono::milliseconds(getsize_delay));
    getsize_read_value = size_wrapper.Getsize();
  });

  t1.join();
  t2.join();

  // Thre read and write values are equal.
  EXPECT_EQ(getsize_read_value, resize_write_value);
}

TEST(SizeWrapperTest, GetSizeAfterResize) {
  global_size = 0;

  int resize_write_value = 10;
  int getsize_read_value = 0;

  // getsize delay is smaller than resize delay.
  const int resize_delay = 200;
  const int getsize_delay = 100;

  SizeWrapper size_wrapper(resize_delay);

  auto t1 = std::thread([&size_wrapper, resize_write_value] {
    size_wrapper.Resize(resize_write_value);
  });

  auto t2 = std::thread([&size_wrapper, &getsize_read_value, getsize_delay] {
    std::this_thread::sleep_for(std::chrono::milliseconds(getsize_delay));
    getsize_read_value = size_wrapper.Getsize();
  });

  t1.join();
  t2.join();

  // Thre read and write values are NOT equal.
  EXPECT_NE(getsize_read_value, resize_write_value);
}

See this live example: https://godbolt.org/z/Yq9PMoKds

Related