/**
 * Tests the validate command with {background:true} in a replica set.
 *
 * Checks that {full:true} cannot be run with {background:true}.
 * Checks that {background:true} runs.
 * Checks that {background:true} can run concurrently with CRUD ops on the same collection.
 *
 * @tags: [
 *   # Background validation is only supported by WT.
 *   requires_wiredtiger,
 *   # inMemory does not have checkpoints; background validation only runs on a checkpoint.
 *   requires_persistence,
 *   # A failpoint is set that only exists on the mongod.
 *   assumes_against_mongod_not_mongos,
 *   # A failpoint is set against the primary only.
 *   does_not_support_stepdowns,
 *   # Checkpoint cursors cannot be open in lsm.
 *   does_not_support_wiredtiger_lsm,
 *   requires_replication,
 * ]
 */

(function() {
'use strict';

load("jstests/libs/fail_point_util.js");
load("jstests/core/txns/libs/prepare_helpers.js");

const dbName = "db_background_validation_repl";
const collName = "coll_background_validation_repl";

// Starts and returns a replica set.
const initTest = () => {
    const replSet = new ReplSetTest({nodes: 1, name: "rs"});
    replSet.startSet();
    replSet.initiate();
    const primary = replSet.getPrimary();

    let testColl = primary.getDB(dbName)[collName];
    testColl.drop();
    return replSet;
};

const doTest = replSet => {
    /*
     * Create some indexes and insert some data, so we can validate them more meaningfully.
     */
    const testDB = replSet.getPrimary().getDB(dbName);
    const testColl = testDB[collName];
    assert.commandWorked(testColl.createIndex({a: 1}));
    assert.commandWorked(testColl.createIndex({b: 1}));
    assert.commandWorked(testColl.createIndex({c: 1}));

    const numDocs = 100;
    for (let i = 0; i < numDocs; ++i) {
        assert.commandWorked(testColl.insert({a: i, b: i, c: i}));
    }

    /**
     * Ensure {full:true} and {background:true} cannot be run together.
     */
    assert.commandFailedWithCode(testColl.validate({background: true, full: true}),
                                 ErrorCodes.CommandNotSupported);

    assert.commandWorked(testDB.adminCommand({fsync: 1}));

    // Check that {backround:true} is successful.
    let res = testColl.validate({background: true});
    assert.commandWorked(res);
    assert(res.valid, "Validate cmd with {background:true} failed: " + tojson(res));

    /*
     * Test background validation with concurrent CRUD operations.
     */

    // Set a failpoint in the background validation code to pause validation while holding a
    // collection lock.
    let failPoint = configureFailPoint(testDB, "pauseCollectionValidationWithLock");

    jsTest.log(`Starting parallel shell on port ${replSet.getPrimary().port}`);
    // Start an asynchronous thread to run collection validation with {background:true}.
    // Ensure we can perform multiple collection validations on the same collection
    // concurrently.
    let awaitValidateCommand = startParallelShell(function() {
        const asyncTestDB = db.getSiblingDB("db_background_validation_repl");
        const asyncTestColl = asyncTestDB.coll_background_validation_repl;
        const validateRes = asyncTestColl.validate({background: true});
        assert.commandWorked(
            validateRes, "asynchronous background validate command failed: " + tojson(validateRes));
        assert(validateRes.valid,
               "asynchronous background validate command was not valid: " + tojson(validateRes));
    }, replSet.getPrimary().port);

    // Wait for background validation command to start.
    jsTest.log("Waiting for failpoint to hit...");
    failPoint.wait();

    // Check that CRUD ops are succesful while validation is in progress.
    assert.commandWorked(testColl.remove({a: 1, b: 1, c: 1}));
    assert.commandWorked(testColl.insert({a: 1, b: 1, c: 1, d: 100}));
    assert.commandWorked(testColl.update({d: 100}, {"e": "updated"}));
    let docRes = testColl.find({"e": "updated"});
    assert.eq(1,
              docRes.toArray().length,
              "expected to find a single document, found: " + tojson(docRes.toArray()));

    // Clear the failpoint and make sure the validate command was successful.
    failPoint.off();
    awaitValidateCommand();

    /**
     * Verify everything is still OK by running foreground validation.
     */
    res = testColl.validate({background: false});
    assert.commandWorked(res);
    assert(res.valid, "Validate cmd with {background:true} failed: " + tojson(res));
    assert.eq(res.nIndexes, 4, "Expected 4 indexes: " + tojson(res));
    assert.eq(res.nrecords, numDocs, "Expected " + numDocs + " collection records:" + tojson(res));
    assert.eq(res.keysPerIndex._id_,
              numDocs,
              "Expected " + numDocs + " _id index records: " + tojson(res));
    assert.eq(res.keysPerIndex.a_1,
              numDocs,
              "Expected " + numDocs + " a_1 index records: " + tojson(res));
    assert.eq(res.keysPerIndex.b_1,
              numDocs,
              "Expected " + numDocs + " b_1 index records: " + tojson(res));
    assert.eq(res.keysPerIndex.c_1,
              numDocs,
              "Expected " + numDocs + " c_1 index records: " + tojson(res));
};

const replSet = initTest();
doTest(replSet);
replSet.stopSet();
})();
