void QueueTest::putMessages() {
  EventBase eventBase;

  QueueConsumer consumer;
  QueueConsumer consumer2;
  consumer.fn = [&](int msg) {
    // Stop consuming after we receive a message with value 0, and start
    // consumer2
    if (msg == 0) {
      consumer.stopConsuming();
      consumer2.startConsuming(&eventBase, &queue);
    }
  };
  consumer2.fn = [&](int msg) {
    // Stop consuming after we receive a message with value 0
    if (msg == 0) {
      consumer2.stopConsuming();
    }
  };
  consumer.startConsuming(&eventBase, &queue);

  list<int> msgList = { 1, 2, 3, 4 };
  vector<int> msgVector = { 5, 0, 9, 8, 7, 6, 7, 7,
                            8, 8, 2, 9, 6, 6, 10, 2, 0 };
  // Call putMessages() several times to add messages to the queue
  queue.putMessages(msgList.begin(), msgList.end());
  queue.putMessages(msgVector.begin() + 2, msgVector.begin() + 4);
  // Test sending 17 messages, the pipe-based queue calls write in 16 byte
  // chunks
  queue.putMessages(msgVector.begin(), msgVector.end());

  // Loop until the consumer has stopped
  eventBase.loop();

  vector<int> expectedMessages = { 1, 2, 3, 4, 9, 8, 7, 5, 0 };
  vector<int> expectedMessages2 = { 9, 8, 7, 6, 7, 7, 8, 8, 2, 9, 6, 10, 2, 0 };
  EXPECT_EQ(expectedMessages.size(), consumer.messages.size());
  for (unsigned int idx = 0; idx < expectedMessages.size(); ++idx) {
    EXPECT_EQ(expectedMessages[idx], consumer.messages.at(idx));
  }
  EXPECT_EQ(expectedMessages2.size(), consumer2.messages.size());
  for (unsigned int idx = 0; idx < expectedMessages2.size(); ++idx) {
    EXPECT_EQ(expectedMessages2[idx], consumer2.messages.at(idx));
  }
}
TEST(NotificationQueueTest, ConsumeUntilDrainedStress) {
  for (size_t i = 0; i < 1 << 8; ++i) {
    // Basic tests: make sure we
    // - drain all the messages
    // - ignore any maxReadAtOnce
    // - can't add messages during draining
    EventBase eventBase;
    IntQueue queue;
    QueueConsumer consumer;
    consumer.fn = [&](int j) {
      EXPECT_THROW(queue.tryPutMessage(j), std::runtime_error);
      EXPECT_FALSE(queue.tryPutMessageNoThrow(j));
      EXPECT_THROW(queue.putMessage(j), std::runtime_error);
      std::vector<int> ints{1, 2, 3};
      EXPECT_THROW(
          queue.putMessages(ints.begin(), ints.end()),
          std::runtime_error);
    };
    consumer.setMaxReadAtOnce(10); // We should ignore this
    consumer.startConsuming(&eventBase, &queue);
    for (int j = 0; j < 20; j++) {
      queue.putMessage(j);
    }
    EXPECT_TRUE(consumer.consumeUntilDrained());
    EXPECT_EQ(20, consumer.messages.size());

    // Make sure there can only be one drainer at once
    folly::Baton<> callbackBaton, threadStartBaton;
    consumer.fn = [&](int /* i */) { callbackBaton.wait(); };
    QueueConsumer competingConsumer;
    competingConsumer.startConsuming(&eventBase, &queue);
    queue.putMessage(1);
    atomic<bool> raceA {false};
    atomic<bool> raceB {false};
    size_t numConsA = 0;
    size_t numConsB = 0;
    auto thread = std::thread([&]{
      threadStartBaton.post();
      raceB = consumer.consumeUntilDrained(&numConsB) && numConsB;
    });
    threadStartBaton.wait();
    raceA = competingConsumer.consumeUntilDrained(&numConsA) && numConsA;
    callbackBaton.post();
    thread.join();
    EXPECT_FALSE(raceA && raceB);
    EXPECT_TRUE(raceA || raceB);
    EXPECT_TRUE(raceA ^ raceB);
  }
}