Testing locking and transactions

Now we are going to do some serious multi user testing. Having introduced transactions and complex locking to Database abstraction with google apps script in Working with transactions, testing all that in a multi user environment was always going to be tough. Using the lessons learned in Simulating multiuser testing, here's how.

The tests

I'm adding new backends to the test as I complete their implementation of transactions., locking and rollbacks. The test will run serveral of instances of the same back end along side others. The objective is to make sure there is no leakage of locking across or between backends and that cache voiding and so on were working in this complex environment.

Here's the profiles.

Most of this is recycled from previous posts, so I won't repeat the details of the profile functions. Here they are all put together though.

function dbMassiveProfiles() {
  
  var CHUNKS = 4;
  var profile = [];
  
  // clear the db
  var profileClear = getTheMassiveClearProfile();
  
 // get the test data
  var profileTest = [dbAsyncTestData ()];
  
  // reduce the test data
  var profileReduction = [dbAsyncReduce()];
    
  // get and process all the messages
  var profileSheets = getTheMassiveExecuteProfile(CHUNKS);
  
  //Log the results
  var profileLog= [dbAsyncLog ('massive')];

  profile.push(
    profileClear,
    profileTest,
    profileReduction,
    profileSheets,
    profileReduction,
    profileLog
  );
  
  return profile;
  
}
function getTheMassiveExecuteProfile (chunks) {

  return  [ 
    dbSheetParameters,
    dbDriveParameters, 
    dbMemoryParameters,
    dbParseParameters,
    dbMongoLabParameters
  ].reduce (function (p,c) {
    for (var i =0; i <chunks;i++ ) {
      p.push(dbAsyncExecute (c().parameters, c().driver,i,chunks));
    }
    return p;
  },[]);

}

function getTheMassiveClearProfile (chunks) {

  return  [ 
    dbSheetParameters,
    dbDriveParameters, 
    dbMemoryParameters,
    dbParseParameters,
    dbMongoLabParameters
  ].map (function (f) {
    return dbAsyncClear (f().parameters, f().driver);
  });


}


And the results 


The tests

These are the standard tests I use against all databases, except now I've updated them to include transactions

/**
 * execute test cases against database
 * @param {DbAbstraction} handler a handler
 * @param {string} accessToken if ouath2 is needed
 * @param {number} cycles sclae factor. not required if testData is already present
 * @param {Array.object} testData the test data
 * @param {string} strain filters all queries if present
 * @return {object} the results of the test
 */
