WL#7770: Develop GUNIT test framework and guidelines for DD API

Affects: Server-8.0   —   Status: Complete

The goals of this WL task are

  1) to develop GUNIT framework to make possible writing
     unit tests of DD API;

  2) provide guidelines to writing DD API tests.

This WL task has been extracted from WL#7284.
Unit test
=========

For unit tests of the new DD, there are two main approaches:

1. Testing the upper layers in the new DD implementation based on using the 
   dictionary client and the shared cache.
2. Testing the raw module and the interaction with the handler API.

These two approaches are rather different, and will be elaborated below.


1. Testing the upper layers in the new DD implementation
--------------------------------------------------------

This approach bypasses the entire handler API usage, and just uses main memory
data structures to simulate the persistent storage of the dictionary objects.

Pros: Provides a functional dictionary cache.
Cons: Hard to test functionality bypassing the cache.

Parts of the dictionary cache implementation is used to replace the usage of the
raw module and the handler API for storing the dd objects. Thus, when an object
is to be stored, instead of writing its fields into some record which is stored
in a storage engine, the entire object is just put into a data structure.
Similarly, when an object is to be acquired, a lookup is done the the data
structure, instead of reading records from the storage engine. If an obejct is
found, it is cloned and returned to the caller.

This approach provides a fully functional dictionary cache, and allows testing
high level use cases within a unit testing context. A templatized unit test has
been implemented based on this approach (in unittest/gunit/dd_cache-t.cc).


2. Testing the raw module and the interaction with the handler API
------------------------------------------------------------------

This approach is based on faking some of the key server data structures (such as
TABLE, Field, etc.) to allow mocking a handler interface. 

Pros: Allows testing the interaction between the new DD and the handler.
Cons: Effort demanding to implement the tests. Hard to test more complex use cases.

There are basically three layers in this part of the unit test framework:

1. The base layer, comprising classes that are of general interest,
   also for tests that are not related to the new dd. 
2. The generic dd layer, comprising subclasses of (1), designed to be used
   for testing the new dd. 
3. The individual unit tests, using instances of classes in (2). 

Each of the three layers are explained in more detail below. 

2.1 Base layer
..............
The base layer contains classes that are partially defined already in other
units tests. Some refactoring has been done to extract the classes into separate
header files. Additional refactoring should be done to utilize the base layer in
tests outside of the new dd to avoid implementing numerous versions of similar
classes. There are three main groups of classes: 

- The Base_mock_HANDLER class: Mocking the pure virtual handler
  functions.
- The Fake_TABLE and Fake_TABLE_SHARE classes: Exists already, should
  factor out generic code into Base_fake_* classes, and then provide
  various subclasses depending on use.
- The various Base_mock_field_* classes: As for the Fake_TABLE* classes,
  these should provide common functionality to be used by various
  subclasses depending on use.

2.2 Generic DD layer
....................
The generic DD layer contains class definitions intended to be used by a variety
of unit tests related to the new dd. 

- The Mock_HANDLER class: For the new dd, we need to mock write_row,
  update_row and index_read_idx_map.
- The various Mock_field_* classes: For the new dd, we mock store and
  val_* methods. We also implement additional fake_store and fake_val_*
  methods that can be invoked by the mocked methods as side effects. This
  makes it possible to invoke methods to both fake and verify the behavior. 
- Functions creating instances of Fake_TABLE, Fake_TABLE_SHARE, the
  required Mock_field_* classes and the Mock_HANDLER representing a single 
  dictionary table. These functions essentially fake opening a dictionary 
  table. 

2.3 Unit test layer
...................
The unit test layer contains the individual unit tests, structured in the usual
way. A test fixture class overrides the StartUp and TearDown methods, which do
common tasks before and after each test, e.g. initializing the dictionary,
starting a transaction, etc. The main task of each individual test is to set
side effects and expectations regarding the faked and mocked server classes.
E.g., for a test that provokes an insert of a new schema into the schemata dd
table, the index_read_idx_map is set up to return 1 (no record found), and each
Mock_field_*::store method is set up to invoke fake_store on the same instance,
which stores the value in a separate data field in the instance. Additionally,
expectations regarding the number of calls can be set. And finally, the
Handler::write_row method is expected to be called once. 

After the setup, the new schema is created and stored. This will invoke the
various side effects and expectation checking. After storing the new schema, it
is verified that the contents of the various table fields, which are set by
means of the fake_store method which is invoked when store is called, is as
expected. 

For tests reading objects from the dd tables, the field contents is set in
advance, and the val_* methods are set to have the side effect that they call
the fake_val_* methods to return the preset field contents. 

A unit test for schema objects has been implemented (in
unittest/gunit/dd_schema-t.cc), and may be used as an example for additional
unit tests of other object types. 

2.4 Future work
...............
The following issues may be improved upon in future enhancements:

- Extend T::TYPE dd classes to provide schema information enabling the unit
  tests to create tables with the correct schema automatically. At least 
  provide a public enumeration of the fields, as well as the total number of 
  fields. 
- Templatize the get_schema_table() function to take a dd table type argument
  like template  get_dd_table(), then use available schema 
  information from the T::TYPE to create the dd table object.
- Implement additional unit tests both for schemata and for other dictionary
  entities.
- Better integration with other unit tests; separate out common code into
  common base classes. Particularly important for the Fake_TABLE and 
  Mock_field_* classes.