TEST(LockManager, CompatibleFirstImmediateGrant) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_GLOBAL, 0);

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    request2.compatibleFirst = true;

    MMAPV1LockerImpl locker3;
    LockRequestCombo request3(&locker3);

    // Lock all in IS mode
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_IS));
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_IS));
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request3, MODE_IS));

    // Now an exclusive mode comes, which would block
    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestX, MODE_X));

    // If an S comes, it should be granted, because of request2
    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_OK == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // If request1 goes away, the policy should still be compatible-first, because of request2
    ASSERT(lockMgr.unlock(&request1));

    // If S comes again, it should be granted, because of request2 still there
    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_OK == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // With request2 gone the policy should go back to FIFO, even though request3 is active
    ASSERT(lockMgr.unlock(&request2));

    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // Unlock request3 to keep the lock mgr not assert for leaked locks
    ASSERT(lockMgr.unlock(&request3));
    ASSERT(lockMgr.unlock(&requestX));
}
TEST(LockManager, Conflict) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    MMAPV1LockerImpl locker2;

    LockRequestCombo request1(&locker1);
    LockRequestCombo request2(&locker2);

    // First request granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
    ASSERT(request1.recursiveCount == 1);
    ASSERT(request1.numNotifies == 0);

    // Second request must block
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_X));
    ASSERT(request2.mode == MODE_X);
    ASSERT(request2.recursiveCount == 1);
    ASSERT(request2.numNotifies == 0);

    // Release first request
    lockMgr.unlock(&request1);
    ASSERT(request1.recursiveCount == 0);
    ASSERT(request1.numNotifies == 0);

    ASSERT(request2.mode == MODE_X);
    ASSERT(request2.recursiveCount == 1);
    ASSERT(request2.numNotifies == 1);
    ASSERT(request2.lastResult == LOCK_OK);

    // Release second acquire
    lockMgr.unlock(&request2);
    ASSERT(request2.recursiveCount == 0);

    ASSERT(request1.numNotifies == 0);
    ASSERT(request2.numNotifies == 1);
}
TEST(LockManager, EnqueueAtFront) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestX, MODE_X));

    // The subsequent request will block
    MMAPV1LockerImpl lockerLow;
    LockRequestCombo requestLow(&lockerLow);

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestLow, MODE_X));

    // This is a "queue jumping request", which will go before locker 2 above
    MMAPV1LockerImpl lockerHi;
    LockRequestCombo requestHi(&lockerHi);
    requestHi.enqueueAtFront = true;

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestHi, MODE_X));

    // Once the X request is gone, lockerHi should be granted, because it's queue jumping
    ASSERT(lockMgr.unlock(&requestX));

    ASSERT(requestHi.lastResId == resId);
    ASSERT(requestHi.lastResult == LOCK_OK);

    // Finally lockerLow should be granted
    ASSERT(lockMgr.unlock(&requestHi));

    ASSERT(requestLow.lastResId == resId);
    ASSERT(requestLow.lastResult == LOCK_OK);

    // This avoids the lock manager asserting on leaked locks
    ASSERT(lockMgr.unlock(&requestLow));
}
TEST(LockManager, Grant) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker;
    TrackingLockGrantNotification notify;

    LockRequest request;
    request.initNew(&locker, &notify);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &request, MODE_S));
    ASSERT(request.mode == MODE_S);
    ASSERT(request.recursiveCount == 1);
    ASSERT(notify.numNotifies == 0);

    lockMgr.unlock(&request);
    ASSERT(request.recursiveCount == 0);
}
TEST(LockManager, Downgrade) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_X));

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_S));

    // Downgrade the X request to S
    lockMgr.downgrade(&request1, MODE_S);

    ASSERT(request2.numNotifies == 1);
    ASSERT(request2.lastResult == LOCK_OK);
    ASSERT(request2.recursiveCount == 1);

    ASSERT(lockMgr.unlock(&request1));
    ASSERT(lockMgr.unlock(&request2));
}