[MDEV-11] Generic storage engine test suite Created: 2011-11-24 Updated: 2018-03-03 Resolved: 2014-01-15 |
|
| Status: | Closed |
| Project: | MariaDB Server |
| Component/s: | Tests |
| Fix Version/s: | None |
| Type: | Task | Priority: | Major |
| Reporter: | Rasmus Johansson (Inactive) | Assignee: | Elena Stepanova |
| Resolution: | Fixed | Votes: | 0 |
| Labels: | None | ||
| Attachments: |
|
||||||||||||||||
| Issue Links: |
|
||||||||||||||||
| Sub-Tasks: |
|
||||||||||||||||
| Description |
|
Goal GoalThe goal of this task is to create a set of tests which could be used for acceptance/conformance testing of a storage engine. Problems to solveExisting MTR tests are not very suitable for running on different storage engines. Problem 1: Varying result filesTraditional MTR/mysqltest have very strict requirements in regard to test output. Since different storage engines are likely to produce a slightly different output (even if the difference is innocent), the tests start failing, which causes false positives. Example:
Neither is wrong, and yet if we have one variant in the result file, for another engine the test will fail. Till recently the only solution was to copy the whole suite somewhere and recreate result files, thus introducing usually unwanted code duplication. SolutionTo solve this problem, we will use functionality developed in scope of Problem 2: Unsupported featuresMost of tests use combinations of various features, even if not all of them are strictly necessary in scope of this exact test. It means that if an engine does not support at least one feature, either by design or because it has not been implemented yet, the whole test becomes inapplicable. For example, if a traditional MTR test for ALTER TABLE uses at least one key, and keys are not supported, the test will fail – not just produce a result mismatch which we could patch, but actually fail in the middle of execution, – so we will have to stop running it, and ALTER TABLE will not be tested at all; and the same will happen to any test which uses keys, even if it's used briefly for an unimportant part of the test. This problem is even bigger than the first one, because not only does it currently require copying the test suite and maintaining a separate set of result files, but also modifying the test files themselves; and often modifications will be extensive enough to rule out even the possibility of future merges with the original test suite. SolutionWe will solve this problem by allowing all tests to run through intermediate failures to the end (unless the server crashes or a syntax error in the test itself occurs). Thus, the tests will also produce mismatches, and the engine maintainer will be able to decide whether the test is really inapplicable and needs to be disabled, or the partial failures are acceptable and can be stored in the result diff file. Example: We are testing that a table option is supported by the engine (merely is accepted and stored); lets say AUTO_INCREMENT. We create a table with the option and check that it is not lost; but we also want to see that it works with ALTER; so, we add an alter table statement where the option is modified. Now, if we try to run such a test with FEDERATED engine, it will break on ALTER – not because the option is unsupported, but because ALTER is. In the storage engine test suite, the test will continue to the end and will produce a mismatch (because the ALTER statement will cause an error). Then, someone who tunes the suite for FEDERATED engine, can see the difference, decide that it's reasonable, since ALTER is not supposed to work, and create an rdiff file. Thus, we do not skip the test for the table option entirely, but simply limit the testing to functionality the engine supports. The tests will try to be smart in their internal logic, but within reason. When a statement is likely to fail (e.g. a feature is often unsupported), the test will check the result of the statement and will produce a verbose error message, including versions why it could happen. Also, if a failure happened on table creation, or in some other key moment, so that a part of test becomes useless (if a table was not created, there is no point at trying to alter it, etc.), the test will ignore a part of the flow, and proceed to the next part. But it does not happen always, the checks are only performed when the probability of a failure is reasonably high. Additionally, some tests will check the value of the default index (see below notes about configuration), and if it is unset, certain tests will be completely skipped, because every part of them requires indexes (e.g. index.test). For the functionality described in the ENGINES table (transactions, savepoints, XA), if a test requires one of the features and it is shown as unsupported in the table, the test will produce a warning. But unlike with the indexes, it will not be skipped, because we assume that for a new engine information in the ENGINES table might be inaccurate. Problem 3: Different primitivesSome engines have specifics which makes even simplest hardcoded statements inapplicable. For example, whenever a test contains In even more complicated scenario, in order to create a table properly, additional actions need to be performed; e.g. to create a functional MERGE table, we need to also create an underlying MyISAM table; and if we want to alter a MERGE table, we should alter the underlying table(s) accordingly. SolutionWe will provide the engine maintainer with several tools to tune the suite for their engine.
For more details, see the 'Tuning' section. Bugs filed while working on the suite:LP:973039 / The list might be incomplete TuningPlease note that the test suite is synthetic. It means that it contains tests for a set of features which (the whole set) is not supported by any known engine. The same is true for result files – they are taken from different engines, depending on which produce the most sensible result, and in rare cases are even artificial. This means that there is no engine which would pass the whole suite without any tuning – not even MyISAM, which contributed to the result files the most. AssumptionsWe presume that the tests are set on a source tree build (which is reasonable, considering that the main goal is to help with storage engine development). We want to configure the tests for some engine OurENGINE. The storage engine code is located in <basedir>/storage/<ourengine> folder. The storage_engine test suite is located in <basedir>/mysql-test/suite/storage_engine folder and contains subsuites in subfolders of the corresponding names (currently <basedir>/mysql-test/suite/storage_engine/parts and <basedir>/mysql-test/suite/storage_engine/trx. Common tuning steps1. Create <basedir>/storage/ourengine/mysql-test/storage_engine folder. 2. Copy <basedir>/mysql-test/suite/storage_engine/define_engine.inc to <basedir>/storage/ourengine/mysql-test/storage_engine and edit the file (at the very least set ENGINE variable; check other variables which you find there, and modify as needed. 3. If the engine requires any additional server options (e.g. to load the engine, or to tune it, or both), create <basedir>/storage/ourengine/mysql-test/storage_engine/suite.opt file and add the options there. 4. If you know in advance that the engine requires additional steps before a test, add them at the end of define_engine.inc. 5. If you created any SQL objects in define_engine.inc, create file <basedir>/storage/ourengine/mysql-test/storage_engine/cleanup_engine.inc, or copy a stub from <basedir>/mysql-test/suite/storage_engine, and add the logic to drop the objects. 6. If you know in advance that the engine requires non-standard table creation and/or modification procedure, copy <basedir>/mysql-test/suite/storage_engine/create_table.inc and <basedir>/mysql-test/suite/storage_engine/alter_table.inc into <basedir>/storage/ourengine/mysql-test/storage_engine/ and modify them as needed. 7. Try to run the 1st test: 8. If the test produces a mismatch, analyze it and decide whether table creation or test options or server options require more tuning, or the difference is expected. 9. If the difference is expected, create an rdiff file as 10. When the 1st test passes, run the whole suite: perl ./mtr --suite=storage_engine-ourengine --force --max-test-fail=0 11. Analyze failures, modify parameters or include files as needed, create rdiff files. 12. If any tests requires specific non-standard server/engine options, create files <testname>.opt in <basedir>/storage/ourengine/mysql-test/storage_engine. 13. If any tests have to be skipped, add them to <basedir>/storage/ourengine/mysql-test/storage_engine/disabled.def. 14. When you are satisfied with the results of storage_engine suite, proceed to the subsuites. If you are interested in running partitions tests, create folder <basedir>/storage/ourengine/mysql-test/storage_engine/parts 15. Repeat step 3, if needed, only now create suite.opt in <basedir>/storage/ourengine/mysql-test/storage_engine/parts. 16. Run the subsuite as perl ./mtr --suite=storage_engine/parts-ourengine --force --max-test-fail=0 17. Repeat steps 11-13, only now create files in <basedir>/storage/ourengine/mysql-test/storage_engine/parts 18. If you want to run transactional tests, repeat steps 14-17 for trx subsuite. 19. To execute the whole set of tests, run perl ./mtr --suite=storage_engine-ourengine,storage_engine/*-ourengine ExamplesBelow are some real-life experiences of tuning the test suite for engines that currently come with MariaDB. Actual exact steps are always unique and depend on the storage engine (otherwise it wouldn't be tuning). In some cases only very basic actions are required; in others, it takes more work. In general, the more a storage engine is "different", the trickier is the task. Easy level: MyISAMLets see how to make the suite work for a relatively standard engine, in terms of behavior similar to main MySQL engines. cd <basedir>/mysql-test Edit the copied version of define_engine.inc to set ENGINE to MyISAM:
All other parameters look good: MyISAM does not require any specific table or column options, and it supports non-unique indexes, INT and CHAR types. These all are defaults (you can see it in define_engine.inc). Also, we do not need any additional server options to activate the engine, since MyISAM is always there. So, now we can try to run the 1st test:
The first test passed. Okay, now we can run the whole suite. Some tests will fail, this is expected; we need to see the results
Note: Total execution time depends on the engine and on the machine. For MyISAM it's pretty fast, for other engines might take longer. 7 failing tests on the first iteration is extremely good, of course it won't be that bright with other engines. But it's MyISAM, what did you expect... (Please keep in mind that the suite results are synthetic, meaning that they belong to different engines, and in rare cases are even artificial, e.g. when the only available engine supporting the functionality currently has a bug. So, no engine is expected to pass all tests without tuning. Not even MyISAM.) Now it's time to analyze results. Result handling in this suite is somewhat different from standard MTR tests. Unless you managed to crash the server or to hit a syntax error in the test itself, all results will be mismatches – that is, a test will not abort on a failed statement, but instead will try to proceed. The big benefit of it is that if your engine does not support everything, the tests are still usable, you'll just need to approve the difference in the results (by creating an rdiff file). Of course, it also means that the tests produce more noise than usual – e.g. if your engine does not support ALTER, a standard MTR test would break on the first ALTER statement, while ours will continue and will allow you to use the logic, but will also produce a bunch of mismatches due to failing statements. Internal logic in tests does its best to make it cleaner, but still, the noise is expected. So, with this knowledge, lets find the failures and go through them one by one. The first failing test is alter_tablespace. Well, naturally – no tablespaces for MyISAM. But lets look at the output. Mismatch says that some stuff is missing, and instead the test produces this:
Since MyISAM is not supposed to support tablespaces, changing the engine code is not an option. You can decide whether to disable the test entirely, or to create an rdiff file for it. Unless you have really tough restrictions on suite execution time, I recommend keeping tests and creating rdiffs. Even if you don't care about the result, the test, running as a regression check, will at least show that this very first statement has not suddenly started crashing the server, or something unexpectedly changed in the behavior. Creating an rdiff file is simple: diff -u suite/storage_engine/alter_tablespace.result suite/storage_engine/alter_tablespace.reject > ../storage/myisam/mysql-test/storage_engine/alter_tablespace.rdiff Next failed test: check_table. Its difference is simple:
No harm if the engine realizes that the table is up to date and says so; adding a diff. diff -u suite/storage_engine/check_table.result suite/storage_engine/check_table.reject > ../storage/myisam/mysql-test/storage_engine/check_table.rdiff Next failing test: foreign_keys Just like alter_tablespace, it produces a lot of messages about possibly unsupported functionality, which is natural, since MyISAM doesn't have foreign keys. Adding a diff. Next failing test: index_type_hash It produces mismatches where HASH type is replaced by BTREE type:
Since MyISAM doesn't support HASH type, it is fine. Adding a diff. Next failing test: show_engine This test is optimistic and expects that SHOW ENGINE <engine name> STATUS command returns something – that is, the test obfuscates the output, but expects that it contains a row. For MyISAM it is not the case, hence the diff looks like a missing row:
Adding a diff. Next failing tests: tbl_opt_insert_method, tbl_opt_union MyISAM does not use the table option INSERT_METHOD, it's the MERGE engine thing. But table creation does not normally fail on unsupported options, they are simply ignored. That's what we see here:
SHOW ENGINE does not show the option. This is fine, adding rdiff. Exactly the same for UNION. These were all 7 failures. Now it's time to take care of subsuites. Currently there are two of them: parts (stands for 'partitions'), and trx (stands for 'transactions'). MyISAM definitely supports partitioning, so lets try them first. (we are still in <basedir>/mysql-test) mkdir ../storage/myisam/mysql-test/storage_engine/parts This will show MTR that our engine is interested in the storage_engine/parts subsuite. No additional parameters or tricks should be needed for MyISAM to run partition tests, since the suite already sets --partition option. So, we'll just run it:
Note: For now, it is a very basic suite, only contains a few tests and literally takes several seconds All good, tests passed, nothing needs to be done here. Finally, there is the transactions subsuite (also contains tests for XA and snapshots). It is also very basic, but we know that MyISAM doesn't support transactions, XA and snapshots, so results won't be this pretty. Maybe we don't even need to run it, but why not try. mkdir ../storage/myisam/mysql-test/storage_engine/trx
The results are mess, as expected. All differences start with an extra warning (sometimes more than one, as the same is produced for snapshots and XA):
The rest is a mix of wrong results (missing rows, extra rows, different rows), warnings about rollback not working, etc. With 3rd-party engines, it would be up to a maintainer whether to add diffs for all tests or just ignore the whole thing since transactions are not supported. If you choose to ignore, simply delete the newly created ../storage/<your_engine>/mysql-test/storage_engine/trx, and the subsuite won't be executed for the engine. I choose to keep it, so I'll add rdiffs for all 13 files, it's not that much work, after all.
Tip: If you decide to do the same, take time to go through the reject files and see that your engine works as expected, under the circumstances; sometimes making things do what they are not normally expected to do reveals hidden problems. Now we can run everything together:
That's all. Now just stay out of failures. Intermediate level: InnoDB pluginA little bit more work is required to create an overlay for InnoDB. Lets try to do it for InnoDB plugin (which is not loaded by default as of 5.5.25, but is built there). Again, start with creating the overlay directory: mkdir -p ../storage/innobase/mysql-test/storage_engine
As for MyISAM, all defaults are fine for InnoDB. But now we also need to server startup options to run server with the InnoDB plugin. create the file ../storage/innobase/mysql-test/storage_engine/suite.opt:
It should be enough for the base suite. Lets run the 1st test now:
And then the whole suite:
Not as great as it was with MyISAM. Lets see the details. Some mismatches are either identical or similar to those in MyISAM, and caused by unsupported functionality (e.g. fulltext search, hash indexes, optimize_table, etc.). I won't go through them here, will just add rdiff files. But some deserve attention. alter_table_online:
It's hard to say whether all engines that support ALTER ONLINE should support them for the same set of changes; most likely not, and what we see here is just an InnoDB limitation. On the other hand, we know that MariaDB supports ALTER ONLINE, and namely renaming a column (see http://kb.askmonty.org/en/alter-table), and InnoDB supports at least some ALTER ONLINE operations (e.g. CHANGE COLUMN i i INT DEFAULT 1 works); so I think it's worth filing it as a low-priority bug, at least to make sure it works as expected: https://mariadb.atlassian.net/browse/MDEV-397 For now, I will add the test to ../storage/innobase/mysql-test/storage_engine/disabled.def list (need to create it, since it's the first test we disable for the engine):
If later it turns out to be expected behavior or limitation, I will remove the line from disabled.def, and will instead add an rdiff file. alter_tablespace:
Now, that seems unexpected. But then again, tablespace operations are only applicable when InnoDB works in innodb-file-per-table mode, which we did not set in our options. Unless we want to use it for all tests, lets set it for this one only: ../storage/innobase/mysql-test/storage_engine/alter_tablespace.opt
autoinc_vars:
This is weird. Now real investigation starts – there is a good reason to look at the reject file to see the continuous flow:
The first insert works all right with auto_increment_increment = 300. Then we change it to 50, but the following insert still uses 300 for the first value it inserts, and only then switches to 50. Thus we get 1100 instead of 850, and following values also differ. This smells like a bug, although not a very serious one. Since a brief check shows it's also reproducible on Oracle MySQL, we will file it on bugs.mysql.com: http://bugs.mysql.com/bug.php?id=65225 (I actually did it some time ago, when I tried to run the storage engine suite for InnoDB for the first time, that's why it's not brand new). And we will also add the test to ../storage/innobase/mysql-test/storage_engine/disabled.def:
delete_low_prio, insert_high_prio, insert_low_prio, select_high_prio, update_low_prio: They all have similar fragments in their output:
As the documentation says, the high|low priority functionality (e.g. DELETE LOW_PRIORITY) only works for table-level locking, and the whole test is based on this assumption. InnoDB uses row-level locking, so the entire flow does not work quite as expected. We still can add rdiff files, but, unlike the most of other tests, these ones take relatively long (probably over 10 seconds each). Besides, since locking works entirely different here, the test results are likely to be unstable, as it will be all about timing. So, it makes more sense to disable the tests by adding them to ../storage/innobase/mysql-test/storage_engine/disabled.def:
tbl_opt_ai:
We already looked at ignored table options in MyISAM tests, but this one is different. Why would AUTO_INCREMENT be ignored, it should be supported all right by InnoDB? (Brief manual check confirms it). Some digging shows, however, that in our case it is truly ignored. It is reproducible with Oracle MySQL, filing a bug on bugs.mysql.com: http://bugs.mysql.com/bug.php?id=65901 Adding the test to ../storage/innobase/mysql-test/storage_engine/disabled.def:
tbl_opt_key_block_size, tbl_opt_row_format:
Doing the same as we did for alter_tablespace, only now adding both innodb_file_per_table and innodb_file_format: ../storage/innobase/mysql-test/storage_engine/tbl_opt_key_block_size.opt:
type_char_indexes:
Note: For now we assume that inside one engine, statistics is stable enough to produce consistent results on each test run, which is why we show certain fields in explain to let you decide whether you are satisfied with them or not. If further experience shows that even for the same engine, these tests routinely produce different results, and more often than not it's valid behavior, we might change it. For now, I will consider these results acceptable, and will add rdiff. As I said before, the rest of failures do not deserve verbose analysis, they are pretty straightforward, I just added rdiff for each of them. Now working with storage_engine/parts and storage_engine/trx. mkdir ../storage/innobase/mysql-test/storage_engine/trx Copy your previously created suite.opt file to each of the subfolders: as far as MTR is concerned, they are separate suites. cp ../storage/innobase/mysql-test/storage_engine/suite.opt ../storage/innobase/mysql-test/storage_engine/trx/ Maybe you'll want to add something else to those options. I, for one, will add --innodb-lock-wait-timeout=1 to ../storage/innobase/mysql-test/storage_engine/trx/suite.opt. Probably it should have been done for other suites, too – but it's never late, if there are any timeout issues observed. When you add rdiff files for subsuites, don't forget to put them in the subfolders: diff -u suite/storage_engine/parts/checksum_table.result suite/storage_engine/parts/checksum_table.reject > ../storage/innobase/mysql-test/storage_engine/parts/checksum_table.rdiff Again, mostly failures are mismatches due to different output or unsupported functionality. trx/cons_snapshot_serializable:
It is a bug. Filing as http://bugs.mysql.com/bug.php?id=65146 and adding to disabled.def (don't forget that it should be under trx folder now:
Now, running the whole set:
Much slower than for MyISAM, but that's how it is usually is. Advanced level: MERGEYet more tricks would be required to tune the same suite for the MERGE engine, because now we will also have to think about how a table is created. Start the same way as we started for other engines, by creating the overlay folder: mkdir -p ../storage/myisammrg/mysql-test/storage_engine We know that we'll need INSERT_METHOD and UNION in our table options; in other circumstances, they should have been added to $default_tbl_opts; but we cannot set a global UNION, because it will contain different underlying tables, and since we will be modifying the creation procedure anyway, there is no point at adding INSERT_METHOD here, either.
What happens if we now run the 1st test if we did before? perl ./mtr --suite=storage_engine-myisammrg 1st
That's because we don't have underlying tables under the merge table. We need to modify table creation procedure.
In order to achieve this, we need to override 3 files, and modify our already created ../storage/myisammrg/mysql-test/storage_engine/define_engine.inc. Lets start with the latter. define_engine.inc is the include file which is executed before each test. So, it's the place to put the logic which precedes a test.
Now, it's time for the 3 files to override: cp suite/storage_engine/cleanup_engine.inc ../storage/myisammrg/mysql-test/storage_engine/ cleanup_engine.inc is the file which is executed after each test; so, in ../storage/myisammrg/mysql-test/storage_engine/cleanup_engine.inc I will be dropping my mrg schema:
Now, the actual table creation. Below I will show the changes I had made; in fact, there are many ways to achieve the same goal, probably some of them more efficient. Be creative when the time comes.
We know we also need to modify alter_table.inc, but it's interesting to see if our changes actually work.
Great. Lets now modify ../storage/myisammrg/mysql-test/storage_engine/alter_table.inc:
Now lets try to run the suite:
Not great, but not that bad either, considering. Lets look at the results. alter_table and some other tests produce the following mismatch on SHOW CREATE TABLE:
Quite as expected, since we have additional options on our tables; requires adding an rdiff. alter_table_online:
This is all right I guess. It's good that online alter can be done, right?
Looking earlier in the test output, we find out that we are working with temporary tables here. And there is the bug MySQL:57657 which says that altering a temporary MERGE table is broken in 5.5. Whether to add an rdiff or disable the test – it's a question. I think I will disable, after all, although it's a bit sad. You can choose to be smarter, and since you have your own alter_table.inc anyway, add some logic in there, checking whether a table is temporary or not. create_table:
AS SELECT doesn't work with MERGE tables; we didn't consider it in our simple changes of create_table.inc, because we only do AS SELECT a few times in the suite, so it seems easier just to accept this difference here. Although in general, it's up to the person who modifies the creation procedure. lock: The test is quite messed up, because merge children are locked through the parent tables, which the test of course does not expect. E.g. if it locks two tables and then drops them, it expects that nothing is locked any longer, which is not true for the merge tables. Adding an rdiff, anyway locking is very specific for merge tables and needs to be tested as an engine feature rather than as basic functionality. The rest are usual mismatches due to unsupported functionality etc. MERGE engine doesn't support partitions and transactions, but again, lets see what happens, since it's nearly for free: mkdir ../storage/myisammrg/mysql-test/storage_engine/parts
All tests failed, of course. For all partitioned tables:
Transactional tests run somehow, but of course diffs are as extensive as they were for MyISAM. All this is expected, and can be solved either by removing the nearly created trx and parts subdirs, or adding rdiffs. It seems reasonable to remove parts and keep trx, but with the paranoic assumption that one day an attempt to create a partitioned MERGE table will crash the server, I will keep parts too; anyway they all together take less than a second (rejecting table creation and failing everything with "table doesn't exist" is fast). So, I will add rdiffs for each file. Running all at once now:
|
| Comments |
| Comment by Elena Stepanova [ 2012-03-25 ] |
|
|
| Comment by Elena Stepanova [ 2012-03-26 ] |
|
Whichever approach we choose, we will need |
| Comment by Elena Stepanova [ 2012-04-25 ] |
|
Raw approximation of current coverage on my local tree, on the example of running the suite on MyISAM. It might be inaccurate, but gives the idea (mainly for my future reference) The script runs gcov with function summaries for handler.cc, handler.h, ha_<engine>.cc, ha_engine.h, and for each function stores the number of calls as reported by gcov. If the function names and parameter lists are identical in handler and ha_<engine>, the function is considered the same; so if it's called in ha_<engine> but not in the handler, it is not reported in 'Not called'. Summary from the attached list: "Not called" are the ones I will be working on next. |
| Comment by Rasmus Johansson (Inactive) [ 2012-06-06 ] |
|
In lines with what was agreed on the Maria call 2012-06-05 the next steps are:
|
| Comment by Elena Stepanova [ 2013-01-18 ] |
|
Since the suite proved to be very useful for testing Cassandra and especially LevelDB, it makes sense to make some more changes that will make it far more universal. Additionally, there were some test cases among LevelDB bug reports that are generic enough and might be worth adding to the suite. |
| Comment by Elena Stepanova [ 2013-01-23 ] |
|
I decided for now to go with a simple (and quick) solution, to modify remaining INSERT statements to use an explicit set of columns. It was already partially done before, but many statements remained, especially in inc files. Now all INSERTs (except for those in insert.test and 1st.test that specifically test INSERT INTO <table name> VALUES) should provide the list of columns. It will help with engines like LevelDB (and possibly Cassandra), since now we can modify create_table.inc to add mandatory columns, in case of LevelDB a primary key column when it's missing, and also create a trigger which will populate it upon INSERT. Thus, nearly all test functionality should be applicable. |
| Comment by Elena Stepanova [ 2013-02-03 ] |
|
Pushed the described change into maria/5.5 |