function testCases (handler, accessToken, cycles,testData,strain,small) {

  // for constraints
  var dhc = cDbAbstraction.dhConstants;
  var c = dhc.CONSTRAINTS;

  // duplicate it up to test scalability
  cycles = cycles || 1;
  var what = 'datbase test'
  var whatall ='mixed-'+cycles
  
  // get some test data if not passed here already
  testData = testData ? cUseful.clone(testData) : getSomeTestData (cycles);
  var sample = testData.length;
  
  // use this for paralell running stream identification
  strain = strain || handler.getDBName();
  testData.forEach(function(d) {
    d.strain = strain;
  });

  var thenall = new Date().getTime();

    
  // clean set of data
  var r2 = handler.transaction ( function (db) {

   // delete everything
   var result = handler.remove({strain:strain});
   assert (result.handleCode >= 0 ,result,'remove');

    //query everything
    var result = handler.query({strain:strain});
    assert ([result.handleCode >= 0,result.data.length === 0],result, 'querypostdelete');
  
    //save the test data
    var result = handler.save (testData);
    assert (result.handleCode >= 0 ,result,'save');  

    return result;
  });
  assert ([r2.transaction.code >=0], r2, 'transaction clean set');
  
  var then = new Date().getTime();

  //query everything
  var result = handler.query({
    strain:strain
  });
  assert ([result.handleCode >= 0 ,result.data.length === testData.length], result, 'query1');


  // make sure we're getting the right id with complex constaints
  var result = handler.query ( {
    strain:strain,
    'stuff.age': handler.constraints([[c.GT,25],[c.LTE,60]])
  });
  assert ([result.handleCode >= 0, testData.filter( function(d) { 
        return  (d.stuff.age <= 60 && d.stuff.age > 25)  ; 
      }).length === result.data.length],result, "first test easy");

  // query everything with limit
  var result = handler.query(
    {strain:strain},
    {limit:1}
  );
  assert ([result.handleCode >= 0, result.data.length === 1],result,'querylimit');

  // sort reverse
  var result = handler.query(
    {strain:strain},
    {sort:'-name'}
  );
  
  assert ([result.handleCode >= 0, result.data.length === testData.length],
    result, 'querysort- data length was ' + result.data.length);
  
  // sort reverse, skip 1
  var result = handler.query(
    {strain:strain},
    {skip:9,sort:'-name'}
  );
  assert ([result.handleCode >= 0,result.data.length === testData.length -9],result,'querysort+skip');

  // flattten query everything
  var result = handler.query({
    "name":'ethel',
    strain:strain
  });
  assert ([result.handleCode >= 0,testData.filter(function(d) { 
      return d.name ==='ethel'; 
    }).length === result.data.length ],result,'filterdot0'); 

  // flatten query everything
  var result = handler.query({
    strain:strain,
    stuff:{sex:'female'}
  });
  assert ([result.handleCode >= 0,testData.filter(function(d) { 
      return d.stuff.sex ==='female'; 
    }).length === result.data.length], result,'filter'); 

  // check a single constraint works
  var result = handler.query({
    strain:strain,
    "stuff.age":handler.constraints([[c.GT,25]])
  });
  assert ([result.handleCode >= 0,testData.filter(function(d) { 
      return   d.stuff.age > 25; 
    }).length === result.data.length],result,'filterdotc1'); 

  // two constraints on same property
  var result = handler.query ( {
    strain:strain,
    'stuff.age': handler.constraints([[c.LT,60],[c.GT,25]])
  });
  assert ([result.handleCode >= 0,testData.filter(function(d) { 
      return  d.stuff.age > 25 && d.stuff.age < 60; 
    }).length === result.data.length],result,'filterdotc2'); 
      

  // check two constraints plus a different property
  var result = handler.query ( {
    strain:strain,
    'stuff.age': handler.constraints([[c.LT,60],[c.GTE,25]]),
    'stuff.sex': 'male'
  },undefined,1,true);
  assert ([result.handleCode>= 0,testData.filter( function(d) { 
      return  d.stuff.age >= 25 && d.stuff.age < 60 && d.stuff.sex === 'male'; 
    }).length === result.data.length],result,'filterdotc3'); 
            
  var result = handler.query ( {
    strain:strain,
    name:handler.constraints([[c.IN,['ethel','fred']]])
  });
  assert ([
    result.handleCode >= 0,
    testData.filter(function(d) { 
      return   ['ethel','fred'].indexOf(d.name) != -1; 
    }).length === result.data.length],result,'filterdotc4'); 
  
  // check text constraints  
  var result = handler.query ( { 
    strain:strain,
    'stuff.age': handler.constraints([[c.GT,25]]), 
    'stuff.sex': 'male',
    'name':handler.constraints([[c.IN,['john','mary']]])
  });    
  assert ([result.handleCode >= 0, testData.filter(function(d) { 
      return  d.stuff.age > 25 && d.stuff.sex === 'male' && ['john','mary'].indexOf(d.name) != -1; 
    }).length === result.data.length], result,'filterdotc5'); 

  // this transaction is testing gets
  var t = handler.transaction( function (db) {

      var result = handler.query ( { 
      strain:strain,
      'stuff.age': handler.constraints([[c.GT,25]])
    },{limit:1}, 1, true );
    assert ([result.handleCode >= 0,result.data.length ===1, result.handleKeys.length ===1], result, "limitkeycheck1");
      

    // testing Get
    var r2 = handler.get(result.handleKeys);
    assert ([r2.handleCode >= 0,r2.data.length === result.data.length,r2.data.every (function(d) { 
        return d.stuff.age > 25 ; 
      })], r2, "getcheck1");

    // retest constraint
    var result = handler.query ( {
      strain:strain,
      'stuff.age': handler.constraints([[c.GT,60]])
    });
    assert ([result.handleCode >= 0,testData.filter(function(d) { 
        return  ( d.stuff.age > 60)  ; 
      }).length === result.data.length], result, "repeat test easy");

    return result;
  });
  assert ([t.transaction.code >=0], t,' transaction 99');
  
  var t = handler.transaction ( function (db) {
    var result = db.query (
      { strain:strain,
       'stuff.sex': 'male'
      },undefined,1,true);

      assert ([ 
        result.handleCode >= 0,
        testData.filter(function(d) { 
          return  (d.stuff.sex === 'male')  ; 
        }).length === result.handleKeys.length
      ], result, "does male work");
      
 
      var k = db.remove (
        { strain:strain,
         'stuff.sex': 'male'
        },undefined,1,true);
      
      assert ([k.handleCode >= 0], k, "delete the males");
  
  
      var k = db.save (result.data);
      assert ([k.handleCode >= 0], k, "insert the males");
      
      var result = db.query (
      { strain:strain,
       'stuff.sex': 'male'
      },undefined,1,true);

      assert ([ 
        result.handleCode >= 0,
        testData.filter(function(d) { 
          return  (d.stuff.sex === 'male')  ; 
        }).length === result.handleKeys.length
      ], result, "redo male save");
      

    var result = db.query (
      { strain:strain,
       'stuff.sex': 'female'
      },undefined,1,true);

      assert ([ 
        result.handleCode >= 0,
        testData.filter(function(d) { 
          return  (d.stuff.sex === 'female')  ; 
        }).length === result.handleKeys.length
      ], result, "does female work");
      
      
      var r2 = db.update( result.handleKeys, result.data.map (function(d) { 
       d.stuff.female = d.stuff.sex === 'female';
       d.timeStamp = new Date().getTime();
       return d; 
      }));

      assert ([r2.handleCode >= 0], r2, "update the females");

      var result = db.query ({
        strain:strain,
        'stuff.female': true
      });
      
      assert ([ 
        result.handleCode >= 0,
        testData.filter(function(d) { 
          return  (d.stuff.sex === 'female')  ; 
        }).length === result.data.length
      ], result, "female test");
      
      return result;
    });

   assert ([t.transaction.code >=0], t,' transaction 100');
  
  // check previous query still works
  var result = handler.query ( {
    strain:strain,
    'stuff.age': handler.constraints([[c.LTE,60],[c.GT,25]])
  });
  assert ([
    result.handleCode >= 0,
    testData.filter(function(d) { return  ( d.stuff.age <= 60 && d.stuff.age > 25)  ; } ).length === result.data.length
  ],result, "repeat test after update");

  //query post update
  var result = handler.query({
    strain:strain
  });
  assert (result.handleCode >= 0,result, 'querypostupdate');

 // lets try counting
  var result = handler.count ({
    strain:strain
  });

  assert ([
    result.handleCode >= 0,
    testData.length === result.data[0].count 
  ],result,'count 1'); 

  // check count
  var result = handler.count ( {
    strain:strain,
    'stuff.age': handler.constraints([[c.GT,25]]), 
    'stuff.sex': 'male',
    'name':handler.constraints([[c.IN,['fred','john']]])
  });
  assert ([
    result.handleCode >= 0,
    testData.filter(function(d) { 
      return  d.stuff.age > 25 && d.stuff.sex === 'male' && ['fred','john'].indexOf(d.name) != -1; 
    }).length === result.data[0].count
  ],result,'count 2'); 

  handler.transaction ( function (db) {
  // get readu for OR  
    var result = handler.query ( {
      strain:strain,
      'stuff.age': handler.constraints([[c.GTE,60]]), 
      'stuff.sex': 'male'
    });  
    assert ([
      result.handleCode >= 0,
      testData.filter(function(d) { 
      return  ( d.stuff.age >= 60 && d.stuff.sex === 'male') ; }).length === result.data.length
    ],result,'normal 0'); 


  // make sure we're getting the right id with complex constaints
    var result = handler.query ( {
      strain:strain,
      'stuff.age': handler.constraints([[c.GT,25],[c.LTE,60]])
    });
    assert ([
      result.handleCode >= 0,
      testData.filter(function(d) { 
          return  (d.stuff.age <= 60 && d.stuff.age > 25)  ; 
      }).length === result.data.length
    ],result, "repeat test constraint");

  // try OR
    var result = handler.query ( [
      {'stuff.age': handler.constraints([[c.LT,26]]),
       'stuff.sex':'male',strain:strain
      },
      {'stuff.age': handler.constraints([[c.GTE,60]]),
       'stuff.sex':'male',strain:strain
      }
    ]);

    assert ([
      result.handleCode >= 0,
      testData.filter(function(d) { 
      return  (d.stuff.age < 26 && d.stuff.sex === 'male') || (d.stuff.age >= 60 && d.stuff.sex === 'male')}).length === result.data.length
    ],result,'OR 1'); 
  
    return result;
  });
  
  // show all the males
  var result = handler.query ( {
    strain:strain,
    'stuff.sex':'male'
  },undefined,1,true);
  
  assert ([
    result.handleCode >= 0 ,
    result.data.every (function(d) {
      return d.stuff.sex === 'male'; 
    })
  ],result , "show the males");

  var t = handler.transaction (function(db) {
    // remove all the males
    var result = handler.remove ( {
      'stuff.sex':'male',
      strain:strain
    });
    assert (result.handleCode >= 0 , result , "remove the males");
  
    // check they've all gone
    var result = handler.query ({
      strain:strain
    });
    assert ([
        result.handleCode >=0,
        result.data.every (function(d) {return !d.stuff.man; }),
        result.data.length === testData.filter (function(d) { return d.stuff.sex !== 'male'}).length
      ], result,'check after delete males');


    // add them back in
    var result = handler.save( testData.filter(function (d) { 
      return d.stuff.sex === 'male' 
    }));
    assert (result.handleCode >= 0 , result , "add them in again");
  
    // check what we haveassertion
    var result = handler.count({
      stuff:{sex:'female'},
      strain:strain
    });
    assert ([
      result.handleCode >= 0,
      result.data[0].count === testData.filter (function(d) { return d.stuff.sex === 'female'}).length
    ], result,'check count female');
  
    // check what we have
    var result = handler.count({stuff:{sex:'male'},strain:strain});
    assert ([
        result.handleCode >= 0,
        result.data[0].count === testData.filter (function(d) { return d.stuff.sex === 'male'}).length
      ],
      result,'check count male');

  // sort and save
    var result = handler.query(
      {strain:strain},
      {sort:'-serial'}
    );
    assert ([
      result.handleCode >= 0,
      result.data.length === testData.length
    ],result,'check sort count');
  
  
    // mark as good and save
    var r2 = handler.save (result.data.map(function(d){ 
      d.good=true; return d;
    }));
    assert (r2.handleCode >= 0 , r2 , "wrote back the data");
    
    // check we have twice the records
    var result = handler.query({
      strain:strain
    });
    assert ([
      result.handleCode >= 0,
      result.data.length === testData.length *2
    ],result,'check sort count');
  
    // delete the ones we added
    var r2 = handler.remove({
      good: true,
      strain:strain
    });
    assert (r2.handleCode >= 0 , r2 , "removed the data");
    return result;
  
  });
  assert ([t.transaction.code >=0],t,'transaction big');
  
  // check the original length
  var result = handler.query({
    strain:strain
  });
  assert ([
    result.handleCode >= 0,
    result.data.length === testData.length
  ], result,'check final count');

  var now = new Date().getTime();

  return {end:now,start:thenall,what:what,where:handler.getDBName(),code:result.handleCode,elapsed:now-thenall,volume:testData.length};

  
}

For more on this topic, see Running things in parallel using HTML service. For more snippets like this see Google Apps Scripts snippets

For help and more information join our forum,follow the blog or follow me on twitter .

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.




Comments