diff options
author | Dan Gudmundsson <[email protected]> | 2010-06-08 13:24:21 +0200 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2010-06-08 13:24:21 +0200 |
commit | cc362ea3985f3d5f20d8f5316059c5ede04e6851 (patch) | |
tree | 1aa5d79c99260940af209657294bfec3db835b54 /lib | |
parent | b8b646ff6780ce1a3d128b22fb90d4ddd75f2ca6 (diff) | |
download | otp-cc362ea3985f3d5f20d8f5316059c5ede04e6851.tar.gz otp-cc362ea3985f3d5f20d8f5316059c5ede04e6851.tar.bz2 otp-cc362ea3985f3d5f20d8f5316059c5ede04e6851.zip |
Release mnesia tests
Diffstat (limited to 'lib')
34 files changed, 22820 insertions, 0 deletions
diff --git a/lib/mnesia/test/Makefile b/lib/mnesia/test/Makefile new file mode 100644 index 0000000000..a4f32e3f78 --- /dev/null +++ b/lib/mnesia/test/Makefile @@ -0,0 +1,118 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= \ + mt \ + mnesia_SUITE \ + mnesia_test_lib \ + mnesia_install_test \ + mnesia_registry_test \ + mnesia_config_test \ + mnesia_frag_test \ + mnesia_inconsistent_database_test \ + mnesia_config_backup \ + mnesia_config_event \ + mnesia_examples_test \ + mnesia_nice_coverage_test \ + mnesia_evil_coverage_test \ + mnesia_evil_backup \ + mnesia_trans_access_test \ + mnesia_dirty_access_test \ + mnesia_atomicity_test \ + mnesia_consistency_test \ + mnesia_isolation_test \ + mnesia_durability_test \ + mnesia_recovery_test \ + mnesia_qlc_test \ + mnesia_schema_recovery_test \ + mnesia_measure_test \ + mnesia_cost \ + mnesia_dbn_meters + +MnesiaExamplesDir := ../examples + +ExampleModules = \ + company \ + company_o \ + bup \ + mnesia_meter \ + mnesia_tpcb +ExamplesHrl = \ + company.hrl \ + company_o.hrl + +ERL_FILES= $(MODULES:%=%.erl) $(ExampleModules:%=$(MnesiaExamplesDir)/%.erl) + +HRL_FILES= mnesia_test_lib.hrl $(ExamplesHrl:%=$(MnesiaExamplesDir)/%) + +TARGET_FILES= \ + $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ExampleModules:%=$(EBIN)/%.$(EMULATOR)) + +INSTALL_PROGS= $(TARGET_FILES) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/mnesia_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +#ERL_COMPILE_FLAGS += + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +tests debug opt: $(TARGET_FILES) + +$(EBIN)/%.beam: $(MnesiaExamplesDir)/%.erl + $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(EBIN) $< + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: opt + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) mnesia.spec mnesia.spec.vxworks $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR) + $(INSTALL_PROGRAM) mt $(INSTALL_PROGS) $(RELSYSDIR) +# chmod -f -R u+w $(RELSYSDIR) +# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + +release_docs_spec: + + diff --git a/lib/mnesia/test/README b/lib/mnesia/test/README new file mode 100644 index 0000000000..e0ced7399d --- /dev/null +++ b/lib/mnesia/test/README @@ -0,0 +1,107 @@ +This directory contains the test suite of Mnesia. +Compile it with "erl -make". + +Test cases are identified with a {Mod, Fun} tuple that maps +to a function Mod:Fun(Config), where the test case hopefully +is implemented. The test suite is organized in a hierarchy +with {mnesia_SUITE, all} as the top. + +The module called mt, implements various convenience functions +to ease up the execution of test cases. It does also provide +aliases for some test cases. For example the atom Mod is an +alias for {Mod, all}, the atom all for {mnesia_SUITE, all}, +evil for mnesia_evil_coverage_test etc. + + mt:struct(TestCase) + + Displays the test case structure from TestCase + and downwards the hierarchy. E.g. mt:struct(all) + will display the entire test suite. + + mt:t(TestCase), mt:t(TestCase, Config) + + Runs a single test case or a hierarchy of test cases. + mt:t(silly) is be a good starter, but you may also + try mt:t(all) directly if you feel lucky. + + The identity of the last run test case and the outcome of + it is stored on file. mt:t() will re-run the last test case. + + The Config argument contains various configuration + parameters for the test cases, such as which nodes that + are available for running the test suite. The default + settings should be enough for the most. Use mt:read_config() + to get the current default setting and change it with + mt:write_config(Config). + + mt:doc(TestCase) + + Generates html documentation for the test suite. + +In order to be able to run the test suite, the Erlang node must +be started with the distribution enabled and the code path must +be set to the mnesia/ebin, mnesia/examples, and mnesia/test +directories. E.g. the following would do: + + erl -sname a -pa $top/examples -pa $top/src -pa $top/ebin + +where $top is the path to the Mnesia installation. Many test +cases needs 2 or 3 nodes. The node names may explicitly be +stated as test suite configuration parameters, but by default +the extra node names are generated. In this example the names +will be: a, a1 and a2. It is enough to start the first node +manually, the extra nodes will automatically be started if +neccessary. + +The attached UNIX shell script mt, does not work on all +platforms, but it may be used as a source for inspiration. It +starts three Erlang nodes in one xterm's each. The main xterm +(a@localhost) logs all output in the Erlang shell to a +file. The file is piped thru grep to easily find successful +test cases (i.e. test cases that encountered an error). + +During development we want to be able to run the test cases +in the debugger. This demands a little bit of preparations: + + - Start the neccessary number of nodes (normally 3). + This may either be done by running the mt script or + by starting the main node and then invoke mt:start_nodes() + to start the extra nodes with slave. + + - Ensure that the nodes are connected. The easiest way to do + this is by invoking mt:ping(). + + - Load all files that needs to be interpreted. This is typically + all Mnesia files plus the test case. By invoking mnesia:ni() + and mnesia:ni([TestModule]) the neccessary modules will be + loaded on all CONNECTED nodes. + +The test case execution is supervised in order to ensure that no test +case exceeds its maximum time limit, which by default is 5 minutes. +When the limit is reached, the running test case gets aborted and the +test server runs the next test case in line. This behaviour is useful +when running the entire test suite during the night, but it is really +annoying during debugging. + + Use the "erl -mnesia_test_timeout" flag to disable the test case + time limit mechanism. + +Some mechanisms in Mnesia are almost impossible to test with a +white box technique. In order to be able to write predictable +test cases which tests the same thing every time it is run, +Mnesia has been instrumented with debug functions. These may be +controlled from a test program. For example to verify that the +commit protocols work it is essential that it is possible to +ensure that we are able to kill Mnesia in the most critical +situations. Normally Mnesia is compiled with the debug +functions disabled and this means that test cases which +requires this functionality will be skipped. The mnesia:ni(), +mentioned above, functions ensures that the interpreted code is +instrumented with Mnesia's debug functionality. The mnesia:nc() +functions compiles Mnesia with the debug setting enabled. + +Happy bug hunting! + + Hakan Mattsson <[email protected]> + + diff --git a/lib/mnesia/test/mnesia.spec b/lib/mnesia/test/mnesia.spec new file mode 100644 index 0000000000..596f8b917d --- /dev/null +++ b/lib/mnesia/test/mnesia.spec @@ -0,0 +1,23 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 2}. +{skip, {mnesia_measure_test, ram_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_only_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, cost, "Takes to long time"}}. +{skip, {mnesia_measure_test, dbn_meters, "Takes to long time"}}. +{skip, {mnesia_measure_test, tpcb, "Takes to long time"}}. +{skip, {mnesia_measure_test, prediction, "Not yet implemented"}}. +{skip, {mnesia_measure_test, consumption, "Not yet implemented"}}. +{skip, {mnesia_measure_test, scalability, "Not yet implemented"}}. +{skip, {mnesia_measure_test, tpcb, "Takes too much time and memory"}}. +{skip, {mnesia_measure_test, measure_all_api_functions, "Not yet implemented"}}. +{skip, {mnesia_measure_test, mnemosyne_vs_mnesia_kernel, "Not yet implemented"}}. +{skip, {mnesia_examples_test, company, "Not yet implemented"}}. +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not yet implemented"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Not yet implemented"}}. +{skip, {mnesia_config_test, max_wait_for_decision, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. diff --git a/lib/mnesia/test/mnesia.spec.vxworks b/lib/mnesia/test/mnesia.spec.vxworks new file mode 100644 index 0000000000..11c01ea3fe --- /dev/null +++ b/lib/mnesia/test/mnesia.spec.vxworks @@ -0,0 +1,362 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 3}. +{diskless, true}. +{skip, {mnesia_measure_test, all, "Too heavy"}}. +%{mnesia_install_test, silly_durability} 'IMPL' +%{mnesia_install_test, silly_move} 'IMPL' +{skip, {mnesia_install_test, silly_upgrade, "Uses disk"}}. +%{mnesia_install_test, conflict} 'IMPL' +%{mnesia_install_test, dist} 'IMPL' +{skip, {mnesia_examples_test, all, "Uses disk"}}. +{skip, {mnesia_nice_coverage_test, all, "Uses disk"}}. + +%{mnesia_evil_coverage_test, system_info} 'IMPL' +%{mnesia_evil_coverage_test, table_info} 'IMPL' +%{mnesia_evil_coverage_test, error_description} 'IMPL' +{skip, {mnesia_evil_coverage_test, db_node_lifecycle, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, local_content, "Uses disk"}}. +%{mnesia_evil_coverage_test, start_and_stop} 'IMPL' +%{mnesia_evil_coverage_test, transaction} 'IMPL' +{skip, {mnesia_evil_coverage_test, checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, global_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, incremental_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, selective_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_errors, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_clear, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_keep, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_recreate, "Uses disk"}}. +{skip, {mnesia_evil_backup, traverse_backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, install_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, uninstall_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_fallback, "Uses disk"}}. +%{mnesia_evil_coverage_test, table_lifecycle} 'IMPL' +{skip, {mnesia_evil_coverage_test, replica_management, "Uses disk"}}. +%{mnesia_evil_coverage_test, change_table_access_mode} 'IMPL' +%{mnesia_evil_coverage_test, change_table_load_order} 'IMPL' +{skip, {mnesia_evil_coverage_test, set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, offline_set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, replica_location, "Uses disk"}}. +%{mnesia_evil_coverage_test, add_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, add_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, add_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, create_live_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, create_live_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, create_live_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, del_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, del_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, del_table_index_disc_only, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_ram, "Uses disk"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_write_ram} 'IMPL' + +{skip, {mnesia_dirty_access_test, dirty_write_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_write_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_update_counter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_set_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_bag_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_iter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_iter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_iter_disc_only, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, admin_tests, "Uses disk"}}. + +%{mnesia_trans_access_test, write} 'IMPL' +%{mnesia_trans_access_test, read} 'IMPL' +%{mnesia_trans_access_test, wread} 'IMPL' +%{mnesia_trans_access_test, delete} 'IMPL' +%{mnesia_trans_access_test, delete_object} 'IMPL' +%{mnesia_trans_access_test, match_object} 'IMPL' +%{mnesia_trans_access_test, all_keys} 'IMPL' +%{mnesia_trans_access_test, index_match_object} 'IMPL' +%{mnesia_trans_access_test, index_read} 'IMPL' +%{mnesia_trans_access_test, index_update_set} 'IMPL' +%{mnesia_trans_access_test, index_update_bag} 'IMPL' +{skip, {mnesia_evil_coverage_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, dump_log, "Uses disk"}}. +%{mnesia_evil_coverage_test, wait_for_tables} 'IMPL' +{skip, {mnesia_evil_coverage_test, force_load_table, "Uses disk"}}. +%{mnesia_evil_coverage_test, user_properties} 'IMPL' +%{mnesia_evil_coverage_test, record_name_dirty_access_ram} 'IMPL' +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc, "Uses disc"}}. +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, snmp_open_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_close_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_next_index} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_row} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_mnesia_key} 'IMPL' +%{mnesia_evil_coverage_test, snmp_update_counter} 'IMPL' +%{mnesia_evil_coverage_test, info} 'IMPL' +%{mnesia_evil_coverage_test, schema_0} 'IMPL' +%{mnesia_evil_coverage_test, schema_1} 'IMPL' +%{mnesia_evil_coverage_test, view_0} 'IMPL' +{skip, {mnesia_evil_coverage_test, view_1, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, view_2, "Uses disk"}}. +%{mnesia_evil_coverage_test, lkill} 'IMPL' +%{mnesia_evil_coverage_test, kill} 'IMPL' + +%{mnesia_config_test, access_module} 'IMPL' +%{mnesia_config_test, auto_repair} 'IMPL' +{skip, {mnesia_config_test, backup_module, "Uses disk"}}. +{skip, {mnesia_config_test, dynamic_connect, "Uses disk"}}. +%{mnesia_config_test, debug} 'IMPL' +%{mnesia_config_test, dir} 'IMPL' +{skip, {mnesia_config_test, dump_log_load_regulation, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_time_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_write_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_update_in_place, "Uses disk"}}. +{skip, {mnesia_config_test, embedded_mnemosyne, "Uses Mnemosyne"}}. +%{mnesia_config_test, event_module} 'IMPL' +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not Yet impl"}}. +%{mnesia_config_test, inconsistent_database} 'IMPL' +{skip, {mnesia_config_test, max_wait_for_decision, "Not Yet impl"}}. +{skip, {mnesia_config_test, start_one_disc_full_then_one_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, start_first_one_disc_less_then_one_disc_full, "Uses disc"}}. +%%{skip, {mnesia_config_test, start_first_one_disc_less_then_two_more_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, schema_location_and_extra_db_nodes_combinations, "Uses disk"}}. +{skip, {mnesia_config_test, table_load_to_disc_less_nodes, "Uses disc"}}. +{skip, {mnesia_config_test, schema_merge, "Uses Disc"}}. +%{mnesia_config_test, unknown_config} 'IMPL' +%{mnesia_registry_test, good_dump} 'IMPL' +%{mnesia_registry_test, bad_dump} 'IMPL' + +%{mnesia_atomicity_test, explicit_abort_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, runtime_error_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, kill_self_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, throw_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, mnesia_down_during_infinite_trans} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wt} 'IMPL' +%{mnesia_atomicity_test, restart_r_one} 'IMPL' +%{mnesia_atomicity_test, restart_w_one} 'IMPL' +%{mnesia_atomicity_test, restart_rt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wr_one} 'IMPL' +%{mnesia_atomicity_test, restart_sw_one} 'IMPL' +%{mnesia_atomicity_test, restart_r_two} 'IMPL' +%{mnesia_atomicity_test, restart_w_two} 'IMPL' +%{mnesia_atomicity_test, restart_rt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wr_two} 'IMPL' +%{mnesia_atomicity_test, restart_sw_two} 'IMPL' + +%{mnesia_isolation_test, no_conflict} 'IMPL' +%{mnesia_isolation_test, simple_queue_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_queue_conflict} 'IMPL' +%{mnesia_isolation_test, simple_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, lock_burst} 'IMPL' +%{mnesia_isolation_test, basic_sticky_functionality} 'IMPL' +%{mnesia_isolation_test, create_table} 'IMPL' +%{mnesia_isolation_test, delete_table} 'IMPL' +%{mnesia_isolation_test, move_table_copy} 'IMPL' +%{mnesia_isolation_test, add_table_index} 'IMPL' +%{mnesia_isolation_test, del_table_index} 'IMPL' +%{mnesia_isolation_test, transform_table} 'IMPL' +%{mnesia_isolation_test, snmp_open_table} 'IMPL' +%{mnesia_isolation_test, snmp_close_table} 'IMPL' +{skip, {mnesia_isolation_test, change_table_copy_type, "Uses disk"}}. +%{mnesia_isolation_test, change_table_access} 'IMPL' +%{mnesia_isolation_test, add_table_copy} 'IMPL' +%{mnesia_isolation_test, del_table_copy} 'IMPL' +{skip, {mnesia_isolation_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_isolation_test, extra_admin_tests, "Uses disk"}}. +%{mnesia_isolation_test, del_table_copy_1} 'IMPL' +%{mnesia_isolation_test, del_table_copy_2} 'IMPL' +%{mnesia_isolation_test, del_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_1} 'IMPL' +%{mnesia_isolation_test, add_table_copy_2} 'IMPL' +%{mnesia_isolation_test, add_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_4} 'IMPL' +%{mnesia_isolation_test, move_table_copy_1} 'IMPL' +%{mnesia_isolation_test, move_table_copy_2} 'IMPL' +%{mnesia_isolation_test, move_table_copy_3} 'IMPL' +%{mnesia_isolation_test, move_table_copy_4} 'IMPL' +%{mnesia_isolation_test, dirty_updates_visible_direct} 'IMPL' +%{mnesia_isolation_test, dirty_reads_regardless_of_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_invisibible_outside_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_visible_inside_trans} 'IMPL' +%{mnesia_isolation_test, write_shadows} 'IMPL' +%{mnesia_isolation_test, delete_shadows} 'IMPL' +%{mnesia_isolation_test, write_delete_shadows_bag} 'IMPL' + +{skip, {mnesia_durability_test, all, "Uses disk "}}. +%{mnesia_durability_test, load_local_contents_directly} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesA} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesB} 'IMPL' +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes1, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes2, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, load_when_last_replica_becomes_available, "Uses disk"}}. +%{skip, {mnesia_durability_test, load_when_we_have_down_from_all_other_replica_nodes, "Uses disk"}}. +%{skip, {mnesia_durability_test, late_load_transforms_into_disc_load, "Uses disc"}}. +%{mnesia_durability_test, late_load_leads_to_hanging} 'IMPL' +%{mnesia_durability_test, force_load_when_nobody_intents_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_has_decided_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_else_already_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_when_we_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_on_a_non_local_table} 'IMPL' +%{mnesia_durability_test, force_load_when_the_table_does_not_exist} 'IMPL' +%{mnesia_durability_test, master_nodes} 'IMPL' +%{mnesia_durability_test, master_on_non_local_tables} 'IMPL' +%{mnesia_durability_test, remote_force_load_with_local_master_node} 'IMPL' +%{mnesia_durability_test, dump_ram_copies} 'IMPL' +%{skip, {mnesia_durability_test, dump_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, dump_disc_only, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_only_copies, "Uses disc"}}. + +{skip, {mnesia_recovery_test, mnesia_down, "Uses Disk"}}. +%{mnesia_recovery_test, no_master_2} 'IMPL' +%{mnesia_recovery_test, no_master_3} 'IMPL' +%{mnesia_recovery_test, one_master_2} 'IMPL' +%{mnesia_recovery_test, one_master_3} 'IMPL' +%{mnesia_recovery_test, two_master_2} 'IMPL' +%{mnesia_recovery_test, two_master_3} 'IMPL' +%{mnesia_recovery_test, all_master_2} 'IMPL' +%{mnesia_recovery_test, all_master_3} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_disk_ram, "Uses disk"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_init_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_tm_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, explicit_stop_during_snmp} 'IMPL' + +{skip, {mnesia_recovery_test, schema_trans, "Uses Disk, needs disk log"}}. +{skip, {mnesia_recovery_test, async_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sync_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sym_trans, "Uses disc"}}. +{skip, {mnesia_recovery_test, asym_trans, "Uses disc"}}. + +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not Yet impl"}}. +{skip, {mnesia_recovery_test, after_corrupt_files, "Uses disk"}}. + +%{mnesia_evil_coverage_test, subscriptions} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_ok} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_child_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_parent_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_dies} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_sync_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_async_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_ets} 'IMPL' + +{skip, {mnesia_recovery_test, disc_less, "Uses disc (on the other nodes)"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not Yet impl"}}. +%{mnesia_consistency_test, consistency_after_restart_1_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_restart_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_1_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_2_ram, "Uses disk"}}. +%{mnesia_consistency_test, consistency_after_add_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_add_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_activation, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc_only, "Uses disc"}}. +%%{mnesia_consistency_test, updates_during_checkpoint_activation_3_ram} 'IMPL' +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, inst_fallback_process_dies, "Uses disk"}}. +{skip, {mnesia_consistency_test, fatal_when_inconsistency, "Uses disk"}}. +{skip, {mnesia_consistency_test, after_delete, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_switch, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_abort, "Uses disk"}}. +%{mnesia_consistency_test, cause_switch_after} 'IMPL' +%{mnesia_consistency_test, cause_abort_before} 'IMPL' +%{mnesia_consistency_test, cause_abort_after} 'IMPL' +%{mnesia_consistency_test, change_schema_before} 'IMPL' +%{mnesia_consistency_test, change_schema_after} 'IMPL' + diff --git a/lib/mnesia/test/mnesia_SUITE.erl b/lib/mnesia/test/mnesia_SUITE.erl new file mode 100644 index 0000000000..b28deaf330 --- /dev/null +++ b/lib/mnesia/test/mnesia_SUITE.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_SUITE). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify that Mnesia really is a distributed real-time DBMS", + "This is the test suite of the Mnesia DBMS. The test suite", + "covers many aspects of usage and is indended to be developed", + "incrementally. The test suite is divided into a hierarchy of test", + "suites where the leafs actually implements the test cases.", + "The intention of each test case and sub test suite can be", + "read in comments where they are implemented or in worst cases", + "from their long mnemonic names. ", + "", + "The most simple test case of them all is called 'silly'", + "and is useful to run now and then, e.g. when some new fatal", + "bug has been introduced. It may be run even if Mnesia is in", + "such a bad shape that the test machinery cannot be used.", + "NB! Invoke the function directly with mnesia_SUITE:silly()", + "and do not involve the normal test machinery."]; +all(suite) -> + [ + light, + medium, + heavy, + clean_up_suite + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly() -> + mnesia_install_test:silly(). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +light(doc) -> + ["The 'light' test suite runs a selected set of test suites and is", + "intended to be the smallest test suite that is meaningful", + "to run. It starts with an installation test (which in essence is the", + "'silly' test case) and then it covers all functions in the API in", + "various depths. All configuration parameters and examples are also", + "covered."]; +light(suite) -> + [ + install, + nice, + evil, + {mnesia_frag_test, light}, + qlc, + registry, + config, + examples + ]. + +install(suite) -> + [{mnesia_install_test, all}]. + +nice(suite) -> + [{mnesia_nice_coverage_test, all}]. + +evil(suite) -> + [{mnesia_evil_coverage_test, all}]. + +qlc(suite) -> + [{mnesia_qlc_test, all}]. + +registry(suite) -> + [{mnesia_registry_test, all}]. + +config(suite) -> + [{mnesia_config_test, all}]. + +examples(suite) -> + [{mnesia_examples_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +medium(doc) -> + ["The 'medium' test suite verfies the ACID (atomicity, consistency", + "isolation and durability) properties and various recovery scenarios", + "These tests may take quite while to run."]; +medium(suite) -> + [ + install, + atomicity, + isolation, + durability, + recovery, + consistency, + {mnesia_frag_test, medium} + ]. + +atomicity(suite) -> + [{mnesia_atomicity_test, all}]. + +isolation(suite) -> + [{mnesia_isolation_test, all}]. + +durability(suite) -> + [{mnesia_durability_test, all}]. + +recovery(suite) -> + [{mnesia_recovery_test, all}]. + +consistency(suite) -> + [{mnesia_consistency_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +heavy(doc) -> + ["The 'heavy' test suite runs some resource consuming tests and", + "benchmarks"]; +heavy(suite) -> + [measure]. + +measure(suite) -> + [{mnesia_measure_test, all}]. + +prediction(suite) -> + [{mnesia_measure_test, prediction}]. + +fairness(suite) -> + [{mnesia_measure_test, fairness}]. + +benchmarks(suite) -> + [{mnesia_measure_test, benchmarks}]. + +consumption(suite) -> + [{mnesia_measure_test, consumption}]. + +scalability(suite) -> + [{mnesia_measure_test, scalability}]. + + +clean_up_suite(doc) -> ["Not a test case only kills mnesia and nodes, that where" + "started during the tests"]; +clean_up_suite(suite) -> + []; +clean_up_suite(Config) when is_list(Config)-> + mnesia:kill(), + Slaves = mnesia_test_lib:lookup_config(nodenames, Config), + Nodes = lists:delete(node(), Slaves), + rpc:multicall(Nodes, erlang, halt, []), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +otp_r4b(doc) -> + ["This test suite is an extract of the grand Mnesia suite", + "it contains OTP R4B specific test cases"]; +otp_r4b(suite) -> + [ + {mnesia_config_test, access_module}, + {mnesia_config_test, dump_log_load_regulation}, + {mnesia_config_test, embedded_mnemosyne}, + {mnesia_config_test, ignore_fallback_at_startup}, + {mnesia_config_test, max_wait_for_decision}, + {mnesia_consistency_test, consistency_after_restore}, + {mnesia_evil_backup, restore}, + {mnesia_evil_coverage_test, offline_set_master_nodes}, + {mnesia_evil_coverage_test, record_name}, + {mnesia_evil_coverage_test, user_properties}, + {mnesia_registry_test, all}, + otp_2363 + ]. + +otp_2363(doc) -> + ["Index on disc only tables"]; +otp_2363(suite) -> + [ + {mnesia_dirty_access_test, dirty_index_match_object_disc_only}, + {mnesia_dirty_access_test,dirty_index_read_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_bag_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_set_disc_only}, + {mnesia_evil_coverage_test, create_live_table_index_disc_only} + ]. + + + diff --git a/lib/mnesia/test/mnesia_atomicity_test.erl b/lib/mnesia/test/mnesia_atomicity_test.erl new file mode 100644 index 0000000000..645c203a91 --- /dev/null +++ b/lib/mnesia/test/mnesia_atomicity_test.erl @@ -0,0 +1,839 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_atomicity_test). +-author('[email protected]'). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify atomicity of transactions", + "Verify that transactions are atomic, i.e. either all operations", + "in a transaction will be performed or none of them. It must be", + "assured that no partitially completed operations leaves any", + "effects in the database."]; +all(suite) -> + [ + explicit_abort_in_middle_of_trans, + runtime_error_in_middle_of_trans, + kill_self_in_middle_of_trans, + throw_in_middle_of_trans, + mnesia_down_in_middle_of_trans + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +explicit_abort_in_middle_of_trans(suite) -> []; +explicit_abort_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = explicit_abort_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(abort_by_purpose) %does that stop the process A ??? + end, + ?match_receive({A, {aborted, abort_by_purpose}}), + + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +runtime_error_in_middle_of_trans(suite) -> []; +runtime_error_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = runtime_error_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + erlang:error(foo), % that should provoke a runtime error + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, _Reason}}), + + %?match_receive({A, {'EXIT', Msg1}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +kill_self_in_middle_of_trans(suite) -> []; +kill_self_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = kill_self_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(self(), kill), % that should kill the process himself + % - poor guy ! + mnesia:write(Rec1C) + end, + %% + %% exit(.., kill) : the transaction can't trap this error - thus no + %% proper result can be send by the test server + + % ?match_receive({A, {aborted, Reason}}), + + ?match_receive({'EXIT', _Pid, killed}), % A is killed and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +throw_in_middle_of_trans(suite) -> []; +throw_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = throw_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + throw(exit_transactian_by_a_throw), + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, {throw, exit_transactian_by_a_throw}}}), + % A ! end_trans, % is A still alive ? + % ?match_receive({A, {atomic, end_trans}}), % {'EXIT', Pid, normal} + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_in_middle_of_trans(suite) -> + [ + mnesia_down_during_infinite_trans, + lock_waiter, + restart_check + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_infinite_trans(suite) -> []; +mnesia_down_during_infinite_trans(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = mnesia_down_during_infinite_trans, + + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {ram_copies, [Node1, Node2]}])), + %% Start a transaction on one node + {success, [A2, A1]} = ?start_activities([Node2, Node1]), + %% Start order of the transactions are important + %% We also needs to sync the tid counter + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, test_ok}) end)), + mnesia_test_lib:start_sync_transactions([A2, A1]), + + %% Obtain a write lock and wait forever + RecA = {Tab, 1, test_not_ok}, + A1 ! fun() -> mnesia:write(RecA) end, + ?match_receive({A1, ok}), + + A1 ! fun() -> process_flag(trap_exit, true), timer:sleep(infinity) end, + ?match_receive(timeout), + + %% Try to get read lock, but gets queued + A2 ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive(timeout), + + %% Kill Mnesia on other node + mnesia_test_lib:kill_mnesia([Node1]), + + %% Second transaction gets the read lock + ?match_receive({A2, [{Tab, 1, test_ok}]}), + exit(A1, kill), % Needed since we trap exit + + ?verify_mnesia([Node2], [Node1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_waiter(doc) -> + ["The purpose of this test case is to test the following situation:", + "process B locks an object, process A accesses that object as", + "well, but A has to wait for the lock to be released. Then", + "mnesia of B goes down. Question: will A get the lock ?", + "important: the transaction of A is the oldest one !!! (= a little tricky)", + "", + "several different access operations shall be tested", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread"]; +lock_waiter(suite) -> + [ + lock_waiter_sw_r, + lock_waiter_sw_rt, + lock_waiter_sw_wt, + lock_waiter_wr_r, + lock_waiter_srw_r, + lock_waiter_sw_sw, + lock_waiter_sw_w, + lock_waiter_sw_wr, + lock_waiter_sw_srw, + lock_waiter_wr_wt, + lock_waiter_srw_wt, + lock_waiter_wr_sw, + lock_waiter_srw_sw, + lock_waiter_wr_w, + lock_waiter_srw_w, + lock_waiter_r_sw, + lock_waiter_r_w, + lock_waiter_r_wt, + lock_waiter_rt_sw, + lock_waiter_rt_w, + lock_waiter_rt_wt, + lock_waiter_wr_wr, + lock_waiter_srw_srw, + lock_waiter_wt_r, + lock_waiter_wt_w, + lock_waiter_wt_rt, + lock_waiter_wt_wt, + lock_waiter_wt_wr, + lock_waiter_wt_srw, + lock_waiter_wt_sw, + lock_waiter_w_wr, + lock_waiter_w_srw, + lock_waiter_w_sw, + lock_waiter_w_r, + lock_waiter_w_w, + lock_waiter_w_rt, + lock_waiter_w_wt + ]. + +lock_waiter_sw_r(suite) -> []; +lock_waiter_sw_r(Config) when is_list(Config) -> + start_lock_waiter(sw, r, Config). + +lock_waiter_sw_rt(suite) -> []; +lock_waiter_sw_rt(Config) when is_list(Config) -> + start_lock_waiter(sw, rt, Config). + +lock_waiter_sw_wt(suite) -> []; +lock_waiter_sw_wt(Config) when is_list(Config) -> + start_lock_waiter(sw, wt,Config). + +lock_waiter_wr_r(suite) -> []; +lock_waiter_wr_r(Config) when is_list(Config) -> + start_lock_waiter(wr, r, Config). + +lock_waiter_srw_r(suite) -> []; +lock_waiter_srw_r(Config) when is_list(Config) -> + start_lock_waiter(srw, r, Config). + +lock_waiter_sw_sw(suite) -> []; +lock_waiter_sw_sw(Config) when is_list(Config) -> + start_lock_waiter(sw, sw,Config). + +lock_waiter_srw_srw(suite) -> []; +lock_waiter_srw_srw(Config) when is_list(Config) -> + start_lock_waiter(srw, srw,Config). + +lock_waiter_wr_wr(suite) -> []; +lock_waiter_wr_wr(Config) when is_list(Config) -> + start_lock_waiter(wr, wr,Config). + +lock_waiter_sw_w(suite) -> []; +lock_waiter_sw_w(Config) when is_list(Config) -> + start_lock_waiter(sw, w,Config). + +lock_waiter_sw_wr(suite) -> []; +lock_waiter_sw_wr(Config) when is_list(Config) -> + start_lock_waiter(sw, wr,Config). + +lock_waiter_sw_srw(suite) -> []; +lock_waiter_sw_srw(Config) when is_list(Config) -> + start_lock_waiter(sw, srw,Config). + +lock_waiter_wr_wt(suite) -> []; +lock_waiter_wr_wt(Config) when is_list(Config) -> + start_lock_waiter(wr, wt,Config). + +lock_waiter_srw_wt(suite) -> []; +lock_waiter_srw_wt(Config) when is_list(Config) -> + start_lock_waiter(srw, wt,Config). + +lock_waiter_wr_sw(suite) -> []; +lock_waiter_wr_sw(Config) when is_list(Config) -> + start_lock_waiter(wr, sw,Config). + +lock_waiter_srw_sw(suite) -> []; +lock_waiter_srw_sw(Config) when is_list(Config) -> + start_lock_waiter(srw, sw,Config). + +lock_waiter_wr_w(suite) -> []; +lock_waiter_wr_w(Config) when is_list(Config) -> + start_lock_waiter(wr, w,Config). + +lock_waiter_srw_w(suite) -> []; +lock_waiter_srw_w(Config) when is_list(Config) -> + start_lock_waiter(srw, w,Config). + +lock_waiter_r_sw(suite) -> []; +lock_waiter_r_sw(Config) when is_list(Config) -> + start_lock_waiter(r, sw,Config). + +lock_waiter_r_w(suite) -> []; +lock_waiter_r_w(Config) when is_list(Config) -> + start_lock_waiter(r, w,Config). + +lock_waiter_r_wt(suite) -> []; +lock_waiter_r_wt(Config) when is_list(Config) -> + start_lock_waiter(r, wt,Config). + +lock_waiter_rt_sw(suite) -> []; +lock_waiter_rt_sw(Config) when is_list(Config) -> + start_lock_waiter(rt, sw,Config). + +lock_waiter_rt_w(suite) -> []; +lock_waiter_rt_w(Config) when is_list(Config) -> + start_lock_waiter(rt, w,Config). + +lock_waiter_rt_wt(suite) -> []; +lock_waiter_rt_wt(Config) when is_list(Config) -> + start_lock_waiter(rt, wt,Config). + +lock_waiter_wt_r(suite) -> []; +lock_waiter_wt_r(Config) when is_list(Config) -> + start_lock_waiter(wt, r,Config). + +lock_waiter_wt_w(suite) -> []; +lock_waiter_wt_w(Config) when is_list(Config) -> + start_lock_waiter(wt, w,Config). + +lock_waiter_wt_rt(suite) -> []; +lock_waiter_wt_rt(Config) when is_list(Config) -> + start_lock_waiter(wt, rt,Config). + +lock_waiter_wt_wt(suite) -> []; +lock_waiter_wt_wt(Config) when is_list(Config) -> + start_lock_waiter(wt, wt,Config). + +lock_waiter_wt_wr(suite) -> []; +lock_waiter_wt_wr(Config) when is_list(Config) -> + start_lock_waiter(wt, wr,Config). + +lock_waiter_wt_srw(suite) -> []; +lock_waiter_wt_srw(Config) when is_list(Config) -> + start_lock_waiter(wt, srw,Config). + +lock_waiter_wt_sw(suite) -> []; +lock_waiter_wt_sw(Config) when is_list(Config) -> + start_lock_waiter(wt, sw,Config). + +lock_waiter_w_wr(suite) -> []; +lock_waiter_w_wr(Config) when is_list(Config) -> + start_lock_waiter(w, wr, Config). + +lock_waiter_w_srw(suite) -> []; +lock_waiter_w_srw(Config) when is_list(Config) -> + start_lock_waiter(w, srw, Config). + +lock_waiter_w_sw(suite) -> []; +lock_waiter_w_sw(Config) when is_list(Config) -> + start_lock_waiter(w, sw, Config). + +lock_waiter_w_r(suite) -> []; +lock_waiter_w_r(Config) when is_list(Config) -> + start_lock_waiter(w, r, Config). + +lock_waiter_w_w(suite) -> []; +lock_waiter_w_w(Config) when is_list(Config) -> + start_lock_waiter(w, w, Config). + +lock_waiter_w_rt(suite) -> []; +lock_waiter_w_rt(Config) when is_list(Config) -> + start_lock_waiter(w, rt, Config). + +lock_waiter_w_wt(suite) -> []; +lock_waiter_w_wt(Config) when is_list(Config) -> + start_lock_waiter(w, wt, Config). + +start_lock_waiter(BlockOpA, BlockOpB, Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + TabName = mk_tab_name(lock_waiter_), + ?match({atomic, ok}, mnesia:create_table(TabName, + [{ram_copies, [N1, N2]}])), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + rpc:call(N2, ?MODULE, sync_tid_release, []), + + Tester = self(), + Fun_A =fun() -> + NewCounter = incr_restart_counter(), + if + NewCounter == 1 -> + Tester ! go_ahead_test, + receive go_ahead -> ok end; + true -> ok + end, + lock_waiter_fun(BlockOpA, TabName, a), + NewCounter + end, + + %% it's not possible to just spawn the transaction, because + %% the result shall be evaluated + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + + ?match(ok, receive go_ahead_test -> ok after 10000 -> timeout end), + + mnesia_test_lib:sync_trans_tid_serial([N1, N2]), + + Fun_B = fun() -> + lock_waiter_fun(BlockOpB, TabName, b), + A ! go_ahead, + wait(infinity) + end, + + B = spawn_link(N2, mnesia, transaction, [Fun_B, 100]), + + io:format("waiting for A (~p on ~p) to be in the queue ~n", [A, [N1, N2]]), + wait_for_a(A, [N1, N2]), + + io:format("Queus ~p~n", + [[{N,rpc:call(N, mnesia, system_info, [lock_queue])} || N <- Nodes]]), + + KillNode = node(B), + io:format("A was in the queue, time to kill Mnesia on B's node (~p on ~p)~n", + [B, KillNode]), + + mnesia_test_lib:kill_mnesia([KillNode]), % kill mnesia of fun B + + %% Read Ops does not need to be restarted + ExpectedCounter = + if + BlockOpA == sw, BlockOpB == w -> 1; + BlockOpA == sw, BlockOpB == wt -> 1; + BlockOpA == sw, BlockOpB == wr -> 1; + BlockOpA == srw, BlockOpB == w -> 1; + BlockOpA == srw, BlockOpB == wt -> 1; + BlockOpA == srw, BlockOpB == wr -> 1; + BlockOpA == r, BlockOpB /= sw -> 1; + BlockOpA == rt, BlockOpB /= sw -> 1; + true -> 2 + end, + ?match_multi_receive([{'EXIT', A, {atomic, ExpectedCounter}}, + {'EXIT', B, killed}]), + + %% the expected result depends on the transaction of + %% fun A - when that doesn't change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + ExpectedResult = + case BlockOpA of + w -> {TabName, 1, a}; + sw ->{TabName, 1, a}; + _all_other -> {TabName, 1, c} + end, + + ?match({atomic, [ExpectedResult]}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end, 100)), + ?verify_mnesia([N1], [N2]). + +mk_tab_name(Prefix) -> + {Mega, Sec, Micro} = erlang:now(), + list_to_atom(lists:concat([Prefix , Mega, '_', Sec, '_', Micro])). + +lock_waiter_fun(Op, TabName, Val) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read({TabName, 1}); + w -> mnesia:write({TabName, 1, Val}); + wr -> mnesia:wread({TabName, 1}); + srw -> mnesia:read(TabName, 1, sticky_write); + sw -> mnesia:s_write({TabName, 1, Val}) + end. + +wait_for_a(Pid, Nodes) -> + wait_for_a(Pid, Nodes, 5). + +wait_for_a(_P, _N, 0) -> + ?error("Timeout while waiting for lock on a~n", []); + +wait_for_a(Pid, Nodes, Count) -> + %% io:format("WAIT_FOR_A ~p ON ~w ~n", [Pid, Nodes]), + List = [rpc:call(N, mnesia, system_info, [lock_queue]) || N <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, Count). + +check_q(Pid, [{{_Oid,_Tid}, _Op, Pid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [_ | Tail], N, Count) -> + check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_a(Pid, N, Count - 1). + +perform_restarted_transaction (Fun_Trans) -> + %% the result of the transaction shall be: + %% - undefined (if the transaction was never executed) + %% - Times ( number of times that the transaction has been executed) + + Result = mnesia:transaction(Fun_Trans, 100), + exit(Result). + +%% Returns new val +incr_restart_counter() -> + NewCount = + case get(count_restart_of_transaction) of + undefined -> 1; + OldCount -> OldCount + 1 + end, + put(count_restart_of_transaction, NewCount), + NewCount. + +wait(Mseconds) -> + receive + after Mseconds -> ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +restart_check (doc) -> + [ + "test case:'A' performs a transaction on a table which", + "is only replicated on node B. During that transaction", + "mnesia on node B is killed. The transaction of A should", + "be stopped, since there is no further replica", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread,"]; +restart_check(suite) -> + [ + restart_r_one, + restart_w_one, + restart_rt_one, + restart_wt_one, + restart_wr_one, + restart_sw_one, + restart_r_two, + restart_w_two, + restart_rt_two, + restart_wt_two, + restart_wr_two, + restart_sw_two + ]. + +restart_r_one(suite) -> []; +restart_r_one(Config) when is_list(Config) -> + start_restart_check(r, one, Config). + +restart_w_one(suite) -> []; +restart_w_one(Config) when is_list(Config) -> + start_restart_check(w, one, Config). + +restart_rt_one(suite) -> []; +restart_rt_one(Config) when is_list(Config) -> + start_restart_check(rt, one, Config). + +restart_wt_one(suite) -> []; +restart_wt_one(Config) when is_list(Config) -> + start_restart_check(wt, one, Config). + +restart_wr_one(suite) -> []; +restart_wr_one(Config) when is_list(Config) -> + start_restart_check(wr, one, Config). + +restart_sw_one(suite) -> []; +restart_sw_one(Config) when is_list(Config) -> + start_restart_check(sw, one, Config). + +restart_r_two(suite) -> []; +restart_r_two(Config) when is_list(Config) -> + start_restart_check(r, two, Config). + +restart_w_two(suite) -> []; +restart_w_two(Config) when is_list(Config) -> + start_restart_check(w, two, Config). + +restart_rt_two(suite) -> []; +restart_rt_two(Config) when is_list(Config) -> + start_restart_check(rt, two, Config). + +restart_wt_two(suite) -> []; +restart_wt_two(Config) when is_list(Config) -> + start_restart_check(wt, two, Config). + +restart_wr_two(suite) -> []; +restart_wr_two(Config) when is_list(Config) -> + start_restart_check(wr, two, Config). + +restart_sw_two(suite) -> []; +restart_sw_two(Config) when is_list(Config) -> + start_restart_check(sw, two, Config). + +start_restart_check(RestartOp, ReplicaNeed, Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + + {TabName, _TabNodes} = create_restart_table(ReplicaNeed, Nodes), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + + %% Really sync tid_release + rpc:multicall([N2,N3], ?MODULE, sync_tid_release, []), + Coord = self(), + + Fun_A = fun() -> + NewCounter = incr_restart_counter(), + case NewCounter of + 1 -> + mnesia:write({TabName, 1, d}), + %% send a message to the test proc + Coord ! {self(),fun_a_is_blocked}, + receive go_ahead -> ok end; + _ -> + %% the fun will NOT be blocked here + restart_fun_A(RestartOp, TabName) + end, + NewCounter + end, + + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + ?match_receive({A,fun_a_is_blocked}), + + %% mnesia shall be killed at that node, where A is reading + %% the information from + kill_where_to_read(TabName, N1, [N2, N3]), + + %% wait some time to let mnesia go down and spread those news around + %% fun A shall be able to finish its job before being restarted + wait(500), + A ! go_ahead, + + %% the sticky write doesnt work on remote nodes !!! + ExpectedMsg = + case RestartOp of + sw when ReplicaNeed == two -> + {'EXIT',A,{aborted, {not_local, TabName}}}; + _all_other -> + case ReplicaNeed of + one -> + {'EXIT',A,{aborted, {no_exists, TabName}}}; + two -> + {'EXIT',A,{atomic, 2}} + end + end, + + ?match_receive(ExpectedMsg), + + %% now mnesia has to be started again on the node KillNode + %% because the next test suite will need it + ?match([], mnesia_test_lib:start_mnesia(Nodes, [TabName])), + + + %% the expected result depends on the transaction of + %% fun A - when that doesnt change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + + ExpectedResult = + case ReplicaNeed of + one -> + []; + two -> + case RestartOp of + w -> [{TabName, 1, a}]; + _ ->[ {TabName, 1, c}] + end + end, + + ?match({atomic, ExpectedResult}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end,100)), + ?verify_mnesia(Nodes, []). + +create_restart_table(ReplicaNeed, [_N1, N2, N3]) -> + TabNodes = + case ReplicaNeed of + one -> [N2]; + two -> [N2, N3] + end, + TabName = mk_tab_name(restart_check_), + ?match({atomic, ok}, mnesia:create_table(TabName, [{ram_copies, TabNodes}])), + {TabName, TabNodes}. + +restart_fun_A(Op, TabName) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read( {TabName, 1}); + w -> mnesia:write({TabName, 1, a}); + wr -> mnesia:wread({TabName, 1}); + sw -> mnesia:s_write({TabName, 1, a}) + end. + +kill_where_to_read(TabName, N1, Nodes) -> + Read = rpc:call(N1,mnesia,table_info, [TabName, where_to_read]), + case lists:member(Read, Nodes) of + true -> + mnesia_test_lib:kill_mnesia([Read]); + false -> + ?error("Fault while killing Mnesia: ~p~n", [Read]), + mnesia_test_lib:kill_mnesia(Nodes) + end. + +sync_tid_release() -> + sys:get_status(whereis(mnesia_tm)), + sys:get_status(whereis(mnesia_locker)), + ok. + diff --git a/lib/mnesia/test/mnesia_config_backup.erl b/lib/mnesia/test/mnesia_config_backup.erl new file mode 100644 index 0000000000..a33ec6ac5c --- /dev/null +++ b/lib/mnesia/test/mnesia_config_backup.erl @@ -0,0 +1,105 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_backup). +-author('[email protected]'). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% This module is used for testing the backup module config parameter. +%% +%% This module is an impostor for the mnesia_backup module. +%% +%% +%% Original doc below: +%% +%% This module contains one implementation of callback functions +%% used by Mnesia at backup and restore. The user may however +%% write an own module the same interface as mnesia_backup and +%% configure Mnesia so the alternate module performs the actual +%% accesses to the backup media. This means that the user may put +%% the backup on medias that Mnesia does not know about, possibly +%% on hosts where Erlang is not running. +%% +%% The OpaqueData argument is never interpreted by other parts of +%% Mnesia. It is the property of this module. Alternate implementations +%% of this module may have different interpretations of OpaqueData. +%% The OpaqueData argument given to open_write/1 and open_read/1 +%% are forwarded directly from the user. +%% +%% All functions must return {ok, NewOpaqueData} or {error, Reason}. +%% +%% The NewOpaqueData arguments returned by backup callback functions will +%% be given as input when the next backup callback function is invoked. +%% If any return value does not match {ok, _} the backup will be aborted. +%% +%% The NewOpaqueData arguments returned by restore callback functions will +%% be given as input when the next restore callback function is invoked +%% If any return value does not match {ok, _} the restore will be aborted. +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-export([ + open_write/1, write/2, commit_write/1, abort_write/1, + open_read/1, read/1, close_read/1 + ]). + +-record(backup, {name, mode, items}). + +open_write(Name) -> + file:delete(Name), + {ok, #backup{name = Name, mode = write, items = []}}. + +write(Opaque, Item) when Opaque#backup.mode == write -> + %% Build the list in reverse order + {ok, Opaque#backup{items = [Item | Opaque#backup.items]}}. + +commit_write(Opaque) when Opaque#backup.mode == write -> + Bin = term_to_binary(Opaque#backup.items), + case file:write_file(Opaque#backup.name, Bin) of + ok -> + {ok, Opaque#backup{mode = closed, items = []}}; + {error, Reason} -> + {error, {commit_write, Reason}} + end. + +abort_write(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. + +open_read(Name) -> + case file:read_file(Name) of + {ok, Bin} -> + ReverseList = binary_to_term(Bin), + List = lists:reverse(ReverseList), + {ok, #backup{name = Name, mode = read, items = List}}; + {error, Reason} -> + {error, {open_read, Reason}} + end. + +read(Opaque) when Opaque#backup.mode == read -> + case Opaque#backup.items of + [Head | Tail] -> + {ok, Opaque#backup{items = Tail}, Head}; + [] -> + {ok, Opaque#backup{mode = closed}, []} + end. + +close_read(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. diff --git a/lib/mnesia/test/mnesia_config_event.erl b/lib/mnesia/test/mnesia_config_event.erl new file mode 100644 index 0000000000..6c1dea7ed5 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_event.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_event). +-author('[email protected]'). + +-behaviour(gen_event). + +%% +%% This module was stolen from Mnesia +%% + + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({get_log, Pid}, State) -> + Pid ! {log, State}, + {ok, State}; +handle_any_event(Msg, State) -> + io:format("Got event: ~p~n", [Msg]), + {ok, [Msg | State]}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl new file mode 100644 index 0000000000..7b62c63a62 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_test.erl @@ -0,0 +1,1466 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_test). +-author('[email protected]'). + +-include("mnesia_test_lib.hrl"). + +-record(test_table,{i,a1,a2,a3}). +-record(test_table2,{i, b}). + +-export([ + all/1, + access_module/1, + auto_repair/1, + backup_module/1, + debug/1, + dir/1, + dump_log_load_regulation/1, + dump_log_thresholds/1, + dump_log_update_in_place/1, + embedded_mnemosyne/1, + event_module/1, + ignore_fallback_at_startup/1, + inconsistent_database/1, + max_wait_for_decision/1, + send_compressed/1, + + app_test/1, + schema_config/1, + schema_merge/1, + unknown_config/1, + + dump_log_time_threshold/1, + dump_log_write_threshold/1, + + start_one_disc_full_then_one_disc_less/1, + start_first_one_disc_less_then_one_disc_full/1, + start_first_one_disc_less_then_two_more_disc_less/1, + schema_location_and_extra_db_nodes_combinations/1, + table_load_to_disc_less_nodes/1, + dynamic_connect/1, + dynamic_basic/1, + dynamic_ext/1, + dynamic_bad/1, + + init_per_testcase/2, + fin_per_testcase/2, + c_nodes/0 + ]). + +-export([check_logs/1]). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire_schema(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema], + N, Config, ?FILE, ?LINE)). +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(trans(Fun), + ?match({atomic, ok}, mnesia:transaction(Fun))). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +all(doc) -> + [ + "Test all configuration parameters", + "Perform an exhaustive test of all the various parameters that", + "may be used to configure the Mnesia application.", + "", + "Hint: Check out the unofficial function mnesia:start/1.", + " But be careful to cleanup all configuration parameters", + " afterwards since the rest of the test suite may rely on", + " these default configurations. Perhaps it is best to run", + " these tests in a separate node which is dropped afterwards.", + "Are really all configuration parameters covered?"]; + +all(suite) -> + [ + access_module, + auto_repair, + backup_module, + debug, + dir, + dump_log_load_regulation, + dump_log_thresholds, + dump_log_update_in_place, + embedded_mnemosyne, + event_module, + ignore_fallback_at_startup, + inconsistent_database, + max_wait_for_decision, + send_compressed, + + app_test, + schema_config, + unknown_config + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +access_module(doc) -> + ["Replace the activity access module with another module and ", + "use it to read and write to some alternate table storage"]; +access_module(suite) -> []; +access_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{access_module, mnesia_frag}])), + + ?match(mnesia_frag, mnesia:system_info(access_module)), + + access_tab(ram_copies, Nodes), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + access_tab(disc_copies, Nodes) + , access_tab(disc_only_copies, Nodes) + end, + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +access_tab(Storage, Nodes) -> + Tab = list_to_atom(lists:concat([access_tab_, Storage])), + RecName = some_access, + Attr = val, + TabDef = [{Storage, Nodes}, + {type, bag}, + {index, [Attr]}, + {record_name, RecName}], + ?match({atomic,ok}, mnesia:create_table(Tab, TabDef)), + + Activity = fun(Kind) -> + A = [Kind, Tab, RecName, Attr, Nodes], + io:format("kind: ~w, storage: ~w~n", [Kind, Storage]), + mnesia:activity(Kind, fun do_access/5, A) + end, + ModActivity = fun(Kind, M) -> + io:format("kind: ~w, storage: ~w. module: ~w~n", + [Kind, Storage, M]), + A = [Kind, Tab, RecName, Attr, Nodes], + mnesia:activity(Kind, fun do_access/5, A, M) + end, + ?match(ok, Activity(transaction)), + ?match(ok, Activity({transaction, 47})), + ?match(ok, ModActivity(transaction, mnesia)), + ?match(ok, ModActivity(transaction, mnesia_frag)), + + ?match(ok, Activity(async_dirty)), + ?match(ok, Activity(sync_dirty)), + case Storage of + ram_copies -> + ?match(ok, Activity(ets)); + _ -> + ignore + end. + +do_access(Kind, Tab, RecName, Attr, Nodes) -> + Tens = lists:sort([{RecName, 1, 10}, {RecName, 3, 10}]), + {OptNodes, OptTens} = + case Kind of + transaction -> {Nodes, Tens}; + {transaction, _} -> {Nodes, Tens}; + async_dirty -> {[], Tens}; + sync_dirty -> {[], Tens}; + ets -> {[], []} + end, + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:write(Tab, {RecName, 1, 10}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 20}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 21}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 22}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 3, 10}, write)), + + Twos = [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}], + ?match(Twos, lists:sort(mnesia:read(Tab, 2, read))), + + ?match(ok, mnesia:delete_object(Tab, {RecName, 2, 21}, sticky_write)), + + TenPat = {RecName, '_', 10}, + ?match(Tens, lists:sort(mnesia:match_object(Tab, TenPat, read))), + ?match(OptTens, lists:sort(mnesia:index_match_object(Tab, TenPat, Attr, read) )), + ?match(OptTens, lists:sort(mnesia:index_read(Tab, 10, Attr))), + Keys = [1, 2, 3], + ?match(Keys, lists:sort(mnesia:all_keys(Tab))), + + First = mnesia:first(Tab), + Mid = mnesia:next(Tab, First), + Last = mnesia:next(Tab, Mid), + ?match('$end_of_table', mnesia:next(Tab, Last)), + ?match(Keys, lists:sort([First,Mid,Last])), + + %% For set and bag these last, prev works as first and next + First2 = mnesia:last(Tab), + Mid2 = mnesia:prev(Tab, First2), + Last2 = mnesia:prev(Tab, Mid2), + ?match('$end_of_table', mnesia:prev(Tab, Last2)), + ?match(Keys, lists:sort([First2,Mid2,Last2])), + + ?match([ok, ok, ok], [mnesia:delete(Tab, K, write) || K <- Keys]), + W = wild_pattern, + ?match([], mnesia:match_object(Tab, mnesia:table_info(Tab, W), read)), + ?log("Safe fixed ~p~n", [catch ets:info(Tab, safe_fixed)]), + ?log("Fixed ~p ~n", [catch ets:info(Tab, fixed)]), + + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, write)), + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, write)), + + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +auto_repair(doc) -> + ["Try the auto_repair mechanism on the various disk_logs and dets files.", + "", + "The case tests both normal values of the parameter, and also", + "one crazy value.", + "The test of the real auto_repair functionality is made in the", + "dets suite" + ]; +auto_repair(suite) -> []; +auto_repair(Config) when is_list(Config) -> + ?init(1, Config), + ?match(ok, mnesia:start()), % Check default true + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, true}])), + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, false}])), + ?match(false, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match({error, {bad_type, auto_repair, your_mama}}, + mnesia:start([{auto_repair, your_mama}])), + ?match(stopped, mnesia:stop()), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +backup_module(doc) -> + ["Replace the backup module with another module and use it to", + "read and write to an alternate backup media, e.g stored in", + "the internal state of a simple process."]; +backup_module(suite) -> []; +backup_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{backup_module, mnesia_config_backup}])), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table2, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table2)}])), + %% Write in test table + ?trans(fun() -> mnesia:write(#test_table{i=1}) end), + ?trans(fun() -> mnesia:write(#test_table{i=2}) end), + + %% Write in test table 2 + ?trans(fun() -> mnesia:write(#test_table2{i=3}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=4}) end), + mnesia_test_lib:sync_tables(Nodes, [test_table, test_table2]), + + File = whow, + %% Now make a backup + ?match(ok, mnesia:backup(File)), + + ?match(ok, mnesia:install_fallback(File)), + + %% Now add things + ?trans(fun() -> mnesia:write(#test_table{i=2.5}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=3.5}) end), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes, [test_table, test_table2])), + + %% Now check newly started tables + ?match({atomic, [1,2]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table)) end)), + ?match({atomic, [3,4]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table2)) end)), + + file:delete(File), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug(doc) -> + ["Try out the four debug levels and ensure that the", + "expected events are generated."]; +debug(suite) -> []; +debug(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + case application:get_env(mnesia,debug) of + undefined -> + ?match(none, mnesia:system_info(debug)); + {ok, false} -> + ?match(none, mnesia:system_info(debug)); + {ok, true} -> + ?match(debug, mnesia:system_info(debug)); + {ok, Env} -> + ?match(Env, mnesia:system_info(debug)) + end, + + ?match(ok, mnesia:start([{debug, verbose}])), + ?match(verbose, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, debug}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, trace}])), + ?match(trace, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, true}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, false}])), + ?match(none, mnesia:system_info(debug)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dir(doc) -> + ["Try to use alternate Mnesia directories"]; +dir(suite) -> []; +dir(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + + ?match(ok, mnesia:start([{dir, tuff}])), + Dir = filename:join([element(2, file:get_cwd()), "tuff"]), + ?match(Dir, mnesia:system_info(directory)), + mnesia_test_lib:kill_mnesia(Nodes), + + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_update_in_place(doc) -> + ["Change the update in place policy for the transaction log dumper."]; +dump_log_update_in_place(suite) -> []; +dump_log_update_in_place(Config) when is_list(Config) -> + Nodes = ?acquire(1, Config), + ?match(true, mnesia:system_info(dump_log_update_in_place)), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{dump_log_update_in_place, false}])), + ?match(false, mnesia:system_info(dump_log_update_in_place)), + + mnesia_test_lib:sync_tables(Nodes, [schema, test_table]), + + %% Now provoke some log dumps + + L = lists:map( + fun(Num) -> + %% Write something on one end ... + mnesia:transaction( + fun() -> + mnesia:write(#test_table{i=Num}) end + ) end, + lists:seq(1, 110)), + + L2 = lists:duplicate(110, {atomic, ok}), + + %% If this fails then some of the 110 writes above failed + ?match(true, L==L2), + if L==L2 -> ok; + true -> + ?verbose("***** List1 len: ~p, List2 len: ~p~n", + [length(L), length(L2)]), + ?verbose("L: ~p~nL2:~p~n", [L, L2]) + end, + + %% If we still can write, then Mnesia is probably alive + ?trans(fun() -> mnesia:write(#test_table{i=115}) end), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_thresholds(doc) -> + ["Elaborate with various values of the dump log thresholds and how", + "they affects each others. Both the dump_log_time_threshold and the", + "dump_log_write_threshold must be covered. Do also check that both", + "kinds of overload events are generated as expected.", + "", + "Logs are checked by first doing whatever has to be done to trigger ", + "a dump, and then stopping Mnesia and then look in the ", + "data files and see that the correct amount of transactions ", + "have been done."]; +dump_log_thresholds(suite) -> + [ + dump_log_time_threshold, + dump_log_write_threshold + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_write_threshold(doc)-> + ["This test case must be rewritten.", + "Dump logs are tested by doing transactions, then killing Mnesia and ", + "then examining the table data files and see if they are correct.", + "The test_table is used as a counter, test_table. is stepped once ", + "for each transaction."]; +dump_log_write_threshold(suite)->[]; +dump_log_write_threshold(Config) when is_list(Config) -> + [N1] = ?acquire_schema(1, Config), + + Threshold = 3, + ?match(ok,mnesia:start([{dump_log_write_threshold, Threshold}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, [N1]}, + {attributes, + record_info(fields,test_table)}])), + ?match(dumped, mnesia:dump_log()), + + ?match(ok, do_trans(2)), % Shall not have dumped + check_logs(0), + + ?match(ok, do_trans(Threshold - 2)), % Trigger a dump + receive after 1000 -> ok end, + check_logs(Threshold), + + + ?match(ok, do_trans(Threshold - 1)), + ?match(dumped, mnesia:dump_log()), %% This should trigger ets2dcd dump + check_logs(0), %% and leave no dcl file + + ?match(stopped, mnesia:stop()), + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_write_threshold,0}}, + mnesia:start([{dump_log_write_threshold,0}])), + + ?verify_mnesia([], [N1]), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_time_threshold(doc)-> + ["See doc on above."]; +dump_log_time_threshold(suite)->[]; +dump_log_time_threshold(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + Time = 4000, + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_time_threshold,0}}, + mnesia:start([{dump_log_time_threshold,0}])), + + + ?match(ok,mnesia:start([{dump_log_write_threshold,100}, + {dump_log_time_threshold, Time}])), + + ?match({atomic,ok},mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields, + test_table)}])), + + %% Check that nothing is dumped when within time threshold + ?match(ok, do_trans(1)), + check_logs(0), + + ?match(Time, mnesia:system_info(dump_log_time_threshold)), + + %% Check that things get dumped when time threshold exceeded + ?match(ok, do_trans(5)), + receive after Time+2000 -> ok end, + check_logs(6), + + ?verify_mnesia([node()], []), + ?cleanup(1, Config), + ok. + +%%%%%%%% +%% +%% Help functions for dump log + +%% Do a transaction N times +do_trans(0) -> ok; +do_trans(N) -> + Fun = fun() -> + XX=incr(), + mnesia:write(#test_table{i=XX}) + end, + {atomic, ok} = mnesia:transaction(Fun), + do_trans(N-1). + +%% An increasing number +incr() -> + case get(bloody_counter) of + undefined -> put(bloody_counter, 2), 1; + Num -> put(bloody_counter, Num+1) + end. + +%% +%% Check that the correct number of transactions have been recorded. +%%-record(test_table,{i,a1,a2,a3}). +check_logs(N) -> + File = mnesia_lib:tab2dcl(test_table), + Args = [{file, File}, {name, testing}, {repair, true}, {mode, read_only}], + + if N == 0 -> + ?match(false, mnesia_lib:exists(File)); + true -> + ?match(true, mnesia_lib:exists(File)), + ?match({ok, _Log}, disk_log:open(Args)), + + {Cont, Terms} = disk_log:chunk(testing, start), + ?match(eof, disk_log:chunk(testing, Cont)), + %%?verbose("N: ~p, L: ~p~n", [N, L]), + disk_log:close(testing), + + %% Correct number of records in file + ?match({N, N}, {N, length(Terms) -1 }) %% Ignore Header + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_log_load_regulation(doc) -> + ["Test the load regulation of the dumper"]; +dump_log_load_regulation(suite) -> + []; +dump_log_load_regulation(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = dump_log_load_regulation, + + %% Normal + NoReg = false, + ?match(NoReg, mnesia:system_info(Param)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + %% Regulation activated + Reg = true, + ?match(ok,mnesia:start([{Param, Reg}])), + ?match(Reg, mnesia:system_info(Param)), + + Args = + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 5}, + {n_branches, length(Nodes) * 10}, + {n_accounts_per_branch, 5}, + {replica_type, disc_copies}, + {stop_after, timer:seconds(30)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}], + + ?match({ok, _}, mnesia_tpcb:start(Args)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +embedded_mnemosyne(doc) -> + ["Start Mnemosyne as an embedded part of Mnesia", + "on some of the nodes"]; +embedded_mnemosyne(suite) -> + []; +embedded_mnemosyne(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = embedded_mnemosyne, + + %% Normal + NoMnem = false, + ?match(NoMnem, mnesia:system_info(Param)), + ?match(undefined, whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + case code:priv_dir(mnemosyne) of + {error, _} -> %% No mnemosyne on later systems + ok; + _ -> + %% Mnemosyne as embedded application + Mnem = true, + ?match(undefined, whereis(mnemosyne_catalog)), + ?match(ok,mnesia:start([{Param, Mnem}])), + ?match(Mnem, mnesia:system_info(Param)), + ?match(Pid when is_pid(Pid), whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match(undefined, whereis(mnemosyne_catalog)) + end, + ?verify_mnesia([], Nodes), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ignore_fallback_at_startup(doc) -> + ["Start Mnesia without rollback of the database to the fallback. ", + "Once Mnesia has been (re)started the installed fallback should", + "be handled as a normal active fallback.", + "Install a customized event module which disables the termination", + "of Mnesia when mnesia_down occurrs with an active fallback."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +max_wait_for_decision(doc) -> + ["Provoke Mnesia to make a forced decision of the outome", + "of a heavy weight transaction."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_compressed(doc) -> []; +send_compressed(suite) -> []; +send_compressed(Config) -> + [N1,N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, mnesia:create_table(t0, [{ram_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_only_copies,[N1,N2]}])), + + Max = 1000, + Create = fun(Tab) -> [mnesia:write({Tab, N, {N, "FILLER-123490878345asdasd"}}) + || N <- lists:seq(1, Max)], + ok + end, + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, mnesia:start([{send_compressed, 9}])), + ?match(ok, mnesia:wait_for_tables([t0,t1,t2], 5000)), + + ?match({atomic, ok}, mnesia:transaction(Create, [t0])), + ?match({atomic, ok}, mnesia:transaction(Create, [t1])), + ?match({atomic, ok}, mnesia:transaction(Create, [t2])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [t0,t1,t2])), + + Verify = fun(Tab) -> + [ [{Tab,N,{N,_}}] = mnesia:read(Tab, N) || N <- lists:seq(1, Max)], + ok + end, + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t2]])), + + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t2]])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +app_test(doc) -> []; +app_test(suite) -> []; +app_test(_Config) -> + ?match(ok,test_server:app_test(mnesia)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +event_module(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +event_module(suite) -> []; +event_module(Config) when is_list(Config) -> + Filter = fun({mnesia_system_event,{mnesia_info, _, _}}) -> false; + (_) -> true + end, + + [_N1, N2]=Nodes=?acquire_schema(2, Config), + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{event_module, mnesia_config_event}, + {extra_db_nodes, Nodes}]; + false -> + [{event_module, mnesia_config_event}] + end, + + ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])), + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog1 = receive + {log, L1} -> L1 + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}], + lists:filter(Filter, DebugLog1)), + mnesia_test_lib:kill_mnesia([N2]), + receive after 2000 -> ok end, + + ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])), + + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog = receive + {log, L} -> L + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}, + {mnesia_system_event,{mnesia_down,N2}}, + {mnesia_system_event,{mnesia_up, N2}}], + lists:filter(Filter, DebugLog)), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_config(doc) -> + ["Try many configurations with various schema_location's with and", + "without explicit extra_db_nodes. Do also provoke various schema merge", + "situations. Most of the other test suites focusses on tests where the", + "schema is residing on disc. Now it is time to perform an exhaustive", + "elaboration with various disc less configurations."]; +schema_config(suite) -> + [ + start_one_disc_full_then_one_disc_less, + start_first_one_disc_less_then_one_disc_full, + start_first_one_disc_less_then_two_more_disc_less, + schema_location_and_extra_db_nodes_combinations, + table_load_to_disc_less_nodes, + schema_merge, + dynamic_connect + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_one_disc_full_then_one_disc_less(doc)-> + ["Start a disk node and then a disk less one. Distribute some", + "tables between them."]; +start_one_disc_full_then_one_disc_less(suite) -> []; +start_one_disc_full_then_one_disc_less(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec) end)), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:read({test_table2, 155}) end)), + + ?verify_mnesia([N1, N2], []), + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_one_disc_full(doc)-> + ["no_doc"]; +start_first_one_disc_less_then_one_disc_full(suite) -> []; +start_first_one_disc_less_then_one_disc_full(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, Nodes}]])), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + mnesia_test_lib:sync_tables([N1, N2], [test_table]), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + mnesia_test_lib:sync_tables([N1, N2], [test_table, test_table2]), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(Rec) end])), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:read({test_table2, 155}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_two_more_disc_less(doc)-> + ["no doc"]; +start_first_one_disc_less_then_two_more_disc_less(suite) -> []; +start_first_one_disc_less_then_two_more_disc_less(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?init(3, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N3, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N3, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1, N2]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2, N3]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok, ok], []}, + rpc:multicall([N1, N2, N3], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N2, N3], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + %% Then do the other way around + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=33}) end])), + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_location_and_extra_db_nodes_combinations(doc)-> + ["Test schema loaction and extra_db_nodes combinations."]; +schema_location_and_extra_db_nodes_combinations(suite) -> []; +schema_location_and_extra_db_nodes_combinations(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema], 10000])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +table_load_to_disc_less_nodes(doc)-> + ["Load tables to disc less nodes"]; +table_load_to_disc_less_nodes(suite) -> []; +table_load_to_disc_less_nodes(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({atomic, [{test_table, 44, _, _, _}]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_merge(doc) -> + ["Provoke various schema merge situations.", + "Perform various schema updates while some nodes are down,", + "stop the started nodes, start the stopped nodes and perform", + "schema updates. Now we have a situation were some of the table", + "definitions have been changed on two or more nodes independently", + "of each other and when Mnesia on the nodes tries to connect", + "to each other at restart the schema will be merged.", + "Do also try to provoke schema merge situations were the", + "schema cannot be merged."]; + +schema_merge(suite) -> []; + +schema_merge(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire(2,Config), + + mnesia_test_lib:kill_mnesia([N2]), + receive after 1000 -> ok end, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, + [test_table, + [{Storage, [N1]}, + {attributes, + record_info(fields,test_table)}]])), + + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + mnesia_test_lib:kill_mnesia([N1]), + receive after 2000 -> ok end, + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N2, mnesia, start, [])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, + [test_table2, + [{Storage, [N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + receive after 5000 -> ok end, + + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:write(#test_table2{i=33}) end])), + + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N1, mnesia, start, [])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?match({[{atomic, [{test_table2, 33, _}]}, + {atomic, [{test_table2, 33, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table2, 33}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +-define(connect(Nodes), mnesia:change_config(extra_db_nodes, Nodes)). +-define(rpc_connect(From, Nodes), + rpc:call(From, mnesia, change_config, [extra_db_nodes, Nodes])). + + +sort({ok, NS}) -> + {ok, lists:sort(NS)}; +sort(Ns) when is_tuple(Ns) -> + Ns; +sort(NS) when is_list(NS) -> + lists:sort(NS). + + +dynamic_connect(doc) -> + ["Test the new functionality where we start mnesia first and then " + "connect to the other mnesia nodes"]; +dynamic_connect(suite) -> + [ + dynamic_basic, + dynamic_ext, + dynamic_bad + ]. + + +dynamic_basic(suite) -> []; +dynamic_basic(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort(Nodes), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes--[N1]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + + ?match({ok, SNs}, sort(?rpc_connect(N1, Nodes))), %% What shall happen? + ?match({ok, []}, sort(?rpc_connect(N1, [nonode@nothosted]))), %% What shall happen? + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + ?match(ok, mnesia:dirty_write({tab2, 1, 1})), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N2, N1, [N3]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + io:format("T1 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, start, [])), + io:format("T2 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + timer:sleep(2000), + io:format("T3 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match({ok, [N1]}, sort(?rpc_connect(N3, [N1]))), + io:format("T4 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match({ok, [N3]}, sort(?rpc_connect(N1, [N3]))), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, N2)), + + % Ok, we have now removed references to node N2 from the other nodes + % mnesia should come up now. + ?match({atomic, ok}, mnesia:add_table_copy(tab1, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N2, [N3]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia([N2]), + + %%% SYNC!!! + timer:sleep(1000), + + ?match([N3,N1], sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match([N3,N1], sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N3, [N2]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + ?verify_mnesia(Nodes, []), +%% ?cleanup(3, Config). + ok. + +c_nodes() -> + {mnesia_lib:val({current, db_nodes}),mnesia_lib:val(recover_nodes)}. + + +dynamic_ext(suite) -> []; +dynamic_ext(Config) when is_list(Config) -> + Ns = [N1,N2] = ?acquire_nodes(2, Config), + SNs = lists:sort([N1,N2]), + + ?match({atomic, ok}, mnesia:create_table(tab0, [{disc_copies, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, [N2]}])), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0,tab1,tab2,tab3], 2000])), + + Check = fun({Tab,Storage}) -> + ?match(Storage, rpc:call(N2, mnesia, table_info, [Tab, storage_type])), + ?match([{N2,Storage}], + lists:sort(rpc:call(N2, mnesia, table_info, [Tab, where_to_commit]))) + end, + [Check(Test) || Test <- [{tab1, ram_copies},{tab2, disc_copies},{tab3, disc_only_copies}]], + + T = now(), + ?match(ok, mnesia:dirty_write({tab0, 42, T})), + ?match(ok, mnesia:dirty_write({tab1, 42, T})), + ?match(ok, mnesia:dirty_write({tab2, 42, T})), + ?match(ok, mnesia:dirty_write({tab3, 42, T})), + + ?match(stopped, rpc:call(N2, mnesia, stop, [])), + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(ok, mnesia:wait_for_tables([tab0,tab1,tab2,tab3], 10000)), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2,tab3], 100])), + ?match([], mnesia:dirty_read({tab1, 41})), + ?match([{tab2,42,T}], mnesia:dirty_read({tab2, 42})), + ?match([{tab3,42,T}], mnesia:dirty_read({tab3, 42})), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2, mnesia,dirty_read,[{tab0,42}])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2,mnesia,dirty_read,[{tab0,42}])), + + ?verify_mnesia(Ns, []), + ok. + +check_storage(Me, Orig, Other) -> + io:format("Nodes ~p ~p ~p~n",[Me,Orig,Other]), + rpc:multicall(Other, sys, status, [mnesia_locker]), + rpc:call(Me, sys, status, [mnesia_locker]), + rpc:call(Orig, sys, status, [mnesia_locker]), + rpc:multicall(Other, sys, status, [mnesia_controller]), + rpc:call(Me, sys, status, [mnesia_controller]), + rpc:call(Orig, sys, status, [mnesia_controller]), + %% Verify disc_copies + W2C = lists:sort([{Node,disc_copies} || Node <- [Me,Orig|Other]]), + W2W = lists:sort([Me,Orig|Other]), + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [schema, storage_type])), + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [schema, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [schema, where_to_commit]))), + + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [tab2, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [tab2, storage_type])), + ?match(W2W, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_write]))), + ?match(Me, rpc:call(Me, mnesia, table_info, [tab2, where_to_read])), + + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [tab2, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_commit]))), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + + ?match(true, rpc:call(Me, mnesia_monitor, use_dir, [])), + ?match(disc_copies, rpc:call(Me, mnesia_lib, val, [{schema, storage_type}])), + + mnesia_test_lib:kill_mnesia([Orig]), + mnesia_test_lib:kill_mnesia(Other), + T = now(), + ?match(ok, rpc:call(Me, mnesia, dirty_write, [{tab2, 42, T}])), + ?match(stopped, rpc:call(Me, mnesia, stop, [])), + ?match(ok, rpc:call(Me, mnesia, start, [])), + ?match([], mnesia_test_lib:start_mnesia([Orig|Other], [tab1,tab2])), + ?match([{tab2,42,T}], rpc:call(Me, mnesia, dirty_read, [{tab2, 42}])), + ?match([{tab2,42,T}], rpc:call(Orig, mnesia, dirty_read, [{tab2, 42}])), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + ok. + + +dynamic_bad(suite) -> []; +dynamic_bad(Config) when is_list(Config) -> + Ns = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort([N2,N3]), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Ns -- [N1]}, + {disc_copies, [N1]}])), + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + ?match({badrpc, {'EXIT', {aborted, {no_exists, _, _}}}}, + rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + + ?match(ok, mnesia:start()), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match(N2, rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + % Make a merge conflict + ?match({atomic, ok}, rpc:call(N3, mnesia, create_table, [tab1, []])), + + io:format("We expect a mnesia crash here~n", []), + ?match({error,{_, _}}, mnesia:start()), + + ?verify_mnesia(Ns -- [N1], []), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +unknown_config(doc) -> + ["Try some unknown configuration parameters and see that expected", + "things happens."]; +unknown_config(suite)-> []; +unknown_config(Config) when is_list(Config) -> + ?init(1, Config), + %% NOTE: case 1 & 2 below do not respond the same + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{undefined_config,[]}])), + %% Below does not work, but the "correct" behaviour would be to have + %% case 1 above to behave as the one below. + + %% in mnesia-1.3 {error,{bad_type,{[],undefined_config}}} + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{[],undefined_config}])), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +inconsistent_database(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +inconsistent_database(suite) -> []; +inconsistent_database(Config) when is_list(Config) -> + Nodes = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}], + 2, Config, ?FILE, ?LINE), + KillAfter = length(Nodes) * timer:minutes(5), + ?acquire_schema(2, Config ++ [{tc_timeout, KillAfter}]), + + Ok = [ok || _N <- Nodes], + StartArgs = [{event_module, mnesia_inconsistent_database_test}], + ?match({Ok, []}, rpc:multicall(Nodes, mnesia, start, [StartArgs])), + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + ?match(ok, mnesia_meter:go(ram_copies, Nodes)), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl new file mode 100644 index 0000000000..ffe8ab7ac3 --- /dev/null +++ b/lib/mnesia/test/mnesia_consistency_test.erl @@ -0,0 +1,1612 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_consistency_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify transaction consistency", + "Consistency is the property of the application that requires any", + "execution of the transaction to take the database from one", + "consistent state to another. Verify that the database is", + "consistent at any point in time.", + "Verify for various configurations.", + " Verify for both set and bag"]; +all(suite) -> + [ + consistency_after_restart, + consistency_after_dump_tables, + consistency_after_add_replica, + consistency_after_del_replica, + consistency_after_move_replica, + consistency_after_transform_table, + consistency_after_change_table_copy_type, + consistency_after_fallback, + consistency_after_restore, + consistency_after_rename_of_node, + checkpoint_retainer_consistency, + backup_consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, %% Unique account id + branch_id = 0, %% Branch where the account is held + balance = 0, %% Account balance + filler = ?ACCOUNT_FILLER %% Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_branches = 1, + n_tellers_per_branch = 10, %% Must be 10 + n_accounts_per_branch = 100000, %% Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +tpcb_config(ReplicaType, _NodeConfig, Nodes, NoDriverNodes) -> + [{n_branches, 10}, + {n_drivers_per_node, 10}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes -- NoDriverNodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {reuse_history_id, true}]. + +%% Stolen from mnesia_tpcb:dist +tpcb_config_dist(ReplicaType, _NodeConfig, Nodes, _Config) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {use_running_mnesia, true}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(15)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:minutes(3) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restart(suite) -> + [ + consistency_after_restart_1_ram, + consistency_after_restart_1_disc, + consistency_after_restart_1_disc_only, + consistency_after_restart_2_ram, + consistency_after_restart_2_disc, + consistency_after_restart_2_disc_only + ]. + +consistency_after_restart_1_ram(suite) -> []; +consistency_after_restart_1_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 2, Config). + +consistency_after_restart_1_disc(suite) -> []; +consistency_after_restart_1_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 2, Config). + +consistency_after_restart_1_disc_only(suite) -> []; +consistency_after_restart_1_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 2, Config). + +consistency_after_restart_2_ram(suite) -> []; +consistency_after_restart_2_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 3, Config). + +consistency_after_restart_2_disc(suite) -> []; +consistency_after_restart_2_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 3, Config). + +consistency_after_restart_2_disc_only(suite) -> []; +consistency_after_restart_2_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 3, Config). + +consistency_after_restart(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_restart with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, [Node1]), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + mnesia_test_lib:kill_mnesia([Node1]), + %% Start and wait for tables to be loaded on all nodes + timer:sleep(timer:seconds(3)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_dump_tables(suite) -> + [ + consistency_after_dump_tables_1_ram, + consistency_after_dump_tables_2_ram + ]. + +consistency_after_dump_tables_1_ram(suite) -> []; +consistency_after_dump_tables_1_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 1, Config). + +consistency_after_dump_tables_2_ram(suite) -> []; +consistency_after_dump_tables_2_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 2, Config). + +consistency_after_dump_tables(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_dump_tables with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun() -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, rpc:call(Node1, mnesia, dump_tables, + [[branch, teller, account, history]])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + timer:sleep(timer:seconds(1)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account, branch, + teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_add_replica(suite) -> + [ + consistency_after_add_replica_2_ram, + consistency_after_add_replica_2_disc, + consistency_after_add_replica_2_disc_only, + consistency_after_add_replica_3_ram, + consistency_after_add_replica_3_disc, + consistency_after_add_replica_3_disc_only + ]. + +consistency_after_add_replica_2_ram(suite) -> []; +consistency_after_add_replica_2_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 2, Config). + +consistency_after_add_replica_2_disc(suite) -> []; +consistency_after_add_replica_2_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 2, Config). + +consistency_after_add_replica_2_disc_only(suite) -> []; +consistency_after_add_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 2, Config). + +consistency_after_add_replica_3_ram(suite) -> []; +consistency_after_add_replica_3_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 3, Config). + +consistency_after_add_replica_3_disc(suite) -> []; +consistency_after_add_replica_3_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 3, Config). + +consistency_after_add_replica_3_disc_only(suite) -> []; +consistency_after_add_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 3, Config). + +consistency_after_add_replica(ReplicaType, NodeConfig, Config) -> + Nodes0 = ?acquire_nodes(NodeConfig, Config), + AddNode = lists:last(Nodes0), + Nodes = Nodes0 -- [AddNode], + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_add_replica with ~p on ~p~n", + [ReplicaType, Nodes0]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:add_table_copy(account, AddNode, ReplicaType)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes0, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_del_replica(suite) -> + [ + consistency_after_del_replica_2_ram, + consistency_after_del_replica_2_disc, + consistency_after_del_replica_2_disc_only, + consistency_after_del_replica_3_ram, + consistency_after_del_replica_3_disc, + consistency_after_del_replica_3_disc_only + ]. + +consistency_after_del_replica_2_ram(suite) -> []; +consistency_after_del_replica_2_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 2, Config). + +consistency_after_del_replica_2_disc(suite) -> []; +consistency_after_del_replica_2_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 2, Config). + +consistency_after_del_replica_2_disc_only(suite) -> []; +consistency_after_del_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 2, Config). + +consistency_after_del_replica_3_ram(suite) -> []; +consistency_after_del_replica_3_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 3, Config). + +consistency_after_del_replica_3_disc(suite) -> []; +consistency_after_del_replica_3_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 3, Config). + +consistency_after_del_replica_3_disc_only(suite) -> []; +consistency_after_del_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 3, Config). + +consistency_after_del_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_del_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:del_table_copy(account, Node2)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_move_replica(suite) -> + [ + consistency_after_move_replica_2_ram, + consistency_after_move_replica_2_disc, + consistency_after_move_replica_2_disc_only, + consistency_after_move_replica_3_ram, + consistency_after_move_replica_3_disc, + consistency_after_move_replica_3_disc_only + ]. + +consistency_after_move_replica_2_ram(suite) -> []; +consistency_after_move_replica_2_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 2, Config). + +consistency_after_move_replica_2_disc(suite) -> []; +consistency_after_move_replica_2_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 2, Config). + +consistency_after_move_replica_2_disc_only(suite) -> []; +consistency_after_move_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 2, Config). + +consistency_after_move_replica_3_ram(suite) -> []; +consistency_after_move_replica_3_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 3, Config). + +consistency_after_move_replica_3_disc(suite) -> []; +consistency_after_move_replica_3_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 3, Config). + +consistency_after_move_replica_3_disc_only(suite) -> []; +consistency_after_move_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 3, Config). + +consistency_after_move_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config ++ [{tc_timeout, timer:minutes(10)}]), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_move_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes -- [Node2], []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node1, Node2)), + ?log("First move completed from node ~p to ~p ~n", [Node1, Node2]), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node2, Node1)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_transform_table(doc) -> + ["Check that the database is consistent after transform_table.", + " While applications are updating the involved tables. "]; + +consistency_after_transform_table(suite) -> + [ + consistency_after_transform_table_ram, + consistency_after_transform_table_disc, + consistency_after_transform_table_disc_only + ]. + + +consistency_after_transform_table_ram(suite) -> []; +consistency_after_transform_table_ram(Config) when is_list(Config) -> + consistency_after_transform_table(ram_copies, Config). + +consistency_after_transform_table_disc(suite) -> []; +consistency_after_transform_table_disc(Config) when is_list(Config) -> + consistency_after_transform_table(disc_copies, Config). + +consistency_after_transform_table_disc_only(suite) -> []; +consistency_after_transform_table_disc_only(Config) when is_list(Config) -> + consistency_after_transform_table(disc_only_copies, Config). + +consistency_after_transform_table(Type, Config) -> + Nodes = [N1, N2,_N3] = ?acquire_nodes(3, Config), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{index, [3]}, {Type, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{index, [3]}, {Type, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{index, [3]}, {Type, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(empty, [{index, [3]},{Type, Nodes}])), + + Tabs = lists:sort([tab1, tab2, tab3, empty]), + + [[mnesia:dirty_write({Tab, N, N}) || N <- lists:seq(1,10)] || + Tab <- Tabs -- [empty, tab4]], + mnesia:dump_log(), + + Ok = lists:duplicate(4, {atomic, ok}), + ?match(Ok, [mnesia:transform_table(Tab, fun({T, N, N}) -> {T, N, N, ok} end, + [k,a,n]) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + Filter = fun(Tab) -> mnesia:foldl(fun(A, Acc) when size(A) == 3 -> [A|Acc]; + (A, Acc) when size(A) == 4 -> Acc + end, [], Tab) + end, + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes, Tabs), + + ?match([Tabs, Tabs, Tabs], + [lists:sort(rpc:call(Node, mnesia,system_info, [tables]) -- [schema]) || Node <- Nodes]), + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_change_table_copy_type(doc) -> + ["Check that the database is consistent after change of copy type.", + " While applications are updating the involved tables. "]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_fallback(doc) -> + ["Check that installed fallbacks are consistent. Check this by starting ", + "some nodes, run tpcb on them, take a backup at any time, install it ", + "as a fallback, kill all nodes, start mnesia again and check for ", + "any inconsistencies"]; +consistency_after_fallback(suite) -> + [ + consistency_after_fallback_2_ram, + consistency_after_fallback_2_disc, + consistency_after_fallback_2_disc_only, + consistency_after_fallback_3_ram, + consistency_after_fallback_3_disc + , consistency_after_fallback_3_disc_only + ]. + +consistency_after_fallback_2_ram(suite) -> []; +consistency_after_fallback_2_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 2, Config). + +consistency_after_fallback_2_disc(suite) -> []; +consistency_after_fallback_2_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 2, Config). + +consistency_after_fallback_2_disc_only(suite) -> []; +consistency_after_fallback_2_disc_only(Config) when is_list(Config) -> + consistency_after_fallback(disc_only_copies, 2, Config). + +consistency_after_fallback_3_ram(suite) -> []; +consistency_after_fallback_3_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 3, Config). + +consistency_after_fallback_3_disc(suite) -> []; +consistency_after_fallback_3_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 3, Config). + +consistency_after_fallback_3_disc_only(suite) -> []; +consistency_after_fallback_3_disc_only(Config) when is_list(Config) -> + consistency_after_fallback(disc_only_copies, 3, Config). + +consistency_after_fallback(ReplicaType, NodeConfig, Config) -> + %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + %% Make a backup + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup(consistency_after_fallback2)), + + %% Install the backup as a fallback + ?verbose("Doing fallback~n", []), + ?match(ok, mnesia:install_fallback(consistency_after_fallback2)), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + if + ReplicaType == ram_copies -> + %% Test that change_table_copy work i.e. no account.dcd file exists. + ?match({atomic, ok}, mnesia:change_table_copy_type(account, node(), disc_copies)); + true -> + ignore + end, + file:delete(consistency_after_fallback2), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restore(doc) -> + ["Verify consistency after restore operations."]; + +consistency_after_restore(suite) -> + [ + consistency_after_restore_clear_ram, + consistency_after_restore_clear_disc, + consistency_after_restore_clear_disc_only, + consistency_after_restore_recreate_ram, + consistency_after_restore_recreate_disc, + consistency_after_restore_recreate_disc_only + ]. + +consistency_after_restore_clear_ram(suite) -> []; +consistency_after_restore_clear_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, clear_tables, Config). + +consistency_after_restore_clear_disc(suite) -> []; +consistency_after_restore_clear_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, clear_tables, Config). + +consistency_after_restore_clear_disc_only(suite) -> []; +consistency_after_restore_clear_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, clear_tables, Config). + +consistency_after_restore_recreate_ram(suite) -> []; +consistency_after_restore_recreate_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc(suite) -> []; +consistency_after_restore_recreate_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc_only(suite) -> []; +consistency_after_restore_recreate_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, recreate_tables, Config). + +consistency_after_restore(ReplicaType, Op, Config) -> + Delay = 1, + Nodes = ?acquire_nodes(3, [{tc_timeout, timer:minutes(10)} | Config]), + [Node1, Node2, _Node3] = Nodes, + File = "cons_backup_restore", + + ?log("consistency_after_restore with ~p on ~p~n", + [ReplicaType, Nodes]), + Tabs = [carA, carB, carC, carD], + + ?match({atomic, ok}, mnesia:create_table(carA, [{ReplicaType, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(carB, [{ReplicaType, Nodes -- [Node1]}])), + ?match({atomic, ok}, mnesia:create_table(carC, [{ReplicaType, Nodes -- [Node2]}])), + ?match({atomic, ok}, mnesia:create_table(carD, [{ReplicaType, [Node2]}])), + + NList = lists:seq(0, 20), + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 1}) end, NList) || + Tab <- Tabs], + + {ok, Name, _} = mnesia:activate_checkpoint([{max, [schema | Tabs]}, + {ram_overrides_dump, true}]), + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup_checkpoint(Name, File)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 2}) end, NList) || + Tab <- Tabs], + + Pids1 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carA, Op]), ok} || _ <- lists:seq(1, 5)], + Pids2 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carB, Op]), ok} || _ <- lists:seq(1, 5)], + Pids3 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carC, Op]), ok} || _ <- lists:seq(1, 5)], + Pids4 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carD, Op]), ok} || _ <- lists:seq(1, 5)], + + AllPids = Pids1 ++ Pids2 ++ Pids3 ++ Pids4, + + Restore = fun(F, Args) -> + case mnesia:restore(F, Args) of + {atomic, List} -> lists:sort(List); + Else -> Else + end + end, + + timer:sleep(timer:seconds(Delay)), %% Let changers grab locks + ?verbose("Doing restore~n", []), + ?match(Tabs, Restore(File, [{default_op, Op}])), + + timer:sleep(timer:seconds(Delay)), %% Let em die + + ?match_multi_receive(AllPids), + + case ?match(ok, restore_verify_tabs(Tabs)) of + {success, ok} -> + file:delete(File); + _ -> + {T, M, S} = time(), + File2 = ?flat_format("consistency_error~w~w~w.BUP", [T, M, S]), + file:rename(File, File2) + end, + ?verify_mnesia(Nodes, []). + +change_tab(Father, Tab, Test) -> + Key = random:uniform(20), + Update = fun() -> + case mnesia:read({Tab, Key}) of + [{Tab, Key, 1}] -> + quit; + [{Tab, Key, _N}] -> + mnesia:write({Tab, Key, 3}) + end + end, + case mnesia:transaction(Update) of + {atomic, quit} -> + exit(ok); + {aborted, {no_exists, Tab}} when Test == recreate_tables ->%% I'll allow this + change_tab(Father, Tab, Test); + {atomic, ok} -> + change_tab(Father, Tab, Test) + end. + +restore_verify_tabs([Tab | R]) -> + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:foldl(fun({_, _, 1}, ok) -> + ok; + (Else, Acc) -> + [Else|Acc] + end, ok, Tab) + end)), + restore_verify_tabs(R); +restore_verify_tabs([]) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_rename_of_node(doc) -> + ["Skipped because it is an unimportant case."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +checkpoint_retainer_consistency(doc) -> + ["Verify that the contents of a checkpoint retainer has the expected", + "contents in various situations."]; +checkpoint_retainer_consistency(suite) -> + [ + updates_during_checkpoint_activation, + updates_during_checkpoint_iteration, + load_table_with_activated_checkpoint, + add_table_copy_to_table_with_activated_checkpoint + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_activation(doc) -> + ["Perform updates while the checkpoint getting activated", + "and verify that all checkpoint retainers associated with", + "different replicas of the same table really has the same", + "contents."]; +updates_during_checkpoint_activation(suite) -> + [ + updates_during_checkpoint_activation_2_ram, + updates_during_checkpoint_activation_2_disc, + updates_during_checkpoint_activation_2_disc_only, + updates_during_checkpoint_activation_3_ram, + updates_during_checkpoint_activation_3_disc + , updates_during_checkpoint_activation_3_disc_only + ]. + +updates_during_checkpoint_activation_2_ram(suite) -> []; +updates_during_checkpoint_activation_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc(suite) -> []; +updates_during_checkpoint_activation_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc_only(suite) -> []; +updates_during_checkpoint_activation_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 2, Config). + +updates_during_checkpoint_activation_3_ram(suite) -> []; +updates_during_checkpoint_activation_3_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc(suite) -> []; +updates_during_checkpoint_activation_3_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc_only(suite) -> []; +updates_during_checkpoint_activation_3_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 3, Config). + +updates_during_checkpoint_activation(ReplicaType,NodeConfig,Config) -> + %%?verbose("updates_during_checkpoint_activation2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}]), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, mnesia:backup_checkpoint(CPName, + updates_during_checkpoint_activation2)), + timer:sleep(timer:seconds(Delay)), + + ?match(ok, mnesia:install_fallback(updates_during_checkpoint_activation2)), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + file:delete(updates_during_checkpoint_activation2), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_iteration(doc) -> + ["Perform updates while someone is iterating over a checkpoint", + "and verify that the iterator really finds the expected data", + "regardless of ongoing upates."]; + +updates_during_checkpoint_iteration(suite) -> + [ + updates_during_checkpoint_iteration_2_ram, + updates_during_checkpoint_iteration_2_disc + , updates_during_checkpoint_iteration_2_disc_only + ]. + +updates_during_checkpoint_iteration_2_ram(suite) -> []; +updates_during_checkpoint_iteration_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(ram_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc(suite) -> []; +updates_during_checkpoint_iteration_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc_only(suite) -> []; +updates_during_checkpoint_iteration_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_only_copies, 2, Config). + +updates_during_checkpoint_iteration(ReplicaType,NodeConfig,Config) -> + %?verbose("updates_during_checkpoint_iteration2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %?verbose("Mnesia info: ~p~n", [mnesia:info()]), + File = updates_during_checkpoint_iteration2, + {success, [A]} = ?start_activities([Node1]), + ?log("updates_during_checkpoint_iteration with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + TpcbConfigRec = list2rec(TpcbConfig, + record_info(fields,tab_config), + #tab_config{}), + mnesia_tpcb:init(TpcbConfig), + ?match(ok, mnesia_tpcb:verify_tabs()), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + A ! fun () -> mnesia:backup_checkpoint(CPName, File) end, + + do_changes_during_backup(TpcbConfigRec), + + ?match_receive({A,ok}), + + timer:sleep(timer:seconds(Delay)), + ?match(ok, mnesia:install_fallback(File)), + timer:sleep(timer:seconds(Delay)), + + ?match({error,{"Bad balance",_,_}}, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +do_changes_during_backup(TpcbConfig) -> + loop_branches(TpcbConfig#tab_config.n_branches, + TpcbConfig#tab_config.n_accounts_per_branch). + +loop_branches(N_br,N_acc) when N_br >= 1 -> + loop_accounts(N_br,N_acc), + loop_branches(N_br-1,N_acc); +loop_branches(_,_) -> done. + +loop_accounts(N_br, N_acc) when N_acc >= 1 -> + A = #account{id=N_acc, branch_id=N_br, balance = 4711}, + ok = mnesia:dirty_write(A), + loop_accounts(N_br, N_acc-1); + +loop_accounts(_,_) -> done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_table_with_activated_checkpoint(doc) -> + ["Load a table with a checkpoint attached to it and verify that the", + "newly loaded replica also gets a checkpoint retainer attached to it", + "and that it is consistent with the original retainer."]; + +load_table_with_activated_checkpoint(suite) -> + [ + load_table_with_activated_checkpoint_ram, + load_table_with_activated_checkpoint_disc, + load_table_with_activated_checkpoint_disc_only + ]. + +load_table_with_activated_checkpoint_ram(suite) -> []; +load_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(ram_copies, Config). + +load_table_with_activated_checkpoint_disc(suite) -> []; +load_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_copies, Config). + +load_table_with_activated_checkpoint_disc_only(suite) -> []; +load_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_only_copies, Config). + +load_table_with_activated_checkpoint(Type, Config) -> + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + Tab = load_test, + Def = [{attributes, [key, value]}, + {Type, Nodes}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + timer:sleep(timer:seconds(1)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + mnesia_test_lib:stop_mnesia([Node1]), + mnesia_test_lib:start_mnesia([Node1],[Tab]), + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + A ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + List_a = view(load_table_a, Mod), + List_b = view(load_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(load_table_a)), + ?match(ok,file:delete(load_table_b)), + ?verify_mnesia(Nodes, []). + +view(Source, Mod) -> + View = fun(Item, Acc) -> + ?verbose("tab - item : ~p ~n",[Item]), + case Item of + {schema, Tab, Cs} -> %% Remove cookie information + NewCs = lists:keyreplace(cookie, 1, Cs, + {cookie, skip_cookie}), + Item2 = {schema, Tab, NewCs}, + {[Item], [Item2|Acc]}; + _ -> + {[Item], [Item|Acc]} + end + end, + {ok,TabList} = + mnesia:traverse_backup(Source, Mod, dummy, read_only, View, []), + lists:sort(TabList). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +add_table_copy_to_table_with_activated_checkpoint(doc) -> + ["Add a replica to a table with a checkpoint attached to it", + "and verify that the new replica also gets a checkpoint", + "retainer attached to it and that it is consistent with the", + "original retainer."]; + +add_table_copy_to_table_with_activated_checkpoint(suite) -> + [ + add_table_copy_to_table_with_activated_checkpoint_ram, + add_table_copy_to_table_with_activated_checkpoint_disc, + add_table_copy_to_table_with_activated_checkpoint_disc_only + ]. + +add_table_copy_to_table_with_activated_checkpoint_ram(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(ram_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc_only(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_only_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint(Type,Config) -> + Nodes = ?acquire_nodes(2, Config), + %?verbose("NODES = ~p ~n",[Nodes]), + [Node1,Node2] = Nodes, + + Tab = add_test, + Def = [{attributes, [key, value]}, + {Type, [Node1]}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match({atomic,ok},mnesia:add_table_copy(Tab,Node2,ram_copies)), + + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 815})), + + A ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + + List_a = view(add_table_a, Mod), + List_b = view(add_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(add_table_a)), + ?match(ok, file:delete(add_table_b)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +backup_consistency(suite) -> + [ + interupted_install_fallback, + interupted_uninstall_fallback, + mnesia_down_during_backup_causes_switch, + mnesia_down_during_backup_causes_abort, + schema_transactions_during_backup + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_install_fallback(doc) -> + ["Verify that a interrupted install_fallback really", + "is performed on all nodes or none"]; + +interupted_install_fallback(suite) -> + [ + inst_fallback_process_dies, + fatal_when_inconsistency + ]. + +inst_fallback_process_dies(suite) -> + []; +inst_fallback_process_dies(Config) when is_list(Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + + A ! fun() -> mnesia:install_fallback(install_backup) end, + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(A, kill), + AnsPid ! {self(), fallback_continue}, + ?match_receive({'EXIT', A, killed}), + timer:sleep(2000), %% Wait till fallback is installed everywhere + + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("~n---->Mnesia is stopped everywhere<-----~n", []), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[Tab])), + + check_data(Nodes, Tab), + ?match(ok, file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +check_data([N1 | R], Tab) -> + ?match([{Tab, 1, 4711}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 2, 42}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{Tab, 3, 256}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + check_data(R, Tab); +check_data([], _Tab) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +fatal_when_inconsistency(suite) -> + []; +fatal_when_inconsistency(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2, Node3] = Nodes = + ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + ?match(ok, mnesia:dirty_write({Tab, 2, 42424242})), + + A ! fun() -> + mnesia:install_fallback(install_backup) + end, + + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(AnsPid, kill), %% Kill install-fallback on local node will + AnsPid ! {self(), fallback_continue}, + ?deactivate_debug_fun(DebugId), + + ?match_receive({A,{error,{"Cannot install fallback", + {'EXIT',AnsPid,killed}}}}), + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("EXPECTING FATAL from 2 nodes WITH CORE DUMP~n", []), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[])), + is_running(Node1, yes), + ?match([{Node2, mnesia, _}], mnesia_test_lib:start_mnesia([Node2],[])), + is_running(Node2, no), + ?match([{Node3, mnesia, _}], mnesia_test_lib:start_mnesia([Node3],[])), + is_running(Node3, no), + mnesia_test_lib:kill_mnesia(Nodes), + + ?match(ok, mnesia:install_fallback(install_backup)), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + check_data(Nodes, Tab), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +is_running(Node, Shouldbe) -> + timer:sleep(1000), + Running = rpc:call(Node, mnesia, system_info, [is_running]), + case Running of + Shouldbe -> ok; + _ -> is_running(Node, Shouldbe) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_uninstall_fallback(doc) -> + ["Verify that a interrupted uninstall_fallback really", + "is performed on all nodes or none"]; +interupted_uninstall_fallback(suite) -> + [ + after_delete + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_delete(doc) -> + ["interrupt the uninstall after deletion of ", + "fallback files - there shall be no fallback"]; +after_delete(suite) -> []; +after_delete(Config) when is_list(Config) -> + do_uninstall(Config, post_delete). + +%%%%%%%%%%%%%%%%%%%%%%%%% + +do_uninstall(Config,DebugPoint) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + %%?verbose("NODES = ~p ~n",[Nodes]), + + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %%?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %%?verbose("second case ~n"), + {P3, P1, P2}; + _ -> + { P1, P2, P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + DebugId = {mnesia_bup, uninstall_fallback2, DebugPoint}, + DebugFun = fun(PrevContext, _EvalContext) -> + ?verbose("uninstall_fallback pid ~p #~p~n" + ,[self(),PrevContext]), + TestPid ! {self(),uninstall_predelete}, + case receive_messages([uninstall_continue]) of + [{TestPid,uninstall_continue}] -> + ?deactivate_debug_fun(DebugId), + %%?verbose("uninstall_fallback continues~n"), + PrevContext+1 + end + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, + {ram_copies, Nodes}], %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName,install_backup)), + timer:sleep(timer:seconds(1)), + + A ! fun () -> + mnesia:install_fallback(install_backup) + end, + ?match_receive({A,ok}), + + A ! fun () -> + mnesia:uninstall_fallback() + end, + %% + %% catch the debug entry in mnesia and kill one Mnesia node + %% + + + [{AnsPid,uninstall_predelete}] = receive_messages([uninstall_predelete]), + + ?verbose("AnsPid : ~p~n",[AnsPid]), + + mnesia_test_lib:kill_mnesia([Node2]), + timer:sleep(timer:seconds(1)), + + AnsPid ! {self(),uninstall_continue}, + + ?match_receive({A,ok}), + + mnesia_test_lib:kill_mnesia(Nodes) , + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + A ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ A, {[],[],[]} }), + + B ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ B, {[],[],[]} }), + + C ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ C, {[],[],[]} }), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_switch(doc) -> + ["Verify that an ongoing backup is not disturbed", + "even if the node hosting the replica that currently", + "is being backup'ed is stopped. The backup utility", + "is expected to switch over to another replica and", + "fulfill the backup."]; +mnesia_down_during_backup_causes_switch(suite) -> + [ + cause_switch_before, + cause_switch_after + ]. + +%%%%%%%%%%%%%%% + +cause_switch_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +cause_switch_before(suite) -> []; +cause_switch_before(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,pre,Config). + +%%%%%%%%%%%%%%% + +cause_switch_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +cause_switch_after(suite) -> []; +cause_switch_after(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_abort(doc) -> + ["Verify that an ongoing backup is aborted nicely", + "without leaving any backup file if the last replica", + "of a table becomes unavailable due to a node down", + "or some crash."]; +mnesia_down_during_backup_causes_abort(suite) -> + [ + cause_abort_before, + cause_abort_after + ]. + +%%%%%%%%%%%%%%%%%% + +cause_abort_before(doc) -> + ["interrupt the backup before iterating the retainer"]; + +cause_abort_before(suite) -> []; +cause_abort_before(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,pre,Config). + +%%%%%%%%%%%%%%%%%% + +cause_abort_after(doc) -> + ["interrupt the backup after iterating the retainer"]; + +cause_abort_after(suite) -> []; +cause_abort_after(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_transactions_during_backup(doc) -> + ["Verify that an schema transactions does not", + "affect an ongoing backup."]; +schema_transactions_during_backup(suite) -> + [ + change_schema_before, + change_schema_after + ]. + +%%%%%%%%%%%%% + +change_schema_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +change_schema_before(suite) -> []; +change_schema_before(Config) when is_list(Config) -> + do_something_during_backup(change_schema,pre,Config). + +%%%%%%%%%%%%%%%% + +change_schema_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +change_schema_after(suite) -> []; +change_schema_after(Config) when is_list(Config) -> + do_something_during_backup(change_schema,post,Config). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_something_during_backup(Action,DebugPoint,Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + + {success, [A,B,C]} = ?start_activities(Nodes), + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + + Tab = interrupt_table, + Bak = interrupt_backup, + Def = [{attributes, [key, value]}, + {ram_copies, [Node2,Node3]}], + %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + + + DebugId = {mnesia_log, tab_copier, DebugPoint}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt backup pid ~p #~p ~n context ~p ~n" + ,[self(),PrevContext,EvalContext]), + TestPid ! {self(),interrupt_backup_pre}, + global:set_lock({{lock_for_backup, Tab}, self()}, + Nodes, + infinity), + + %%?verbose("interrupt backup - continues ~n"), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + A ! fun () -> + %%?verbose("node: ~p pid: ~p ~n",[node(),self()]), + mnesia:table_info(Tab,where_to_read) + end, + + ReadNode_a = receive { A, ReadNode_a_tmp } -> ReadNode_a_tmp end, + ?verbose("ReadNode ~p ~n",[ReadNode_a]), + + global:set_lock({{lock_for_backup, Tab}, self()}, Nodes, infinity), + + A ! fun () -> %% A shall perform the backup, so the test proc is + %% able to do further actions in between + mnesia:backup_checkpoint(CPName, Bak) + end, + + %% catch the debug function of mnesia, stop the backup process + %% kill the node ReadNode_a and continue the backup process + %% As there is a second replica of the table, the backup shall continue + + case receive_messages([interrupt_backup_pre]) of + [{_AnsPid,interrupt_backup_pre}] -> ok + end, + + case Action of + cause_switch -> + mnesia_test_lib:kill_mnesia([ReadNode_a]), + timer:sleep(timer:seconds(1)); + cause_abort -> + mnesia_test_lib:kill_mnesia([Node2,Node3]), + timer:sleep(timer:seconds(1)); + change_schema -> + Tab2 = second_interrupt_table, + Def2 = [{attributes, [key, value]}, + {ram_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)) + end, + + %% AnsPid ! {self(),interrupt_backup_continue}, + global:del_lock({{lock_for_backup, Tab}, self()}, Nodes), + + case Action of + cause_abort -> + + %% answer of A when finishing the backup + ?match_receive({A,{error, _}}), + + ?match({error,{"Cannot install fallback",_}}, + mnesia:install_fallback(Bak)); + _ -> %% cause_switch, change_schema + + ?match_receive({A,ok}), %% answer of A when finishing the backup + + %% send a fun to that node where mnesia is still running + WritePid = case ReadNode_a of + Node2 -> C; %% node(C) == Node3 + Node3 -> B + end, + WritePid ! fun () -> + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 816})), + ok + end, + ?match_receive({ WritePid, ok }), + ?match(ok, mnesia:install_fallback(Bak)) + end, + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + case Action of + cause_switch -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)); + cause_abort -> + %% the backup should NOT exist + cross_check_tables([A,B,C],Tab,{[],[],[]}), + %% file does not exist + ?match({error, _},file:delete(Bak)); + change_schema -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)) + end, + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). diff --git a/lib/mnesia/test/mnesia_cost.erl b/lib/mnesia/test/mnesia_cost.erl new file mode 100644 index 0000000000..54cb2b3064 --- /dev/null +++ b/lib/mnesia/test/mnesia_cost.erl @@ -0,0 +1,222 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_cost). +-compile(export_all). + +%% This code exercises the mnesia system and produces a bunch +%% of measurements on what various things cost + +-define(TIMES, 1000). %% set to at least 1000 when running for real !! + +%% This is the record we perform all ops on in this test + +-record(item, {a = 1234, + b = foobar, + c = "1.2.3.4", + d = {'Lennart', 'Hyland'}, + e = true + }). + +go() -> + go([node() | nodes()]). + +go(Nodes) when hd(Nodes) == node() -> + {ok, Out} = file:open("MNESIA_COST", write), + put(out, Out), + + rpc:multicall(Nodes, mnesia, lkill, []), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{attributes, record_info(fields, item)}], + {atomic, ok} = mnesia:create_table(item, TabDef), + + round("single ram copy", "no index"), + {atomic, ok} = mnesia:add_table_index(item, #item.e), + round("single ram copy", "One index"), + + {atomic, ok} = mnesia:add_table_index(item, #item.c), + round("single ram copy", "Two indexes"), + + {atomic, ok} = mnesia:del_table_index(item, #item.e), + {atomic, ok} = mnesia:del_table_index(item, #item.c), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), disc_copies), + round("single disc copy", "no index"), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), ram_copies), + + case length(Nodes) of + Len when Len < 2 -> + format("<WARNING> replication skipped. Too few nodes.", []); + _Len -> + N2 = lists:nth(2, Nodes), + {atomic, ok} = mnesia:add_table_copy(item, N2, ram_copies), + round("2 replicated ram copy", "no index") + end, + file:close(Out), + erase(out), + ok. + +round(Replication, Index) -> + run(Replication, Index, [write], + fun() -> mnesia:write(#item{}) end), + + + run(Replication, Index, [read], + fun() -> mnesia:read({item, 1234}) end), + + run(Replication, Index, [read, write], + fun() -> mnesia:read({item, 1234}), + mnesia:write(#item{}) end), + + run(Replication, Index, [wread, write], + fun() -> mnesia:wread({item, 1234}), + mnesia:write(#item{}) end), + + + run(Replication, Index, [match, write, write, write], + fun() -> mnesia:match_object({item, 1, '_', '_', '_', true}), + mnesia:write(#item{a =1}), + mnesia:write(#item{a =2}), + mnesia:write(#item{a =3}) end). + + +format(F, As) -> + io:format(get(out), F, As). + +run(What, OtherInfo, Ops, F) -> + run(t, What, OtherInfo, Ops, F). + +run(How, What, OtherInfo, Ops, F) -> + T1 = erlang:now(), + statistics(runtime), + do_times(How, ?TIMES, F), + {_, RunTime} = statistics(runtime), + T2 = erlang:now(), + RealTime = subtr(T1, T2), + report(How, What, OtherInfo, Ops, RunTime, RealTime). + +report(t, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, transaction call ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Trans ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]); + +report(dirty, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, dirty calls ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Bunch ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]). + + +subtr(Before, After) -> + E =(element(1,After)*1000000000000 + +element(2,After)*1000000+element(3,After)) - + (element(1,Before)*1000000000000 + +element(2,Before)*1000000+element(3,Before)), + E div 1000. + +do_times(t, I, F) -> + do_trans_times(I, F); +do_times(dirty, I, F) -> + do_dirty(I, F). + +do_trans_times(I, F) when I /= 0 -> + {atomic, _} = mnesia:transaction(F), + do_trans_times(I-1, F); +do_trans_times(_,_) -> ok. + +do_dirty(I, F) when I /= 0 -> + F(), + do_dirty(I-1, F); +do_dirty(_,_) -> ok. + + + +table_load([N1,N2| _ ] = Ns) -> + Nodes = [N1,N2], + rpc:multicall(Ns, mnesia, lkill, []), + ok = mnesia:delete_schema(Ns), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{disc_copies,[N1]},{ram_copies,[N2]}, + {attributes,record_info(fields,item)},{record_name,item}], + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1,400)], + + [mnesia:create_table(Tab,TabDef) || Tab <- Tabs], + +%% InitTab = fun(Tab) -> +%% mnesia:write_lock_table(Tab), +%% InitRec = fun(Key) -> mnesia:write(Tab,#item{a=Key},write) end, +%% lists:foreach(InitRec, lists:seq(1,100)) +%% end, +%% +%% {Time,{atomic,ok}} = timer:tc(mnesia,transaction, [fun() ->lists:foreach(InitTab, Tabs) end]), + mnesia:dump_log(), +%% io:format("Init took ~p msec ~n", [Time/1000]), + rpc:call(N2, mnesia, stop, []), timer:sleep(1000), + mnesia:stop(), timer:sleep(500), + %% Warmup + ok = mnesia:start([{no_table_loaders, 1}]), + timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + mnesia:dump_log(), + rpc:call(N2, mnesia, dump_log, []), + io:format("Initialized ~n",[]), + + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 1}]), + {T1, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 1 loader ~p msec~n",[T1/1000]), + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 4}]), + {T2, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 4 loader ~p msec~n",[T2/1000]), + + %% Warmup + rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Initialized ~n",[]), + + + T3 = rpc:call(N2, ?MODULE, remote_load, [Tabs,1]), + io:format("Loading from net with 1 loader ~p msec~n",[T3/1000]), + + T4 = rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Loading from net with 4 loader ~p msec~n",[T4/1000]), + + ok. + +remote_load(Tabs,Loaders) -> + ok = mnesia:start([{no_table_loaders, Loaders}]), +%% io:format("~p ~n", [mnesia_controller:get_info(500)]), + {Time, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + timer:sleep(1000), mnesia:stop(), timer:sleep(1000), + Time. diff --git a/lib/mnesia/test/mnesia_dbn_meters.erl b/lib/mnesia/test/mnesia_dbn_meters.erl new file mode 100644 index 0000000000..feaf90ee75 --- /dev/null +++ b/lib/mnesia/test/mnesia_dbn_meters.erl @@ -0,0 +1,242 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(mnesia_dbn_meters). +-export([ + start/0, + local_start/0, + distr_start/1, + start/3 + ]). + +-record(simple,{key,val=0}). +-define(key,1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Configuration and start + +start() -> + local_start(), + distr_start(nodes()). + +local_start() -> + start(one_ram_only,[node()],some_meters()), + start(one_disc_only,[node()],some_meters()). + +distr_start([]) -> + local_only; +distr_start(OtherNodes) when is_list(OtherNodes) -> + start(ram_and_ram,[node()|OtherNodes],some_meters()), + start(disc_and_disc,[node()|OtherNodes],some_meters()). + +start(Config,Nodes,Meters) -> + Attrs = record_info(fields,simple), + Schema = [{name,simple},{type,set},{attributes,Attrs}] ++ config(Config,Nodes), + L = '====================', + io:format("~n~p dbn_meters: ~p ~p~nSchema = ~p.~n~n",[L,Config,L,Schema]), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + {atomic,_} = mnesia:create_table(Schema), + lists:foreach(fun report_meter/1,Meters), + {atomic, ok} = mnesia:delete_table(simple), + rpc:multicall(Nodes, mnesia, stop, []), + ok. + +config(one_ram_only,[Single|_]) -> + [{ram_copies,[Single]}]; +config(ram_and_ram,[Master|[Slave|_]]) -> + [{ram_copies,[Master,Slave]}]; +config(one_disc_only,[Single|_]) -> + [{disc_copies,[Single]}]; +config(disc_and_disc,[Master|[Slave|_]]) -> + [{disc_copies,[Master,Slave]}]; +config(Config,Nodes) -> + io:format("<ERROR> Config ~p not supported or too few nodes ~p given~n",[Config,Nodes]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The various DBN meters +some_meters() -> + [create, + open_safe_read, + open_dirty_read, + get_int, + open_update, + put_int, + put_int_and_copy, + dirty_put_int_and_copy, + start_trans, + commit_one_update, + delete, + dirty_delete + ]. + +report_meter(Meter) -> + Times = 100, + Micros = repeat_meter(Meter,{atomic,{0,ignore}},Times) div Times, + io:format("\t~-30w ~-10w micro seconds (mean of ~p repetitions)~n",[Meter,Micros,Times]). + +repeat_meter(_Meter,{atomic,{Micros,_Result}},0) -> + Micros; +repeat_meter(Meter,{atomic,{Micros,_Result}},Times) when Times > 0 -> + repeat_meter(Meter,catch meter(Meter),Times-1) + Micros; +repeat_meter(Meter,{aborted,Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\t,aborted, because ~p~n",[Meter,Reason]), + 0; +repeat_meter(Meter,{'EXIT',Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\tcrashed, because ~p~n",[Meter,Reason]), + 0. + +meter(create) -> + Key = 1, + mnesia:transaction(fun() -> mnesia:delete({simple,Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:write(#simple{key=Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_safe_read) -> + Key = 2, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_dirty_read) -> + Key = 21, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:dirty_read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(get_int) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:read({simple,Key}), + BeforeT = erlang:now(), + Int = Simple#simple.val, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,Int) + end, + mnesia:transaction(Fun); + +meter(open_update) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:wread({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int) -> + Key = 4, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + R = Simple#simple{val=7}, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int_and_copy) -> + Key = 5, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:write(Simple2), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_put_int_and_copy) -> + Key = 55, + mnesia:dirty_write(#simple{key=Key}), + [Simple] = mnesia:dirty_read({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:dirty_write(Simple2), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,R)}; + +meter(start_trans) -> + BeforeT = erlang:now(), + {atomic,AfterT} = mnesia:transaction(fun() -> erlang:now() end), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(commit_one_update) -> + Key = 6, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + Simple2 = Simple#simple{val=27}, + _R = mnesia:write(Simple2), + erlang:now() + end, + {atomic,BeforeT} = mnesia:transaction(Fun), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(delete) -> + Key = 7, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:delete({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_delete) -> + Key = 75, + mnesia:dirty_write(#simple{key=Key}), + BeforeT = erlang:now(), + R = mnesia:dirty_delete({simple,Key}), + AfterT = erlang:now(), + {atomic, elapsed_time(BeforeT,AfterT,R)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Calculate the elapsed time +elapsed_time(BeforeT,AfterT,Result) -> + {(element(1,AfterT)*1000000000000 + +element(2,AfterT)*1000000+element(3,AfterT)) - + (element(1,BeforeT)*1000000000000 + +element(2,BeforeT)*1000000+element(3,BeforeT)),Result}. diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl new file mode 100644 index 0000000000..5f9f2a9733 --- /dev/null +++ b/lib/mnesia/test/mnesia_dirty_access_test.erl @@ -0,0 +1,927 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_dirty_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil dirty access, regardless of transaction scope.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + dirty_write, + dirty_read, + dirty_update_counter, + dirty_delete, + dirty_delete_object, + dirty_match_object, + dirty_index, + dirty_iter, + admin_tests + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Write records dirty + +dirty_write(suite) -> + [ + dirty_write_ram, + dirty_write_disc, + dirty_write_disc_only + ]. + +dirty_write_ram(suite) -> []; +dirty_write_ram(Config) when is_list(Config) -> + dirty_write(Config, ram_copies). + +dirty_write_disc(suite) -> []; +dirty_write_disc(Config) when is_list(Config) -> + dirty_write(Config, disc_copies). + +dirty_write_disc_only(suite) -> []; +dirty_write_disc_only(Config) when is_list(Config) -> + dirty_write(Config, disc_only_copies). + +dirty_write(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_write, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_write([])), + ?match({'EXIT', _}, mnesia:dirty_write({Tab, 2})), + ?match({'EXIT', _}, mnesia:dirty_write({foo, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_write({Tab, 1, 2}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records dirty + +dirty_read(suite) -> + [ + dirty_read_ram, + dirty_read_disc, + dirty_read_disc_only + ]. + +dirty_read_ram(suite) -> []; +dirty_read_ram(Config) when is_list(Config) -> + dirty_read(Config, ram_copies). + +dirty_read_disc(suite) -> []; +dirty_read_disc(Config) when is_list(Config) -> + dirty_read(Config, disc_copies). + +dirty_read_disc_only(suite) -> []; +dirty_read_disc_only(Config) when is_list(Config) -> + dirty_read(Config, disc_only_copies). + +dirty_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_read, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_read([])), + ?match({'EXIT', _}, mnesia:dirty_read({Tab})), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1, 2})), + ?match([], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 3})), + ?match([{Tab, 1, 2}, {Tab, 1, 3}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, [{Tab, 1, 2}, {Tab, 1, 3}]}, + mnesia:transaction(fun() -> mnesia:dirty_read({Tab, 1}) end)), + + ?match(false, mnesia:async_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:sync_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:ets(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(async_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(sync_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(ets, fun() -> mnesia:is_transaction() end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Update counter record dirty + +dirty_update_counter(suite) -> + [ + dirty_update_counter_ram, + dirty_update_counter_disc, + dirty_update_counter_disc_only + ]. + +dirty_update_counter_ram(suite) -> []; +dirty_update_counter_ram(Config) when is_list(Config) -> + dirty_update_counter(Config, ram_copies). + +dirty_update_counter_disc(suite) -> []; +dirty_update_counter_disc(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_copies). + +dirty_update_counter_disc_only(suite) -> []; +dirty_update_counter_disc_only(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_only_copies). + +dirty_update_counter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_update_counter, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab, 1}, [])), + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab}, 3)), + ?match({'EXIT', _}, mnesia:dirty_update_counter({foo, 1}, 3)), + ?match(5, mnesia:dirty_update_counter({Tab, 1}, 3)), + ?match([{Tab, 1, 5}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, 8}, mnesia:transaction(fun() -> + mnesia:dirty_update_counter({Tab, 1}, 3) end)), + + ?match(1, mnesia:dirty_update_counter({Tab, foo}, 1)), + ?match([{Tab, foo,1}], mnesia:dirty_read({Tab,foo})), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete record dirty + +dirty_delete(suite) -> + [ + dirty_delete_ram, + dirty_delete_disc, + dirty_delete_disc_only + ]. + +dirty_delete_ram(suite) -> []; +dirty_delete_ram(Config) when is_list(Config) -> + dirty_delete(Config, ram_copies). + +dirty_delete_disc(suite) -> []; +dirty_delete_disc(Config) when is_list(Config) -> + dirty_delete(Config, disc_copies). + +dirty_delete_disc_only(suite) -> []; +dirty_delete_disc_only(Config) when is_list(Config) -> + dirty_delete(Config, disc_only_copies). + +dirty_delete(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_delete([])), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete({Tab, 1}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete matching record dirty + +dirty_delete_object(suite) -> + [ + dirty_delete_object_ram, + dirty_delete_object_disc, + dirty_delete_object_disc_only + ]. + +dirty_delete_object_ram(suite) -> []; +dirty_delete_object_ram(Config) when is_list(Config) -> + dirty_delete_object(Config, ram_copies). + +dirty_delete_object_disc(suite) -> []; +dirty_delete_object_disc(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_copies). + +dirty_delete_object_disc_only(suite) -> []; +dirty_delete_object_disc_only(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_only_copies). + +dirty_delete_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete_object, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match({'EXIT', _}, mnesia:dirty_delete_object([])), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab, 1})), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + + ?match(ok, mnesia:dirty_write(OneRec)), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['_']}, 21})), + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['$5']}, 21})), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read matching records dirty + +dirty_match_object(suite) -> + [ + dirty_match_object_ram, + dirty_match_object_disc, + dirty_match_object_disc_only + ]. + +dirty_match_object_ram(suite) -> []; +dirty_match_object_ram(Config) when is_list(Config) -> + dirty_match_object(Config, ram_copies). + +dirty_match_object_disc(suite) -> []; +dirty_match_object_disc(Config) when is_list(Config) -> + dirty_match_object(Config, disc_copies). + +dirty_match_object_disc_only(suite) -> []; +dirty_match_object_disc_only(Config) when is_list(Config) -> + dirty_match_object(Config, disc_only_copies). + +dirty_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_match, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match([], mnesia:dirty_match_object(OnePat)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_match_object(OnePat)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_match_object(OnePat) end)), + + ?match({'EXIT', _}, mnesia:dirty_match_object({foo, '$1', 2})), + ?match({'EXIT', _}, mnesia:dirty_match_object({[], '$1', 2})), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index(suite) -> + [ + dirty_index_match_object, + dirty_index_read, + dirty_index_update + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty read matching records by using an index + +dirty_index_match_object(suite) -> + [ + dirty_index_match_object_ram, + dirty_index_match_object_disc, + dirty_index_match_object_disc_only + ]. + +dirty_index_match_object_ram(suite) -> []; +dirty_index_match_object_ram(Config) when is_list(Config) -> + dirty_index_match_object(Config, ram_copies). + +dirty_index_match_object_disc(suite) -> []; +dirty_index_match_object_disc(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_copies). + +dirty_index_match_object_disc_only(suite) -> []; +dirty_index_match_object_disc_only(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_only_copies). + +dirty_index_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_match_object, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{attributes, [k, v]}, {Storage, [Node1]}, {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + OneRec = {Tab, 1, 2}, + ?match(ok, mnesia:dirty_write(OneRec)), + + ?match([OneRec], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({Tab, '$1', 2}, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({foo, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({[], '$1', 2}, ValPos)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos) end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records by using an index + +dirty_index_read(suite) -> + [ + dirty_index_read_ram, + dirty_index_read_disc, + dirty_index_read_disc_only + ]. + +dirty_index_read_ram(suite) -> []; +dirty_index_read_ram(Config) when is_list(Config) -> + dirty_index_read(Config, ram_copies). + +dirty_index_read_disc(suite) -> []; +dirty_index_read_disc(Config) when is_list(Config) -> + dirty_index_read(Config, disc_copies). + +dirty_index_read_disc_only(suite) -> []; +dirty_index_read_disc_only(Config) when is_list(Config) -> + dirty_index_read(Config, disc_only_copies). + +dirty_index_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_read, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{type, set}, + {attributes, [k, v]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:dirty_index_read(Tab, 2, ValPos) end)), + ?match(42, mnesia:dirty_update_counter({Tab, 1}, 40)), + ?match([{Tab,1,42}], mnesia:dirty_read({Tab, 1})), + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([{Tab, 1, 42}], mnesia:dirty_index_read(Tab, 42, ValPos)), + + ?match({'EXIT', _}, mnesia:dirty_index_read(Tab, 2, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read(foo, 2, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read([], 2, ValPos)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index_update(suite) -> + [ + dirty_index_update_set_ram, + dirty_index_update_set_disc, + dirty_index_update_set_disc_only, + dirty_index_update_bag_ram, + dirty_index_update_bag_disc, + dirty_index_update_bag_disc_only + ]; +dirty_index_update(doc) -> + ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_index_update_set_ram(suite) -> []; +dirty_index_update_set_ram(Config) when is_list(Config) -> + dirty_index_update_set(Config, ram_copies). + +dirty_index_update_set_disc(suite) -> []; +dirty_index_update_set_disc(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_copies). + +dirty_index_update_set_disc_only(suite) -> []; +dirty_index_update_set_disc_only(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_only_copies). + +dirty_index_update_set(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:dirty_write(Rec2)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2], lists:sort(R2)), + ?match([Rec2], mnesia:dirty_index_match_object(Pat1, ValPos)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match(ok, mnesia:dirty_write(Rec4)), + R4 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R5 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R6)), + ?match([], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R7 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R9 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + R10 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1], lists:sort(R11)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?verify_mnesia(Nodes, []). + +dirty_index_update_bag_ram(suite) -> []; +dirty_index_update_bag_ram(Config)when is_list(Config) -> + dirty_index_update_bag(Config, ram_copies). + +dirty_index_update_bag_disc(suite) -> []; +dirty_index_update_bag_disc(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_copies). + +dirty_index_update_bag_disc_only(suite) -> []; +dirty_index_update_bag_disc_only(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_only_copies). + +dirty_index_update_bag(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{type, bag}, + {attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R2)), + + R3 = mnesia:dirty_index_match_object(Pat1, ValPos), + ?match([Rec1, Rec2], lists:sort(R3)), + + R4 = mnesia:dirty_match_object(Pat2), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + R5 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R6)), + + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + R7 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + R9 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R9)), + R10 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + R12 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R12)), + R13 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + R14 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R15 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match(ok, mnesia:dirty_delete_object(Rec5)), + R16 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R17 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match(ok, mnesia:dirty_write(Rec1)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + R18 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R19 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty iteration +%% dirty_slot, dirty_first, dirty_next + +dirty_iter(suite) -> + [ + dirty_iter_ram, + dirty_iter_disc, + dirty_iter_disc_only + ]. + +dirty_iter_ram(suite) -> []; +dirty_iter_ram(Config) when is_list(Config) -> + dirty_iter(Config, ram_copies). + +dirty_iter_disc(suite) -> []; +dirty_iter_disc(Config) when is_list(Config) -> + dirty_iter(Config, disc_copies). + +dirty_iter_disc_only(suite) -> []; +dirty_iter_disc_only(Config) when is_list(Config) -> + dirty_iter(Config, disc_only_copies). + +dirty_iter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_iter, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], all_slots(Tab)), + ?match([], all_nexts(Tab)), + + Keys = lists:seq(1, 5), + Records = [{Tab, A, B} || A <- Keys, B <- lists:seq(1, 2)], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + SortedRecords = lists:sort(Records), + ?match(SortedRecords, lists:sort(all_slots(Tab))), + ?match(Keys, lists:sort(all_nexts(Tab))), + + ?match({'EXIT', _}, mnesia:dirty_first(foo)), + ?match({'EXIT', _}, mnesia:dirty_next(foo, foo)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, 0)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, [])), + ?match({atomic, Keys}, + mnesia:transaction(fun() -> lists:sort(all_nexts(Tab)) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Returns a list of all keys in table +all_slots(Tab) -> + all_slots(Tab, [], 0). + +all_slots(_Tab, '$end_of_table', _) -> + []; +all_slots(Tab, PrevRecords, PrevSlot) -> + Records = mnesia:dirty_slot(Tab, PrevSlot), + PrevRecords ++ all_slots(Tab, Records, PrevSlot + 1). + +%% Returns a list of all keys in table + +all_nexts(Tab) -> + FirstKey = mnesia:dirty_first(Tab), + all_nexts(Tab, FirstKey). + +all_nexts(_Tab, '$end_of_table') -> + []; +all_nexts(Tab, PrevKey) -> + Key = mnesia:dirty_next(Tab, PrevKey), + [PrevKey] ++ all_nexts(Tab, Key). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_tests(doc) -> + ["Verifies that dirty operations work during schema operations"]; + +admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_trans(Tab, Key, Acc) -> + Update = + fun() -> + Res = (catch mnesia:read({Tab, Key})), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 3 -> + case catch mnesia:sync_dirty(Update) of + ok -> + update_trans(Tab, Key, Acc+1); + Else -> + ?error("Dirty Operation failed on ~p (update no ~p) with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Acc, + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Tab = schema_ops, + Def = [{disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + ?log("Test case removing table from ~w, with ~w~n", [DelNode, Def]), + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1000), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + + dbg:tracer(process, {fun(Msg,_) -> tracer(Msg) end, void}), + %% dbg:n(Node2), + %% dbg:n(Node3), + %% dbg:tp('_', []), + %% dbg:tpl(dets, [timestamp]), + dbg:p(Pid1, [m,c,timestamp]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + Pid1 ! {self(), quit}, R1 = + receive {Pid1, Res1} -> Res1 + after + 5000 -> io:format("~p~n",[process_info(Pid1)]),error + end, + Pid2 ! {self(), quit}, R2 = + receive {Pid2, Res2} -> Res2 + after + 5000 -> error + end, + Pid3 ! {self(), quit}, R3 = + receive {Pid3, Res3} -> Res3 + after + 5000 -> error + end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +tracer({trace_ts, _, send, Msg, Pid, {_,S,Ms}}) -> + io:format("~p:~p ~p >> ~w ~n",[S,Ms,Pid,Msg]); +tracer({trace_ts, _, 'receive', Msg, {_,S,Ms}}) -> + io:format("~p:~p << ~w ~n",[S,Ms,Msg]); + + +tracer(Msg) -> + io:format("UMsg ~p ~n",[Msg]), + ok. + + + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +%% Not so much diff from 1 but I got a feeling of a bug +%% should behave exactly the same but just checking the internal ordering +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). + +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + ?log("Test case adding table at ~w, with ~w~n", [AddNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + Pid1 ! {self(), quit}, R1 = receive {Pid1, Res1} -> Res1 after 5000 -> error end, + Pid2 ! {self(), quit}, R2 = receive {Pid2, Res2} -> Res2 after 5000 -> error end, + Pid3 ! {self(), quit}, R3 = receive {Pid3, Res3} -> Res3 after 5000 -> error end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). + +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + ?log("Test case move table from ~w to ~w, with ~w~n", [FromNode, ToNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + Pid1 ! {self(), quit}, + R1 = receive {Pid1, Res1} -> Res1 after 5000 -> ?error("timeout pid1~n", []) end, + Pid2 ! {self(), quit}, + R2 = receive {Pid2, Res2} -> Res2 after 5000 -> ?error("timeout pid2~n", []) end, + Pid3 ! {self(), quit}, + R3 = receive {Pid3, Res3} -> Res3 after 5000 -> ?error("timeout pid3~n", []) end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +% Verify consistency between different nodes +% Due to limitations in the current dirty_ops this can wrong from time to time! +verify_oids(Tab, N1, N2, N3, R1, R2, R3) -> + io:format("DEBUG 1=>~p 2=>~p 3=>~p~n", [R1,R2,R3]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])). + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> false = mnesia:is_transaction(), mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl new file mode 100644 index 0000000000..b917b0ca40 --- /dev/null +++ b/lib/mnesia/test/mnesia_durability_test.erl @@ -0,0 +1,1470 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_durability_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-record(test_rec,{key,val}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify durability", + "Verify that the effects of committed transactions are durable.", + "The content of the tables tables must be restored at startup."]; +all(suite) -> + [ + load_tables, + durability_of_dump_tables, + durability_of_disc_copies, + durability_of_disc_only_copies + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_tables(doc) -> + ["Try to provoke all kinds of table load scenarios."]; +load_tables(suite) -> + [ + load_latest_data, + load_local_contents_directly, + load_directly_when_all_are_ram_copiesA, + load_directly_when_all_are_ram_copiesB, + late_load_when_all_are_ram_copies_on_ram_nodes, + load_when_last_replica_becomes_available, + load_when_we_have_down_from_all_other_replica_nodes, + late_load_transforms_into_disc_load, + late_load_leads_to_hanging, + force_load_when_nobody_intents_to_load, + force_load_when_someone_has_decided_to_load, + force_load_when_someone_else_already_has_loaded, + force_load_when_we_has_loaded, + force_load_on_a_non_local_table, + force_load_when_the_table_does_not_exist, + load_tables_with_master_tables + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_latest_data(doc) -> + ["Base functionality, verify that the latest data is loaded"]; +load_latest_data(suite) -> []; +load_latest_data(Config) when is_list(Config) -> + [N1,N2,N3] = Nodes = ?acquire_nodes(3, Config), + %%Create a replicated local table + ?match({atomic,ok}, mnesia:create_table(t0, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t3, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t4, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t5, [{disc_copies,[N1,N2]}])), + Rec1 = {t1, test, ok}, + Rec2 = {t1, test, 2}, + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, rpc:call(N2, mnesia, dirty_write, [Rec2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + %% Should wait for N2 + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 3000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + %% We should find the record + ?match([Rec2], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec2], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + %% ok, lets switch order + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match(ok, mnesia:dirty_delete_object(Rec2)), + %% redo + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + %% Should wait for N1 + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 1000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 1000])), + %% We should find the record + ?match([Rec1], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec1], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + ?verify_mnesia(Nodes, []). + + +load_local_contents_directly(doc) -> + ["Local contents shall always be loaded. Check this by having a local ", + "table on two nodes N1, N2, stopping N1 before N2, an then verifying ", + "that N1 can start without N2 being started."]; +load_local_contents_directly(suite) -> []; +load_local_contents_directly(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + %%Create a replicated local table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{local_content,true}, + {disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + %%Verify that it has local contents. + ?match( true, mnesia:table_info(test_rec,local_content) ), + %%Helper Funs + Write_one = fun(Value) -> mnesia:write(#test_rec{key=1,val=Value}) end, + Read_one = fun(Key) -> mnesia:read( {test_rec, Key}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + %?match([], mnesia_test_lib:stop_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + + %%Write a value on N2, same key but a different value + ?match({atomic,ok}, + rpc:call( N2, mnesia, transaction, [Write_one,[22]] ) ), + %%Stop Mnesia on N2 + %?match([], mnesia_test_lib:stop_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + %%Restart Mnesia on N1 verify that we can read from it without + %%starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Read back the value + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + %%Restart Mnesia on N2 and verify the contents there. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match( {atomic,[#test_rec{key=1,val=22}]}, + rpc:call(N2, mnesia, transaction, [Read_one,[1]] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesA(doc) -> + ["Tables that are RAM copies only shall also be loaded directly. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2. ", + "2. When N1 starts he shall have access to the table ", + " without having to start N2" ]; +load_directly_when_all_are_ram_copiesA(suite) -> []; +load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=2,val=Value}) end, + Read_one = fun() -> mnesia:read({test_rec,2}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N1 verify that we can access test_rec from + %%N1 without starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N2 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N2], [test_rec])), + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N2, mnesia, transaction, [Read_one] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1, mnesia, transaction, [Read_one] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesB(doc) -> + ["Tables that are RAM copies only shall be loaded from a replicat ", + "when possible. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2.", + "2. Now start N2 first and then N1, N1 shall then load the table ", + " from N2."]; +load_directly_when_all_are_ram_copiesB(suite) -> []; +load_directly_when_all_are_ram_copiesB(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=3,val=Value}) end, + Read_one = fun() -> mnesia:read( {test_rec, 3}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + ?match({atomic,[#test_rec{key=3,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=3,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N2 verify that we can access test_rec from + %%N2 without starting Mnesia on N1. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N2,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N1 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N1], [test_rec])), + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Check that the start of Mnesai on N1 did not affect the contents on N2 + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_when_all_are_ram_copies_on_ram_nodes(doc) -> + ["Load of ram_copies tables when all replicas resides on disc less nodes"]; +late_load_when_all_are_ram_copies_on_ram_nodes(suite) -> + [ + late_load_when_all_are_ram_copies_on_ram_nodes1, + late_load_when_all_are_ram_copies_on_ram_nodes2 + ]. + +late_load_when_all_are_ram_copies_on_ram_nodes1(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes1(Config) when is_list(Config) -> + [N1, N2] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes2(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes2(Config) when is_list(Config) -> + [N1, N2, N3] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2, N3], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes(DiscNode, RamNs, _Config) + when DiscNode == node() -> + ?match(ok, mnesia:create_schema([DiscNode])), + ?match(ok, mnesia:start()), + Nodes = [DiscNode | RamNs], + Extra = [{extra_db_nodes, Nodes}], + Ok = [ok || _ <- RamNs], + ?match({Ok, []}, rpc:multicall(RamNs, mnesia, start, [Extra])), + ?match([], wait_until_running(Nodes)), + + LastRam = lists:last(RamNs), + %% ?match({atomic, ok}, + %% mnesia:add_table_copy(schema, LastRam, ram_copies)), + Def = [{ram_copies, RamNs}, {attributes, record_info(fields, test_rec)}], + ?match({atomic,ok}, mnesia:create_table(test_rec, Def)), + ?verify_mnesia(Nodes, []), + ?match([], mnesia_test_lib:stop_mnesia(RamNs)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start()), + + Rec1 = #test_rec{key=3, val=33}, + Rec2 = #test_rec{key=4, val=44}, + + FirstRam = hd(RamNs), + ?match(ok, rpc:call(FirstRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(FirstRam, mnesia, wait_for_tables, + [[test_rec], 30000])), + ?match(ok, rpc:call(FirstRam, mnesia, dirty_write,[Rec1])), + ?match(ok, mnesia:wait_for_tables([test_rec], 30000)), + mnesia:dirty_write(Rec2), + + if + FirstRam /= LastRam -> + ?match(ok, rpc:call(LastRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(LastRam, mnesia, wait_for_tables, + [[test_rec], 30000])); + true -> + ignore + end, + ?match([Rec1], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 3}])), + ?match([Rec2], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 4}])), + ?verify_mnesia(Nodes, []). + +wait_until_running(Nodes) -> + wait_until_running(Nodes, 30). + +wait_until_running(Nodes, Times) when Times > 0-> + Alive = mnesia:system_info(running_db_nodes), + case Nodes -- Alive of + [] -> + []; + Remaining -> + timer:sleep(timer:seconds(1)), + wait_until_running(Remaining, Times - 1) + end; +wait_until_running(Nodes, _) -> + Nodes. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_when_last_replica_becomes_available(doc) -> + ["Check that when all Mnesia nodes die at the same instant, then the ", + "replicated table shall be accessible when the last node is started ", + "again.", + "Checked by cheating. Start Mnesia on N1, N2, N3. Have a table ", + "replicated on disc on all three nodes, fill in some transactions, ", + "install a fallback. Restart mnesia on all nodes" + "This is the cheat and it simulates that all nodes died at the same ", + "time. Check that the table is only accessible after the last node ", + "has come up."]; +load_when_last_replica_becomes_available(suite) -> []; +load_when_last_replica_becomes_available(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,11]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,22]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,33]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + + ?match(ok, mnesia:backup("test_last_replica")), + ?match(ok, mnesia:install_fallback("test_last_replica")), + file:delete("test_last_replica"), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + %%Start Mnesia on one node, make sure that test_rec is not available + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 10000])), + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 10000])), + %%Start the third node + ?match(ok, rpc:call(N3, mnesia, start, [])), + %%Make sure that the table is loaded everywhere + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_when_we_have_down_from_all_other_replica_nodes(doc) -> + ["The table can be loaded if this node was the last one surviving. ", + "Check this by having N1, N2, N3 and a table replicated on all those ", + "nodes. Then kill them in the N1, N2, N3 order. Then start N3 and ", + "verify that the table is available with correct contents."]; +load_when_we_have_down_from_all_other_replica_nodes(suite) -> []; +load_when_we_have_down_from_all_other_replica_nodes(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,111]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,222]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,333]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[22,22]])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[33,33]])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?verbose("Mnesia stoped on all three nodes.~n",[]), + + %%Start Mnesia on N3; wait for 'test_rec' table to load + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[#test_rec{key=22,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[22]]) ), + ?match({atomic,[#test_rec{key=33,val=33}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[33]]) ), + ?verify_mnesia([N3], [N1, N2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_transforms_into_disc_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is force loaded, and Mnesia decides to load it from another ", + "Mnesia node because it is avaliable there. The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node to really ", + "force load from disc.", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1 and then do a force load on N1. This force load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_transforms_into_disc_load(suite) -> []; +late_load_transforms_into_disc_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + {success, [A, B]} = ?start_activities(Nodes), + + ?match(Node1, node(A)), + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + TestPid = self(), + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + + mnesia_test_lib:kill_mnesia([Node2]), + TestPid ! {self(),debug_fun_was_called}, + + ?verbose("interrupt late_log - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + + %% start Mnesia on node1 + ?match(ok,mnesia:start()), + ?match(yes, mnesia:force_load_table(Tab)), + ?match(ok, mnesia:wait_for_tables([Tab],timer:seconds(30))), + + receive_messages([debug_fun_was_called]), + + check_tables([A],[{Tab,111},{Tab,222}],[[{Tab,111,4711}],[{Tab,222,42}]]), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_leads_to_hanging(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is loaded, and Mnesia decides to load it from another ", + "Mnesia node because it has the latest copy there. ", + "The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node not to ", + "force load from disc but to wait for the other node to come up again", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1. This load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_leads_to_hanging(suite) -> []; +late_load_leads_to_hanging(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(), PrevContext, EvalContext]), + mnesia_test_lib:kill_mnesia([Node2]), + ?verbose("interrupt late load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 333, 666}])), + + %% start Mnesia on node1 + ?match(ok, mnesia:start()), + + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], timer:seconds(2))), + + ?match({'EXIT', {aborted, _}}, mnesia:dirty_read({Tab, 222})), + %% mnesia on node1 is waiting for node2 coming up + + ?match(ok, rpc:call(Node2, mnesia, start, [])), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 333, 666}], mnesia:dirty_read({Tab, 333})), + ?verify_mnesia([Node2, Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_nobody_intents_to_load(doc) -> + ["Normal force load. Start N1 N2, kill in N1, N2 order. Start N1 do ", + "force load. Did it work?"]; +force_load_when_nobody_intents_to_load(suite) -> []; +force_load_when_nobody_intents_to_load(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1a = #test_rec{key=1,val=111}, + Trec1b = #test_rec{key=1,val=333}, + Trec2a = #test_rec{key=2,val=222}, + Trec3a = #test_rec{key=3,val=333}, + Trec3b = #test_rec{key=3,val=666}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1a]])), + %%Check it + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia on N1 + ?match([], mnesia_test_lib:stop_mnesia([N1])), + %%Write and check value while N1 is down + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec1b]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2a]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec3a]])), + ?match({aborted,{node_not_running,N1}}, + rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec1b]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec2a]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec3a]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + %%Shut down Mnesia on N2 + ?match([], mnesia_test_lib:stop_mnesia([N2])), + + %%Restart Mnesia on N1 + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Check that table is not available (waiting for N2) + ?match({timeout,[Table]}, + rpc:call(N1, mnesia, wait_for_tables, [[Table], 3000])), + + %%Force load on N1 + ?match(yes,rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check values + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Write a value for key=3 + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec3b]])), + + %%Restart N2 and check values + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[Table], 30000])), + + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + + ?match({atomic,[]},rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + + ?match({atomic,[Trec3b]},rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[Trec3b]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_has_decided_to_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "Start N1 and N2, replicate table, kill in N1, N2 order. Start N2 ", + "and start N1 before N2 has really loaded the table but after N2 has ", + "decided to load it."]; + +force_load_when_someone_has_decided_to_load(suite) -> []; +force_load_when_someone_has_decided_to_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + {success, [A, B]} = ?start_activities(Nodes), + ?match(Node1, node(A)), %% Just to check :) + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + Self = self(), + DebugId = {mnesia_controller, late_disc_load}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late disc load, + pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + Self ! {self(), fun_in_postion}, + wait_for_signal(), + ?verbose("interrupt late disc load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + %% kill mnesia on node2 + mnesia_test_lib:kill_mnesia([Node2]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?remote_activate_debug_fun(Node2,DebugId, DebugFun, 1), + + B ! fun() -> mnesia:start() end, + [{Mnesia_Pid, fun_in_postion}] = receive_messages([fun_in_postion]), + + %% start Mnesia on node1 + A ! fun() -> mnesia:start() end, + ?match_receive(timeout), +% Got some problem with this testcase when we modified mnesia init +% These test cases are very implementation dependent! +% A ! fun() -> mnesia:wait_for_tables([Tab], 3000) end, +% ?match_receive({A, {timeout, [Tab]}}), + A ! fun() -> mnesia:force_load_table(Tab) end, + ?match_receive(timeout), + + Mnesia_Pid ! continue, + ?match_receive({B, ok}), + ?match_receive({A, ok}), + ?match_receive({A, yes}), + + B ! fun() -> mnesia:wait_for_tables([Tab], 10000) end, + ?match_receive({B, ok}), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 222, 815}], mnesia:dirty_read({Tab, 222})), + ?verify_mnesia(Nodes, []). + +wait_for_signal() -> + receive + continue -> ok + %% Don't eat any other mnesia internal msg's + after + timer:minutes(2) -> ?error("Timedout in wait_for_signal~n", []) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_else_already_has_loaded(doc) -> + ["Normal case. Do a force load when somebody else has loaded the table. ", + "Start N1, N2, kill in N1, N2 order. Start N2 load the table, start N1 ", + "force load. Did it work? (i.e: did N1 load the table from N2 as that", + "one is the latest version and it is available on N2)"]; + +force_load_when_someone_else_already_has_loaded(suite) -> []; +force_load_when_someone_else_already_has_loaded(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia([N1])), + timer:sleep(500), + ?match([], mnesia_test_lib:stop_mnesia([N2])), + %%Restart Mnesia on N2;wait for tables to load + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Write one value + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2]])), + %%Start on N1; force load + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %% === there must be a Trec2 here !!!! + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_we_has_loaded(doc) -> + ["Force load a table we already have loaded"]; +force_load_when_we_has_loaded(suite) -> []; +force_load_when_we_has_loaded(Config) when is_list(Config) -> + [N1] = Nodes = ?acquire_nodes(1, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + %%Restart Mnesia;wait for tables to load + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Table])), + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec2]])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_on_a_non_local_table(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that isn't replicated on this node."]; +force_load_on_a_non_local_table(suite) -> []; +force_load_on_a_non_local_table(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes( 3, Config), + TableNodes = lists:sublist(Nodes,2), + Table = test_rec, + Trec1 = #test_rec{key=1,val=11}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,TableNodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( TableNodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it from the other nodes + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1]},rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + + %%Make sure that Table is non-local + ?match_inverse(N3, rpc:call(N3,mnesia,table_info,[Table,where_to_read])), + %%Try to force load it + ?match(yes, rpc:call(N3,mnesia,force_load_table,[Table])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_the_table_does_not_exist(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that doesn't exist."]; +force_load_when_the_table_does_not_exist(suite) -> []; +force_load_when_the_table_does_not_exist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes( 2, Config), + + %%Dummy table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Tab = dummy, + %%Make sure that Tab is an unknown table + ?match( false, lists:member(Tab,mnesia:system_info(tables)) ), + ?match( {error, {no_exists, Tab}}, mnesia:force_load_table(Tab) ), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_tables_with_master_tables(doc) -> + ["Verifies the semantics of different master nodes settings", + "The semantics should be:", + "1. Mnesia downs, Normally decides from where mnesia should load tables", + "2. Master tables (overrides mnesia downs) ", + "3. Force load (overrides Master tables) ", + "--- 1st from active master nodes", + "--- 2nd from active nodes", + "--- 3rd get local copy (if ram create new one)" + ]; + +load_tables_with_master_tables(suite) -> + [master_nodes, + starting_master_nodes, + master_on_non_local_tables, + remote_force_load_with_local_master_node]. + + +-define(SDwrite(Tup), fun() -> mnesia:write(Tup) end). + +master_nodes(suite) -> []; +master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + + %% Test one: Master A and the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 2: Master [A,B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 3: Master [A,B] and B is down the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, _Unknown}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 4: Master [B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 5: Master [B] and B is down the table should not be loaded + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + %% Test 6: Force load on table that couldn't be loaded due to master + %% table setttings, loads other active replicas i.e. from C + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, update_2}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 7: Master [B] and B is down the table should not be loaded, + %% force_load when there are no active replicas availible + %% should generate a load of a local table + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B, C]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia([A], [B,C]). + +starting_master_nodes(suite) -> []; +starting_master_nodes(doc) -> + ["Complementory to TEST 5 and 6 above, if the master node (B) starts" + " and loads the table it should be loaded on the waiting node (A) "]; +starting_master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = starting_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + %% Start by checking TEST 5 above. + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + %% Start the B node and the table should be loaded on A! + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?verify_mnesia([A,B,C], []). + + +master_on_non_local_tables(suite) -> []; +master_on_non_local_tables(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_non_local, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [B, C]}])), + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + %% Test 1: Test that table info are updated when master node comes up + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ErrorRead = {badrpc,{'EXIT', {aborted,{no_exists,[test_table_non_local,1]}}}}, + ErrorWrite = {badrpc,{'EXIT', {aborted,{no_exists,test_table_non_local}}}}, + ?match(ErrorRead, rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(ErrorWrite, rpc:call(A, mnesia, dirty_write, [{Tab, 1, updated_twice}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(B, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, init})])), + + %% Test 2: Test that table info are updated after force_load + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(C, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated_twice})])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 10000])), + + ?match([{Tab, 1, updated_twice}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia(Nodes, []). + +remote_force_load_with_local_master_node(doc) -> + ["Force load a table on a remote node while the ", + "local node is down. Start the local node and ", + "verfify that the tables is loaded from disc locally " + "if the local node has itself as master node and ", + "the remote node has both the local and remote node ", + "as master nodes"]; +remote_force_load_with_local_master_node(suite) -> []; +remote_force_load_with_local_master_node(Config) when is_list(Config) -> + [A, B] = Nodes = ?acquire_nodes(2, Config), + + Tab = remote_force_load_with_local_master_node, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match(ok, rpc:call(B, mnesia, set_master_nodes, [Tab, [B]])), + + W = fun(Who) -> mnesia:write({Tab, who, Who}) end, + ?match({atomic, ok}, rpc:call(A,mnesia, sync_transaction, [W, [a]])), + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [W, [b]])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, a}], rpc:call(A, mnesia, dirty_read, [{Tab, who}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, b}], rpc:call(B, mnesia, dirty_read, [{Tab, who}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_dump_tables(doc) -> + [ "Verify that all tables contain the correct data when Mnesia", + "is restarted and tables are loaded from disc to recover", + " their previous contents. " ]; +durability_of_dump_tables(suite) -> [dump_ram_copies, + dump_disc_copies, + dump_disc_only]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_ram_copies(doc) -> + ["Check that ram_copies tables are loaded with the" + "contents that had been dumped before Mnesia", + "was restarted. " ]; +dump_ram_copies(suite) -> []; +dump_ram_copies(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %?verbose("second case ~n"), + {P3,P1,P2}; + _ -> + {P1,P2,P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + %% ram copies table on 2 nodes + + Tab = dump_table, + Def = [{attributes, [key, value]}, + {ram_copies, [Node1,Node2]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + + ?match( {atomic,ok}, mnesia:dump_tables([Tab])), + + %% perform updates (they shall be lost after kill Mnesia ) + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% add another replica on node3 + mnesia:add_table_copy(Tab,Node3,ram_copies), + + %% all 3 replicas shall have the new contents + cross_check_tables([A,B,C],Tab, + {[{Tab,1,815}],[{Tab,2,915}],[{Tab,3,256}]}), + + %% kill mnesia on node 3 + mnesia_test_lib:kill_mnesia([Node3]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(2)), + + mnesia_test_lib:kill_mnesia([Node1,Node2]), %% kill them as well + timer:sleep(timer:seconds(2)), + + %% start Mnesia only on node 3 + ?verbose("starting mnesia on Node3~n",[]), + + %% test_lib:mnesia_start doesnt work, because it waits + %% for the schema on all nodes ... ??? + ?match(ok,rpc:call(Node3,mnesia,start,[]) ), + ?match(ok,rpc:call(Node3,mnesia,wait_for_tables, + [[Tab],timer:seconds(30)] ) ), + + %% node3 shall have the conents of the dump + cross_check_tables([C],Tab,{[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + + %% start Mnesia on the other 2 nodes, too + mnesia_test_lib:start_mnesia([Node1,Node2],[Tab]), + + cross_check_tables([A,B,C],Tab, + {[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table + +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! + +dump_disc_copies(doc) -> + ["Check that it is not possible to dump disc_copies tables"]; +dump_disc_copies(suite) -> []; +dump_disc_copies(Config) when is_list(Config) -> + do_dump_copies(Config, disc_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! +dump_disc_only(doc) -> + ["Check that it is not possible to dump disc_only_copies tables"]; +dump_disc_only(suite) -> []; +dump_disc_only(Config) when is_list(Config) -> + do_dump_copies(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_dump_copies(Config,Copies) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + + Tab = dump_copies, + Def = [{attributes, [key, value]}, + {Copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + ?match( {aborted, {"Only allowed on ram_copies",Tab,[Node1]}}, + mnesia:dump_tables([Tab])), + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + mnesia_test_lib:start_mnesia([Node1],[Tab]), + + ?match([{Tab, 1, 815}], mnesia:dirty_read({Tab,1}) ), + ?match([{Tab, 2, 915}], mnesia:dirty_read({Tab,2}) ), + ?match([{Tab, 3, 256}], mnesia:dirty_read({Tab,3}) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +durability_of_disc_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_copies"]; + +durability_of_disc_copies(suite) -> []; +durability_of_disc_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_copies). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_disc_only_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_only_copies"]; +durability_of_disc_only_copies(suite) -> []; +durability_of_disc_only_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_disc_durability(Config,CopyType) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(1)}]), + {success, [A,B,C]} = ?start_activities(Nodes), + + Tab_set = disc_durability_set, + Def_set = [{attributes, [key, value]}, + {CopyType, Nodes}], + + Tab_bag = disc_durability_bag, + Def_bag = [{attributes, [key, value]}, + {type, bag}, + {CopyType, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab_set, Def_set)), + ?match({atomic, ok}, mnesia:create_table(Tab_bag, Def_bag)), + + %% do updates + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_set, 11, 1111}), + mnesia:write({Tab_set, 22, 2222}), + mnesia:write({Tab_set, 33, 3333}), + mnesia:write({Tab_set, 55, 5555}) + end)), + mnesia:dirty_write({Tab_set, 44, 4444}), + + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_bag, 11, a_1111}), + mnesia:write({Tab_bag, 11, b_1111}), + mnesia:write({Tab_bag, 22, a_2222}), + mnesia:write({Tab_bag, 22, b_2222}), + mnesia:write({Tab_bag, 33, a_3333}), + mnesia:write({Tab_bag, 33, b_3333}) + end)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> mnesia:delete({Tab_set, 22}) end)), + ?match(ok, mnesia:dirty_delete({Tab_set, 33})), + ?match(5558, mnesia:dirty_update_counter({Tab_set, 55}, 3)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:delete_object({Tab_bag, 22, b_2222}) + end)), + ?match(ok, mnesia:dirty_delete_object({Tab_bag, 33, b_3333})), + ?match(10, mnesia:dirty_update_counter({Tab_set, counter}, 10)), + ?match({atomic, ok}, % Also syncs update_counter + mnesia:sync_transaction(fun() -> mnesia:write({Tab_set,66,6666}) end)), + + Updated = {[[{Tab_set,counter,10}], + [{Tab_set,counter,10}], + [{Tab_set,counter,10}]],[]}, + ?match(Updated, rpc:multicall(Nodes, mnesia, dirty_read, [Tab_set,counter])), + + %% kill mnesia on all nodes, start it again and check the data + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab_set,Tab_bag]), + + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), %% Debugging strange msgs.. + ?log("Processes ~p ~p ~p~n", [A,B,C]), + check_tables([A,B,C], + [{Tab_set,11}, {Tab_set,22},{Tab_set,33}, + {Tab_set,44},{Tab_set,55}, {Tab_set,66}, + {Tab_bag,11}, {Tab_bag,22},{Tab_bag,33}, + {Tab_set, counter}], + [[{Tab_set, 11, 1111}], [], [], [{Tab_set, 44, 4444}], + [{Tab_set, 55, 5558}], [{Tab_set, 66, 6666}], + lists:sort([{Tab_bag, 11, a_1111},{Tab_bag, 11, b_1111}]), + [{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}], + [{Tab_set, counter, 10}]]), + + timer:sleep(1000), %% Debugging strange msgs.. + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +%% +%% all the processes in the PidList shall find all +%% table entries in ValList + +check_tables([],_vallist,_resultList) -> ok; +check_tables([Pid|Rest],ValList,ResultList) -> + Pid ! fun () -> + check_values(ValList) + end, + ?match_receive({ Pid, ResultList }), + check_tables(Rest,ValList,ResultList). + +check_values([]) -> []; +check_values([{Tab,Key}|Rest]) -> + Ret = lists:sort(mnesia:dirty_read({Tab,Key})), + [Ret|check_values(Rest)]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + timeout -> + case lists:member(timeout, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),timeout]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p ~n", [self(),timeout]), + [ timeout | receive_messages(ListOfMsgs -- [timeout])] + end; + + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:seconds(40) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + diff --git a/lib/mnesia/test/mnesia_evil_backup.erl b/lib/mnesia/test/mnesia_evil_backup.erl new file mode 100644 index 0000000000..bbbebeb02c --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_backup.erl @@ -0,0 +1,750 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%---------------------------------------------------------------------- +%%% File : mnesia_evil_backup.erl +%%% Author : Dan Gudmundsson <dgud@legolas> +%%% Purpose : Evil backup tests +%%% Created : 3 Jun 1998 by Dan Gudmundsson <[email protected]> +%%%---------------------------------------------------------------------- + +-module(mnesia_evil_backup). +-author('[email protected]'). +-compile(export_all). +-include("mnesia_test_lib.hrl"). + +%%-export([Function/Arity, ...]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Checking all the functionality regarding ", + "to the backup and different ", + "kinds of restore and fallback interface"]; + +all(suite) -> + [ + backup, + bad_backup, + global_backup_checkpoint, + restore_tables, + traverse_backup, + selective_backup_checkpoint, + incremental_backup_checkpoint, +%% local_backup_checkpoint, + install_fallback, + uninstall_fallback, + local_fallback, + sops_with_checkpoint + ]. + +backup(doc) -> ["Checking the interface to the function backup", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +backup(suite) -> []; +backup(Config) when is_list(Config) -> + [Node1, Node2] = _Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + + File2 = "backup_test2.BUP", + Tab2 = backup_tab2, + Def2 = [{disc_only_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match(ok, mnesia:backup(File2, mnesia_backup)), + + File3 = "backup_test3.BUP", + mnesia_test_lib:kill_mnesia([Node2]), + ?match({error, _}, mnesia:backup(File3, mnesia_backup)), + + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia([Node1], [Node2]). + + +bad_backup(suite) -> []; +bad_backup(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + file:write_file(File, "trash", [append]), + ?match(ok, mnesia:dirty_write({Tab, 1, test_bad})), + ?match({atomic,[Tab]}, mnesia:restore(File, [{clear_tables, [Tab]}])), + ?match([{Tab,1,test_ok}], mnesia:dirty_read(Tab, 1)), + + ?match(ok, file:delete(File)), + ?verify_mnesia([Node1], []). + + + +global_backup_checkpoint(doc) -> + ["Checking the interface to the function backup_checkpoint", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +global_backup_checkpoint(suite) -> []; +global_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_cp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + File = "backup_checkpoint.BUP", + File2 = "backup_checkpoint2.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match({error, _}, mnesia:backup_checkpoint(cp_name, File)), + Spec = [{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, _Ns}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + ?match({error, _}, mnesia:backup_checkpoint(cp_name_nonexist, File)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File2, mnesia_backup)), + ?match({error, _}, file:delete(File)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + +restore_tables(doc) -> + ["Tests the interface of restore"]; + +restore_tables(suite) -> + [ + restore_errors, + restore_clear, + restore_keep, + restore_recreate, + restore_clear_ram + ]. + +restore_errors(suite) -> []; +restore_errors(Config) when is_list(Config) -> + [_Node] = ?acquire_nodes(1, Config), + ?match({aborted, enoent}, mnesia:restore(notAfile, [])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, not_a_list)), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [test_badarg])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{test_badarg, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{skip_tables, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{recreate_tables, [schema]}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{default_op, asdklasd}])), + ok. + +restore_clear(suite) -> []; +restore_clear(Config) when is_list(Config) -> + restore(Config, clear_tables). + +restore_keep(suite) -> []; +restore_keep(Config) when is_list(Config) -> + restore(Config, keep_tables). + +restore_recreate(suite) -> []; +restore_recreate(Config) when is_list(Config) -> + restore(Config, recreate_tables). + +check_tab(Records, Line) -> + Verify = fun({Table, Key, Val}) -> + case catch mnesia:dirty_read({Table, Key}) of + [{Table, Key, Val}] -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), {Table, Key, Val}, Else], + ?MODULE, Line), + exit(error) + end; + (Recs) -> + [{Tab, Key, _}, _] = Recs, + SRecs = lists:sort(Recs), + R_Recs = lists:sort(catch mnesia:dirty_read({Tab, Key})), + case R_Recs of + SRecs -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), SRecs, Else], + ?MODULE, Line), + exit(error) + end + end, + lists:foreach(Verify, Records). + +restore(Config, Op) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab1 = ram_snmp, + Def1 = [{snmp, [{key, integer}]}, {ram_copies, [Node1]}], + Tab2 = disc_index, + Def2 = [{index, [val]}, {disc_copies, [Node1, Node2]}], + Tab3 = dionly_bag, + Def3 = [{type, bag}, {disc_only_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + File1 = "restore1.BUP", + File2 = "restore2.BUP", + + Restore = fun(O, A) -> + case mnesia:restore(O, A) of + {atomic, Tabs} when is_list(Tabs) -> {atomic, lists:sort(Tabs)}; + Other -> Other + end + end, + Tabs = lists:sort([Tab1, Tab2, Tab3]), + + [mnesia:dirty_write({Tab1, N, N+42}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab2, N, N+43}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab3, N, N+44}) || N <- lists:seq(1, 10)], + + Res1 = [{Tab1, N, N+42} || N <- lists:seq(1, 10)], + Res2 = [{Tab2, N, N+43} || N <- lists:seq(1, 10)], + Res3 = [{Tab3, N, N+44} || N <- lists:seq(1, 10)], + + {ok, Name, _} = mnesia:activate_checkpoint([{min, Tabs}, {ram_overrides_dump, true}]), + file:delete(File1), + + %% Test standard Restore on one table on one node + ?match(ok, mnesia:backup_checkpoint(Name, File1)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(ok, mnesia:backup(File2)), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + _Res11 = [{Tab1, N, N+1} || N <- lists:seq(1, 11)], + Res21 = [{Tab2, N, N+1} || N <- lists:seq(1, 11)], + Res31 = [[{Tab3, N, N+1}, {Tab3, N, N+44}] || N <- lists:seq(1, 10)], + + ?match({atomic, [Tab1]}, Restore(File1, [{Op, [Tab1]}, + {skip_tables, Tabs -- [Tab1]}])), + case Op of + keep_tables -> + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})); + clear_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})); + recreate_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})) + end, + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res21, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + + %% Restore all tables on it's nodes + mnesia_schema:clear_table(Tab1), + mnesia_schema:clear_table(Tab2), + mnesia_schema:clear_table(Tab3), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab1})), + + ?match({atomic, Tabs}, Restore(File1, [{default_op, Op}, + {module, mnesia_backup}])), + case Op of + clear_tables -> + ?match_receive({mnesia_table_event, {delete, {schema, Tab1}, _}}), + ?match_receive({mnesia_table_event, {write, {schema, Tab1, _}, _}}), + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + keep_tables -> + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})), + ?match([{Tab2, 11, 12}], mnesia:dirty_read({Tab2, 11})), + ?match([{Tab3, 11, 12}], mnesia:dirty_read({Tab3, 11})), + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + %% Check Index + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + %% Check Snmp + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match({ok, {Tab1, 11, 12}}, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + recreate_tables -> + check_subscr(Tab1, 0), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + Ns = lists:sort([Node1, Node2]), + ?match(Ns, lists:sort(mnesia:table_info(Tab2, where_to_write))) + end, + ?match(ok, file:delete(File1)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + + +check_subscr(Tab) -> + check_subscr(Tab, 10). + +check_subscr(_Tab, 0) -> + receive + Msg -> + ?error("Too many msgs ~p~n", [Msg]) + after 500 -> + ok + end; +check_subscr(Tab, N) -> + V = N +42, + receive + {mnesia_table_event, {write, {Tab, N, V}, _}} -> + check_subscr(Tab, N-1) + after 500 -> + ?error("Missing ~p~n", [{Tab, N, V}]) + end. + +restore_clear_ram(suite) -> []; +restore_clear_ram(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, [{diskless, true}|Config]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + + Write = fun(What) -> + mnesia:write({a,1,What}), + mnesia:write({a,2,What}), + mnesia:write({a,3,What}) + end, + Bup = "restore_clear_ram.BUP", + + ?match({atomic, ok}, mnesia:transaction(Write, [initial])), + ?match({ok, _, _}, mnesia:activate_checkpoint([{name,test}, + {min, [schema, a]}, + {ram_overrides_dump, true}])), + ?match(ok, mnesia:backup_checkpoint(test, Bup)), + + ?match({atomic, ok}, mnesia:transaction(Write, [data])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + + restore_clear_ram_loop(100, Nodes, Bup), + + ok. + +restore_clear_ram_loop(N, Nodes = [N1,N2,N3], Bup) when N > 0 -> + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match({_, []}, rpc:multicall([N1,N2], mnesia, start, [[{extra_db_nodes, Nodes}]])), + Key = rpc:async_call(N3, mnesia, start, [[{extra_db_nodes, Nodes}]]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + ?match(ok, rpc:yield(Key)), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[a], 3000])), + case rpc:multicall(Nodes, mnesia, table_info, [a,size]) of + {[3,3,3], []} -> + restore_clear_ram_loop(N-1, Nodes, Bup); + Error -> + ?match(3, Error) + end; +restore_clear_ram_loop(_,_,_) -> + ok. + +traverse_backup(doc) -> + ["Testing the traverse_backup interface, the resulting file is not tested though", + "See install_fallback for result using the output file from traverse_backup", + "A side effect is that the backup file contents are tested"]; +traverse_backup(suite) -> []; +traverse_backup(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + File = "_treverse_backup.BUP", + File2 = "traverse_backup2.BUP", + File3 = "traverse_backup3.BUP", + ?match(ok, mnesia:backup(File)), + + Fun = fun({backup_tab, N, _}, Acc) -> {[{backup_tab, N, test_ok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + + ?match({ok, 5}, mnesia:traverse_backup(File, read_only, Fun, 0)), + ?match(ok, file:delete(read_only)), + + ?match({ok, 5}, mnesia:traverse_backup(File, mnesia_backup, + dummy, read_only, Fun, 0)), + + ?match({ok, 5}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 5}, mnesia:traverse_backup(File2, mnesia_backup, + File3, mnesia_backup, Fun, 0)), + + BadFun = fun({bad_tab, _N, asd}, Acc) -> {{error, error}, Acc} end, + ?match({error, _}, mnesia:traverse_backup(File, read_only, BadFun, 0)), + ?match({error, _}, file:delete(read_only)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ?verify_mnesia(Nodes, []). + + +install_fallback(doc) -> + ["This tests the install_fallback intf.", + "It also verifies that the output from backup_checkpoint and traverse_backup", + "is valid"]; +install_fallback(suite) -> []; +install_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = fallbacks_test, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + + Tab2 = fallbacks_test2, + Def2 = [{disc_copies, [node()]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + Tab3 = fallbacks_test3, + ?match({atomic, ok}, mnesia:create_table(Tab3, Def2)), + Fun2 = fun(Key) -> + Rec = {Tab2, Key, test_ok}, + mnesia:dirty_write(Rec), + [Rec] + end, + TabSize3 = 1000, + OldRecs2 = [Fun2(K) || K <- lists:seq(1, TabSize3)], + + Spec =[{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, Nodes}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:dirty_write({Tab, 6, test_nok})), + [mnesia:dirty_write({Tab2, K, test_nok}) || K <- lists:seq(1, TabSize3 + 10)], + File = "install_fallback.BUP", + File2 = "install_fallback2.BUP", + File3 = "install_fallback3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + + Fun = fun({T, N, _}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("write ~p -> ~p~n", [N, T]), + {[{T, N, test_ok}], Acc + 1}; + 1 -> + io:format("write ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N, test_ok}], Acc + 1} + end; + ({T, N}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("delete ~p -> ~p~n", [N, T]), + {[{T, N}], Acc + 1}; + 1 -> + io:format("delete ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N}], Acc + 1} + end; + (Other, Acc) -> + {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match(ok, mnesia:install_fallback(File2)), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab, Tab2, Tab3])), + + % Verify + ?match([], mnesia:dirty_read({Tab, 1})), + ?match([{Tab3, 1, test_ok}], mnesia:dirty_read({Tab3, 1})), + ?match([{Tab, 2, test_ok}], mnesia:dirty_read({Tab, 2})), + ?match([], mnesia:dirty_read({Tab3, 2})), + ?match([], mnesia:dirty_read({Tab, 3})), + ?match([{Tab3, 3, test_ok}], mnesia:dirty_read({Tab3, 3})), + ?match([{Tab, 4, test_ok}], mnesia:dirty_read({Tab, 4})), + ?match([], mnesia:dirty_read({Tab3, 4})), + ?match([], mnesia:dirty_read({Tab, 5})), + ?match([{Tab3, 5, test_ok}], mnesia:dirty_read({Tab3, 5})), + ?match([], mnesia:dirty_read({Tab, 6})), + ?match([], mnesia:dirty_read({Tab3, 6})), + ?match([], [mnesia:dirty_read({Tab2, K}) || K <- lists:seq(1, TabSize3)] -- OldRecs2), + ?match(TabSize3, mnesia:table_info(Tab2, size)), + + % Check the interface + file:delete(File3), + ?match({error, _}, mnesia:install_fallback(File3)), + ?match({error, _}, mnesia:install_fallback(File2, mnesia_badmod)), + ?match(ok, mnesia:install_fallback(File2, mnesia_backup)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia(Nodes, []). + +uninstall_fallback(suite) -> []; +uninstall_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = uinst_fallbacks_test, + File = "uinst_fallback.BUP", + File2 = "uinst_fallback2.BUP", + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:backup(File)), + Fun = fun({T, N, _}, Acc) when T == Tab -> + {[{T, N, test_nok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({error, enoent}, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File2)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, mnesia:uninstall_fallback()), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab])), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +local_fallback(suite) -> []; +local_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = local_fallback, + File = "local_fallback.BUP", + Def = [{disc_copies, Nodes}], + Key = foo, + Pre = {Tab, Key, pre}, + Post = {Tab, Key, post}, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write(Pre)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:dirty_write(Post)), + Local = [{scope, local}], + ?match({error, enoent}, mnesia:uninstall_fallback(Local)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:uninstall_fallback(Local)), + ?match(false, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + + ?match(false, rpc:call(Node2, mnesia, system_info , [fallback_activated])), + ?match(ok, rpc:call(Node2, mnesia, install_fallback , [File, Local])), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([Post], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + ?match([Pre], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + Dir = rpc:call(Node2, mnesia, system_info , [directory]), + + ?match(ok, mnesia:dirty_write(Post)), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([], mnesia_test_lib:kill_mnesia([Node2])), + ?match(ok, mnesia:install_fallback(File, Local ++ [{mnesia_dir, Dir}])), + ?match([], mnesia_test_lib:kill_mnesia([Node1])), + + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + ?match(yes, rpc:call(Node2, mnesia, force_load_table, [Tab])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +selective_backup_checkpoint(doc) -> + ["Perform a selective backup of a checkpoint"]; +selective_backup_checkpoint(suite) -> []; +selective_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = sel_backup, + OmitTab = sel_backup_omit, + CpName = sel_cp, + Def = [{disc_copies, [Node1, Node2]}], + File = "selective_backup_checkpoint.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:create_table(OmitTab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:dirty_write({OmitTab, 1, test_ok})), + CpSpec = [{name, CpName}, {max, mnesia:system_info(tables)}], + ?match({ok, CpName, _Ns}, mnesia:activate_checkpoint(CpSpec)), + + BupSpec = [{tables, [Tab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec)), + + ?match([schema, sel_backup], bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + + BupSpec2 = [{tables, [Tab, OmitTab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec2)), + + ?match([schema, sel_backup, sel_backup_omit], + bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +bup_tables(File, Mod) -> + Fun = fun(Rec, Tabs) -> + Tab = element(1, Rec), + Tabs2 = [Tab | lists:delete(Tab, Tabs)], + {[Rec], Tabs2} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Tabs} -> + lists:sort(Tabs); + {error, Reason} -> + exit(Reason) + end. + +incremental_backup_checkpoint(doc) -> + ["Perform a incremental backup of a checkpoint"]; +incremental_backup_checkpoint(suite) -> []; +incremental_backup_checkpoint(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = incr_backup, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + ?match([ok|_], [mnesia:dirty_write(R) || R <- OldRecs]), + OldCpName = old_cp, + OldCpSpec = [{name, OldCpName}, {min, [Tab]}], + ?match({ok, OldCpName, _Ns}, mnesia:activate_checkpoint(OldCpSpec)), + + BupSpec = [{tables, [Tab]}], + OldFile = "old_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(OldCpName, OldFile, BupSpec)), + ?match(OldRecs, bup_records(OldFile, mnesia_backup)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 2, 2})), + ?match(ok, mnesia:dirty_write({Tab, 3, -3})), + + NewCpName = new_cp, + NewCpSpec = [{name, NewCpName}, {min, [Tab]}], + ?match({ok, NewCpName, _Ns}, mnesia:activate_checkpoint(NewCpSpec)), + ?match(ok, mnesia:dirty_write({Tab, 4, 4})), + + NewFile = "new_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(NewCpName, NewFile, BupSpec)), + NewRecs = [{Tab, 2, 2}, {Tab, 3, -3}, + {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}, {Tab, 5, -5}], + ?match(NewRecs, bup_records(NewFile, mnesia_backup)), + + DiffFile = "diff_backup.BUP", + DiffBupSpec = [{tables, [Tab]}, {incremental, OldCpName}], + ?match(ok, mnesia:backup_checkpoint(NewCpName, DiffFile, DiffBupSpec)), + DiffRecs = [{Tab, 1}, {Tab, 2}, {Tab, 2, 2}, {Tab, 3}, {Tab, 3, -3}, + {Tab, 4}, {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}], + ?match(DiffRecs, bup_records(DiffFile, mnesia_backup)), + + ?match(ok, mnesia:deactivate_checkpoint(OldCpName)), + ?match(ok, mnesia:deactivate_checkpoint(NewCpName)), + ?match(ok, file:delete(OldFile)), + ?match(ok, file:delete(NewFile)), + ?match(ok, file:delete(DiffFile)), + + ?verify_mnesia(Nodes, []). + +bup_records(File, Mod) -> + Fun = fun(Rec, Recs) when element(1, Rec) == schema -> + {[Rec], Recs}; + (Rec, Recs) -> + {[Rec], [Rec | Recs]} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Recs} -> + lists:keysort(1, lists:keysort(2, lists:reverse(Recs))); + {error, Reason} -> + exit(Reason) + end. + +sops_with_checkpoint(doc) -> + ["Test schema operations during a checkpoint"]; +sops_with_checkpoint(suite) -> []; +sops_with_checkpoint(Config) when is_list(Config) -> + Ns = ?acquire_nodes(2, Config), + + ?match({ok, cp1, Ns}, mnesia:activate_checkpoint([{name, cp1},{max,mnesia:system_info(tables)}])), + Tab = tab, + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies,Ns}])), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + [mnesia:dirty_write(R) || R <- OldRecs], + + ?match({ok, cp2, Ns}, mnesia:activate_checkpoint([{name, cp2},{max,mnesia:system_info(tables)}])), + File1 = "cp1_delete_me.BUP", + ?match(ok, mnesia:dirty_write({Tab,6,-6})), + ?match(ok, mnesia:backup_checkpoint(cp1, File1)), + ?match(ok, mnesia:dirty_write({Tab,7,-7})), + File2 = "cp2_delete_me.BUP", + ?match(ok, mnesia:backup_checkpoint(cp2, File2)), + + ?match(ok, mnesia:deactivate_checkpoint(cp1)), + ?match(ok, mnesia:backup_checkpoint(cp2, File1)), + ?match(ok, mnesia:dirty_write({Tab,8,-8})), + + ?match({atomic,ok}, mnesia:delete_table(Tab)), + ?match({error,_}, mnesia:backup_checkpoint(cp2, File2)), + ?match({'EXIT',_}, mnesia:dirty_write({Tab,9,-9})), + + ?match({atomic,_}, mnesia:restore(File1, [{default_op, recreate_tables}])), + Test = fun(N) when N > 5 -> ?error("To many records in backup ~p ~n", [N]); + (N) -> case mnesia:dirty_read(Tab,N) of + [{Tab,N,B}] when -B =:= N -> ok; + Other -> ?error("Not matching ~p ~p~n", [N,Other]) + end + end, + [Test(N) || N <- mnesia:dirty_all_keys(Tab)], + ?match({aborted,enoent}, mnesia:restore(File2, [{default_op, recreate_tables}])), + + file:delete(File1), file:delete(File2), + + ?verify_mnesia(Ns, []). diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl new file mode 100644 index 0000000000..4fbf1b4003 --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl @@ -0,0 +1,2401 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_evil_coverage_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil usage of the API.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + system_info, + table_info, + error_description, + db_node_lifecycle, + evil_delete_db_node, + start_and_stop, + checkpoint, + table_lifecycle, + add_copy_conflict, + add_copy_when_going_down, + replica_management, + schema_availability, + local_content, + table_access_modifications, + replica_location, + table_sync, + user_properties, + unsupp_user_props, + record_name, + snmp_access, + subscriptions, + iteration, + debug_support, + sorted_ets, + {mnesia_dirty_access_test, all}, + {mnesia_trans_access_test, all}, + {mnesia_evil_backup, all} + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about Mnesia + +system_info(suite) -> []; +system_info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(all, Config), + Ns = ?sort(Nodes), + ?match(yes, mnesia:system_info(is_running)), + ?match(Ns, ?sort(mnesia:system_info(db_nodes))), + ?match(Ns, ?sort(mnesia:system_info(running_db_nodes))), + ?match(A when is_atom(A), mnesia:system_info(debug)), + ?match(L when is_list(L), mnesia:system_info(directory)), + ?match(L when is_list(L), mnesia:system_info(log_version)), + ?match({_, _}, mnesia:system_info(schema_version)), + ?match(L when is_list(L), mnesia:system_info(tables)), + ?match(L when is_list(L), mnesia:system_info(local_tables)), + ?match(L when is_list(L), mnesia:system_info(held_locks)), + ?match(L when is_list(L), mnesia:system_info(lock_queue)), + ?match(L when is_list(L), mnesia:system_info(transactions)), + ?match(I when is_integer(I), mnesia:system_info(transaction_failures)), + ?match(I when is_integer(I), mnesia:system_info(transaction_commits)), + ?match(I when is_integer(I), mnesia:system_info(transaction_restarts)), + ?match(L when is_list(L), mnesia:system_info(checkpoints)), + ?match(A when is_atom(A), mnesia:system_info(backup_module)), + ?match(true, mnesia:system_info(auto_repair)), + ?match({_, _}, mnesia:system_info(dump_log_interval)), + ?match(A when is_atom(A), mnesia:system_info(dump_log_update_in_place)), + ?match(I when is_integer(I), mnesia:system_info(transaction_log_writes)), + ?match(I when is_integer(I), mnesia:system_info(send_compressed)), + ?match(L when is_list(L), mnesia:system_info(all)), + ?match({'EXIT', {aborted, Reason }} when element(1, Reason) == badarg + , mnesia:system_info(ali_baba)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about table + +table_info(suite) -> []; +table_info(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab = table_info, + Type = bag, + ValPos = 3, + Attrs = [k, v], + Arity = length(Attrs) +1, + + Schema = + case mnesia_test_lib:diskless(Config) of + true -> [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {ram_copies, Nodes}]; + false -> + [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {disc_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab, Schema)), + + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + ?match(Mem when is_integer(Mem), mnesia:table_info(Tab, memory)), + ?match(Size, mnesia:table_info(Tab, size)), + ?match(Type, mnesia:table_info(Tab, type)), + + case mnesia_test_lib:diskless(Config) of + true -> + ?match(Nodes, mnesia:table_info(Tab, ram_copies)); + false -> + ?match([Node3], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_copies, Config))), + ?match([Node2], mnesia:table_info(Tab, ram_copies)), + ?match([Node1], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_only_copies, Config))) + end, + Read = [Node1, Node2, Node3], + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)), + Write = ?sort([Node1, Node2, Node3]), + ?match(Write, ?sort(mnesia:table_info(Tab, where_to_write))), + ?match([ValPos], mnesia:table_info(Tab, index)), + ?match(Arity, mnesia:table_info(Tab, arity)), + ?match(Attrs, mnesia:table_info(Tab, attributes)), + ?match({Tab, '_', '_'}, mnesia:table_info(Tab, wild_pattern)), + ?match({atomic, Attrs}, mnesia:transaction(fun() -> + mnesia:table_info(Tab, attributes) end)), + + ?match(L when is_list(L), mnesia:table_info(Tab, all)), + + %% Table info when table not loaded + ?match({atomic, ok}, + mnesia:create_table(tab_info, Schema)), + ?match(stopped, mnesia:stop()), + ?match(stopped, rpc:call(Node2, mnesia, stop, [])), + ?match(ok, mnesia:start()), + ?match(ok, mnesia:wait_for_tables([tab_info], 5000)), + ?match(0, mnesia:table_info(tab_info, size)), + ?verify_mnesia([Node1, Node3], [Node2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Check the error descriptions + +error_description(suite) -> []; +error_description(Config) when is_list(Config) -> + ?acquire_nodes(1, Config), + Errors = [nested_transaction, badarg, no_transaction, combine_error, + bad_index, already_exists, index_exists, no_exists, system_limit, + mnesia_down, not_a_db_node, bad_type, node_not_running, + truncated_binary_file, active, illegal + ], + ?match(X when is_atom(X), mnesia:error_description({error, bad_error_msg})), + ?match(X when is_tuple(X), mnesia:error_description({'EXIT', pid, bad})), + %% This is real error msg + ?match(X when is_tuple(X), mnesia:error_description( + {error, + {"Cannot prepare checkpoint (bad reply)", + {{877,957351,758147},a@legolas}, + {error,{node_not_running,a1@legolas}}}})), + check_errors(error, Errors), + check_errors(aborted, Errors), + check_errors('EXIT', Errors). + +check_errors(_Err, []) -> ok; +check_errors(Err, [Desc|R]) -> + ?match(X when is_list(X), mnesia:error_description({Err, Desc})), + check_errors(Err, R). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop db nodes + +db_node_lifecycle(suite) -> []; +db_node_lifecycle(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = db_node_lifecycle, + + Who = fun(T) -> + L1 = mnesia:table_info(T, ram_copies), + L2 = mnesia:table_info(T, disc_copies), + L3 = mnesia:table_info(T, disc_only_copies), + L1 ++ L2 ++ L3 + end, + + SNs = ?sort(AllNodes), + + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match([], mnesia_test_lib:stop_mnesia(AllNodes)), + ?match(ok, mnesia:delete_schema(AllNodes)), + ?match({error, _}, mnesia:create_schema(foo)), + ?match({error, _}, mnesia:create_schema([foo])), + ?match({error, _}, mnesia:create_schema([foo@bar])), + ?match(ok, mnesia:start()), + ?match(false, mnesia:system_info(use_dir)), + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:dump_tables([Tab])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_copies)), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_only_copies)), + + ?match(stopped, mnesia:stop()), + + ?match(ok, mnesia:create_schema(AllNodes)), + ?match([], mnesia_test_lib:start_mnesia(AllNodes)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + %% Delete the DB + + Tab2 = disk_tab, + Tab3 = not_local, + Tab4 = local, + Tab5 = remote, + + Tabs = [Schema, + [{name, Tab2}, {disc_copies, AllNodes}], + [{name, Tab3}, {ram_copies, [Node2, Node3]}], + [{name, Tab4}, {disc_only_copies, [Node1]}], + [{name, Tab5}, {disc_only_copies, [Node2]}]], + + [?match({atomic, ok}, mnesia:create_table(T)) || T <- Tabs ], + + ?match({aborted, {active, _, Node2}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match({aborted, {node_not_running, Node1}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[Tab2,Tab4])), + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node2)), + + %% Check + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match(RemNodes, Who(Tab2)), + ?match([Node3], Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab2, Node3, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node3)), + ?match([Node1], mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match([Node1], Who(Tab2)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Drop a db node when several disk resident nodes are down + +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = evil_delete_db_node, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies, AllNodes}])), + + ?match([], mnesia_test_lib:stop_mnesia([Node2, Node3])), + + ?match({atomic, ok}, mnesia:del_table_copy(schema, Node2)), + + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match(RemNodes, mnesia:table_info(Tab, disc_copies)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Start and stop the system + +start_and_stop(suite) -> []; +start_and_stop(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(all, Config), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?verify_mnesia(Nodes -- [Node1], [Node1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Checkpoints and backup management + +checkpoint(suite) -> []; +checkpoint(Config) when is_list(Config) -> + checkpoint(2, Config), + checkpoint(3, Config). + +checkpoint(NodeConfig, Config) -> + [Node1 | _] = TabNodes = ?acquire_nodes(NodeConfig, Config), + CreateTab = fun(Type, N, Ns) -> + Tab0 = lists:concat(["local_checkpoint_", Type, N]), + Tab = list_to_atom(Tab0), + catch mnesia:delete_table(Tab), + ?match({atomic, ok}, + mnesia:create_table(Tab, [{Type, Ns}])), + Tab + end, + CreateTabs = fun(Type, Acc) -> + [CreateTab(Type, 1, [hd(TabNodes)]), + CreateTab(Type, 2, TabNodes), + CreateTab(Type, 3, [lists:last(TabNodes)])] ++ + Acc + end, + Types = [ram_copies, disc_copies, disc_only_copies], + Tabs = lists:foldl(CreateTabs, [], Types), + Recs = ?sort([{T, N, N} || T <- Tabs, N <- lists:seq(1, 10)]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs), + + CpName = a_checkpoint_name, + MinArgs = [{name, CpName}, {min, Tabs}, {allow_remote, false}], + ?match({error, _}, rpc:call(Node1, mnesia, activate_checkpoint, [MinArgs])), + + MaxArgs = [{name, CpName}, {max, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [MaxArgs])), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + Args = [{name, CpName}, {min, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [Args])), + Recs2 = ?sort([{T, K, 0} || {T, K, _} <- Recs]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs2), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + ?match({error, Reason1 } when element(1, Reason1) == no_exists, + mnesia:deactivate_checkpoint(CpName)), + ?match({error, Reason2 } when element(1, Reason2) == badarg, + mnesia:activate_checkpoint(foo)), + ?match({error, Reason3 } when element(1, Reason3) == badarg, + mnesia:activate_checkpoint([{foo, foo}])), + ?match({error, Reason4 } when element(1, Reason4) == badarg, + mnesia:activate_checkpoint([{max, foo}])), + ?match({error, Reason5 } when element(1, Reason5) == badarg, + mnesia:activate_checkpoint([{min, foo}])), + ?match({error, _}, mnesia:activate_checkpoint([{min, [foo@bar]}])), + ?match({error, Reason6 } when element(1, Reason6) == badarg, + mnesia:activate_checkpoint([{allow_remote, foo}])), + + Fun = fun(Tab) -> ?match({atomic, ok}, mnesia:delete_table(Tab)) end, + lists:foreach(Fun, Tabs), + ?verify_mnesia(TabNodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Create and delete tables + +%% Get meta info about table + +-define(vrl, mnesia_test_lib:verify_replica_location). + +replica_location(suite) -> []; +replica_location(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = replica_location, + + %% Create three replicas + Schema = [{name, Tab}, {disc_only_copies, [Node1]}, + {ram_copies, [Node2]}, {disc_copies, [Node3]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match([], ?vrl(Tab, [Node1], [Node2], [Node3], Nodes)), + + %% Delete one replica + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node1], [], [Node3], Nodes)), + + %% Move one replica + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node2], [], [Node3], Nodes)), + + %% Change replica type + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, ram_copies)), + ?match([], ?vrl(Tab, [], [Node2], [Node3], Nodes)), + + ?verify_mnesia(Nodes, []). + +table_lifecycle(suite) -> []; +table_lifecycle(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic, ok}, mnesia:create_table([{type, bag}, + {ram_copies, [Node1]}, + {attributes, [rajtan, tajtan]}, + {name, order_of_args}])), + ?match([], mnesia:dirty_read({order_of_args, 4711})), + ?match({atomic, ok}, mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason23 } when element(1, Reason23) ==already_exists, + mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason21 } when element(1, Reason21) == bad_type, + mnesia:create_table([{name, bad_node}, {ram_copies, ["foo"]}])), + ?match({aborted, Reason2} when element(1, Reason2) == bad_type, + mnesia:create_table([{name, zero_arity}, {attributes, []}])), + ?match({aborted, Reason3} when element(1, Reason3) == badarg, + mnesia:create_table([])), + ?match({aborted, Reason4} when element(1, Reason4) == badarg, + mnesia:create_table(atom)), + ?match({aborted, Reason5} when element(1, Reason5) == badarg, + mnesia:create_table({cstruct, table_name_as_atom})), + ?match({aborted, Reason6 } when element(1, Reason6) == bad_type, + mnesia:create_table([{name, no_host}, {ram_copies, foo}])), + ?match({aborted, Reason7 } when element(1, Reason7) == bad_type, + mnesia:create_table([{name, no_host}, {disc_only_copies, foo}])), + ?match({aborted, Reason8} when element(1, Reason8) == bad_type, + mnesia:create_table([{name, no_host}, {disc_copies, foo}])), + + CreateFun = + fun() -> ?match({aborted, nested_transaction}, + mnesia:create_table([{name, nested_trans}])), ok + end, + ?match({atomic, ok}, mnesia:transaction(CreateFun)), + ?match({atomic, ok}, mnesia:create_table([{name, remote_tab}, + {ram_copies, [Node2]}])), + + ?match({atomic, ok}, mnesia:create_table([{name, a_brand_new_tab}, + {ram_copies, [Node1]}])), + ?match([], mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({atomic, ok}, mnesia:delete_table(a_brand_new_tab)), + ?match({'EXIT', {aborted, Reason31}} when element(1, Reason31) == no_exists, + mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({aborted, Reason41} when element(1, Reason41) == no_exists, + mnesia:delete_table(a_brand_new_tab)), + ?match({aborted, Reason9} when element(1, Reason9) == badarg, + mnesia:create_table([])), + + ?match({atomic, ok}, mnesia:create_table([{name, nested_del_trans}, + {ram_copies, [Node1]}])), + + DeleteFun = fun() -> ?match({aborted, nested_transaction}, + mnesia:delete_table(nested_del_trans)), ok end, + ?match({atomic, ok}, mnesia:transaction(DeleteFun)), + + ?match({aborted, Reason10} when element(1, Reason10) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, 2}])), + ?match({aborted, Reason32} when element(1, Reason32) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [-1]}])), + ?match({aborted, Reason33} when element(1, Reason33) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [0]}])), + ?match({aborted, Reason34} when element(1, Reason34) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [1]}])), + ?match({aborted, Reason35} when element(1, Reason35) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [2]}])), + ?match({atomic, ok}, + mnesia:create_table([{name, create_with_index}, {index, [3]}, + {ram_copies, [Node1]}])), + ets:new(ets_table, [named_table]), + + ?match({aborted, _}, mnesia:create_table(ets_table, [{ram_copies, Nodes}])), + + ?verify_mnesia(Nodes, []). + +add_copy_conflict(suite) -> []; +add_copy_conflict(doc) -> + ["Verify that OTP-5065 doesn't happen again, whitebox testing"]; +add_copy_conflict(Config) when is_list(Config) -> + Nodes = [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(b, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(test, [{ram_copies, [Node2]}])), + mnesia:stop(), + ?match(ok,mnesia:start([{no_table_loaders, 1}])), + + verify_ll_queue(10), + + Self = self(), + Test = fun() -> + Res = mnesia:add_table_copy(test, Node1, ram_copies), + Self ! {test, Res} + end, + %% Create conflict with loader queue. + spawn_link(Test), + ?match_receive(timeout), + %% Conflict ok + mnesia_controller:unblock_controller(), + + ?match_receive({test, {atomic,ok}}), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +verify_ll_queue(0) -> + ?error("Couldn't find anything in queue~n",[]); +verify_ll_queue(N) -> + ?match(granted,mnesia_controller:block_controller()), + case mnesia_controller:get_info(1000) of + {info,{state,_,true,[],_Loader,[],[],[],_,_,_,_,_,_}} -> + %% Very slow SMP machines havn't loaded it yet.. + mnesia_controller:unblock_controller(), + timer:sleep(10), + verify_ll_queue(N-1); + {info,{state,_,true,[],Loader,LL,[],[],_,_,_,_,_,_}} -> + io:format("~p~n", [{Loader,LL}]), + ?match([_], LL); %% Verify that something is in the loader queue + Else -> + ?error("No match ~p maybe the internal format has changed~n",[Else]) + end. + +add_copy_when_going_down(suite) -> []; +add_copy_when_going_down(doc) -> + ["Tests abort when node we load from goes down"]; +add_copy_when_going_down(Config) -> + [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, [Node1]}])), + %% Grab a write lock + WriteAndWait = fun() -> + mnesia:write({a,1,1}), + receive continue -> ok + end + end, + _Lock = spawn(fun() -> mnesia:transaction(WriteAndWait) end), + Tester = self(), + spawn_link(fun() -> Res = rpc:call(Node2,mnesia, add_table_copy, + [a, Node2, ram_copies]), + Tester ! {test, Res} + end), + %% We have a lock here we should get a timeout + ?match_receive(timeout), + mnesia_test_lib:kill_mnesia([Node1]), + ?match_receive({test,{aborted,_}}), + ?verify_mnesia([Node2], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add, drop and move replicas, change storage types +%% Change table layout (only arity change supported) + +-record(replica_management, {k, v}). +-record(new_replica_management, {k, v, extra}). + +-define(SS(R), lists:sort(element(1,R))). + +replica_management(doc) -> + "Add, drop and move replicas, change storage types."; +replica_management(suite) -> + []; +replica_management(Config) when is_list(Config) -> + %% add_table_copy/3, del_table_copy/2, move_table_copy/3, + %% change_table_copy_type/3, transform_table/3 + + Nodes = [Node1, Node2, Node3] = ?acquire_nodes(3, Config), + + Tab = replica_management, + Attrs = record_info(fields, replica_management), + + %% + %% Add, delete and change replicas + %% + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {attributes, Attrs}, + {ram_copies, [Node1, Node3]}])), + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + ?match([], ?vrl(Tab, [], [Node1, Node3], [], Nodes)), + %% R - - + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + ?match({aborted, Reason50 } when element(1, Reason50) == combine_error, + mnesia:add_table_copy(Tab, Node2, disc_copies)), + ?match({aborted, Reason51 } when element(1, Reason51) == combine_error, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + SyncedCheck = fun() -> + mnesia:lock({record,Tab,0}, write), + ?match([0,0,0], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))) + end, + mnesia:transaction(SyncedCheck), + + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node3)), + ?match([], ?vrl(Tab, [], [], [], Nodes)), + %% - - - + ?match({aborted,Reason52} when element(1, Reason52) == no_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {attributes, Attrs}, + {disc_copies, [Node1]}])), + ?match([], ?vrl(Tab, [], [], [Node1], Nodes)), + %% D - - + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + ?match({aborted, Reason53} when element(1, Reason53) == badarg, + mnesia:add_table_copy(Tab, Node2, bad_storage_type)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [], [Node1], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO - + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1, Node2], [Node3], [], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% DO DO R + ?match({aborted, Reason54} when element(1, Reason54) == already_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match([], ?vrl(Tab, [Node2], [Node3], [], Nodes)), + %% - DO R + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok},mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO D0 + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node2, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node1,Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1], [Node3], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% DO D R + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match([], ?vrl(Tab, [], [Node3,Node1], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% R D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node2,Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO DO + %% test clear + ?match({atomic, ok}, mnesia:clear_table(Tab)), + mnesia:transaction(SyncedCheck), + + %% rewrite for rest of testcase + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + %% D DO DO + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node1], Nodes)), + %% D - DO + ?match({aborted, Reason55} when element(1, Reason55) == already_exists, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + + %% + %% Move replica + %% + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% - D DO + ?match({aborted, _}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 43, sync_me}) end)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% - D DO + ?match({aborted,Reason56} when element(1, Reason56) == not_active, + mnesia:move_table_copy(Tab, Node3, Node1)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% DO D - + ?match([], mnesia_test_lib:start_mnesia([Node3])), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + %% DO D - + + %% + %% Transformer + %% + + NewAttrs = record_info(fields, new_replica_management), + Transformer = + fun(Rec) when is_record(Rec, replica_management) -> + #new_replica_management{k = Rec#replica_management.k, + v = Rec#replica_management.v, + extra = Rec#replica_management.k * 2} + end, + ?match({atomic, ok}, mnesia:transform_table(Tab, fun(R) -> R end, Attrs)), + ?match({atomic, ok}, mnesia:transform_table(Tab, Transformer, NewAttrs, new_replica_management)), + + ERlist = [#new_replica_management{k = K, v = K+2, extra = K*2} || K <- lists:seq(1, 10)], + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + + ?match(ERlist, ARlist), + + ?match({aborted, Reason56} when element(1, Reason56) == bad_type, + mnesia:transform_table(Tab, Transformer, 0)), + ?match({aborted, Reason57} when element(1, Reason57) == bad_type, + mnesia:transform_table(Tab, Transformer, -1)), + ?match({aborted, Reason58} when element(1, Reason58) == bad_type, + mnesia:transform_table(Tab, Transformer, [])), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, no_fun, NewAttrs)), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, fun(X) -> X end, NewAttrs, {tuple})), + + %% OTP-3878 + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy])), + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy], last_rec)), + + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + ?match({'EXIT',{aborted,{bad_type,_}}}, + mnesia:dirty_write(Tab, #new_replica_management{})), + ?match(ok, mnesia:dirty_write(Tab, {last_rec, k, v, e, dummy})), + + ?verify_mnesia(Nodes, []). + +schema_availability(doc) -> + ["Test that schema succeeds (or fails) as intended when some db nodes are down."]; +schema_availability(suite) -> + []; +schema_availability(Config) when is_list(Config) -> + [N1, _N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tab = schema_availability, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N3]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def1)), + + N = 10, + ?match(ok, mnesia:sync_dirty(fun() -> [mnesia:write({Tab, K, K + 2}) || K <- lists:seq(1, N)], ok end)), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match({[N,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match({atomic, ok}, mnesia:clear_table(Tab)), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?verify_mnesia(Nodes, []). + +-define(badrpc(Tab), {badrpc, {'EXIT', {aborted,{no_exists,Tab}}}}). + +local_content(doc) -> + ["Test local_content functionality, we want to see that correct" + " properties gets propageted correctly between nodes"]; +local_content(suite) -> []; +local_content(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = local1, + Def1 = [{local_content, true}, {ram_copies, Nodes}], + Tab2 = local2, + Def2 = [{local_content, true}, {disc_copies, [Node1]}], + Tab3 = local3, + Def3 = [{local_content, true}, {disc_only_copies, [Node1]}], + Tab4 = local4, + Def4 = [{local_content, true}, {ram_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:create_table(Tab4, Def4)), + + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab1, 1, Node1}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab1, 1, Node2}])), + ?match(ok, rpc:call(Node3, mnesia, dirty_write, [{Tab1, 1, Node3}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab2, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab3, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab4, 1, Node1}])), + + ?match(?badrpc(Tab2), rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(?badrpc(Tab3), rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(?badrpc(Tab4), rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, add_table_copy, [Tab2, Node2, ram_copies])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, add_table_copy, [Tab3, Node2, disc_copies])), + ?match({atomic, ok}, rpc:call(Node3, mnesia, add_table_copy, [Tab4, Node2, disc_only_copies])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match([{Tab1, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node3}], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + ?match(?badrpc([_Tab2, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab2, 1}])), + ?match(?badrpc([_Tab3, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab3, 1}])), + ?match(?badrpc([_Tab4, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab4, 1}])), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + + %% Added for OTP-44306 + ?match(ok, rpc:call(Node3, mnesia, start, [])), + ?match({ok, _}, mnesia:change_config(extra_db_nodes, [Node3])), + + mnesia_test_lib:sync_tables([Node3], [Tab1]), + + ?match([], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, clear_table, [Tab1])), + + SyncedCheck = fun(Tab) -> + mnesia:lock({record,Tab,0}, write), + {OK, []} = rpc:multicall(Nodes, mnesia, table_info, [Tab, size]), + OK + end, + ?match({atomic, [0,1,0]}, mnesia:transaction(SyncedCheck, [Tab1])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab2])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab2])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab3])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab3])), + + ?verify_mnesia(Nodes, []). + +table_access_modifications(suite) -> + [ + change_table_access_mode, + change_table_load_order, + set_master_nodes, + offline_set_master_nodes + ]. + +change_table_access_mode(suite) -> []; +change_table_access_mode(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = test_access_mode_tab, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {ram_copies, Nodes}]; + false -> [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Def)), + + Write = fun(What) -> mnesia:write({Tab, 1, What}) end, + Read = fun() -> mnesia:read({Tab, 1}) end, + + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok])), + %% test read_only + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, [nok]})), + ?match({aborted, _}, rpc:call(Node2, mnesia, transaction, [Write, [nok]])), + ?match({aborted, _}, rpc:call(Node3, mnesia, transaction, [Write, [nok]])), + ?match({atomic, [{Tab, 1, test_ok}]}, mnesia:transaction(Read)), + %% test read_write + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok1])), + ?match({atomic, [{Tab, 1, test_ok1}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node2, mnesia, transaction, [Write, [test_ok2]])), + ?match({atomic, [{Tab, 1, test_ok2}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node3, mnesia, transaction, [Write, [test_ok3]])), + ?match({atomic, [{Tab, 1, test_ok3}]}, mnesia:transaction(Read)), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def4 = [{name, Tab}, {access_mode, read_only_bad}], + ?match({aborted, {bad_type, _, _}}, mnesia:create_table(Def4)), + + Def2 = [{name, Tab}, {access_mode, read_only}], + ?match({atomic, ok}, mnesia:create_table(Def2)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def3 = [{name, Tab}, {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}, + {access_mode, read_write}], + ?match({atomic, ok}, mnesia:create_table(Def3)), + ?match({atomic, ok}, mnesia:transaction(Write, [ok])), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node2)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match({aborted, _}, mnesia:delete_table(Tab)), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + + ?match({aborted, {bad_type, _, _}}, + mnesia:change_table_access_mode(Tab, strange_atom)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode(err_tab, read_only)), + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode([Tab], read_only)), + ?verify_mnesia(Nodes, []). + +change_table_load_order(suite) -> []; +change_table_load_order(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = load_order_tab1, + Tab2 = load_order_tab2, + Tab3 = load_order_tab3, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def)), + + ?match({aborted, _}, mnesia:change_table_load_order(Tab1, should_be_integer)), + ?match({aborted, _}, mnesia:change_table_load_order(err_tab, 5)), + ?match({aborted, _}, mnesia:change_table_load_order([err_tab], 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab1, 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab2, 4)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab3, 73)), + + ?match({aborted, _}, mnesia:change_table_load_order(schema, -32)), + + ?verify_mnesia(Nodes, []). + +set_master_nodes(suite) -> []; +set_master_nodes(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = master_node_tab1, + Tab2 = master_node_tab2, + Tab3 = master_node_tab3, + Def1 = [{ram_copies, [Node1, Node2]}], + Def2 = [{disc_copies, [Node2, Node3]}], + Def3 = [{disc_only_copies, [Node3, Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match({error, _}, mnesia:set_master_nodes(schema, ['[email protected]'])), + ?match({error, _}, mnesia:set_master_nodes(badtab, [Node2, Node3])), + ?match({error, _}, mnesia:set_master_nodes(Tab1, [Node3])), + ?match([], mnesia:table_info(Tab1, master_nodes)), + + ?match(ok, mnesia:set_master_nodes(schema, [Node3, Node1])), + ?match([Node3, Node1], mnesia:table_info(schema, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2])), + ?match([Node2], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2, Node1])), + ?match([Node2, Node1], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node2])), % Should set where_to_read to Node2! + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node3])), + ?match([Node3], mnesia:table_info(Tab3, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1])), + ?match([Node1], mnesia:table_info(schema, master_nodes)), + ?match([Node1], mnesia:table_info(Tab1, master_nodes)), + ?match([], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1, Node2])), + ?match([Node1, Node2], mnesia:table_info(schema, master_nodes)), + ?match([Node1, Node2], mnesia:table_info(Tab1, master_nodes)), + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?verify_mnesia(Nodes, []). + +offline_set_master_nodes(suite) -> []; +offline_set_master_nodes(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = offline_master_node_tab1, + Tab2 = offline_master_node_tab2, + Tab3 = offline_master_node_tab3, + Tabs = ?sort([Tab1, Tab2, Tab3]), + Def1 = [{ram_copies, [Node]}], + Def2 = [{disc_copies, [Node]}], + Def3 = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match([], mnesia:system_info(master_node_tables)), + ?match([], mnesia_test_lib:stop_mnesia([Node])), + + ?match(ok, mnesia:set_master_nodes(Tab1, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match(Tabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes(Tab1, [])), + ?match(ok, mnesia:set_master_nodes(Tab2, [])), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + AllTabs = ?sort([schema | Tabs]), + ?match(AllTabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Syncronize table with log or disc +%% +table_sync(suite) -> + [ + dump_tables, + dump_log, + wait_for_tables, + force_load_table + ]. + +%% Dump ram tables on disc +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_tables, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + %% Dump 10 records + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + AllKeys = fun() -> ?sort(mnesia:all_keys(Tab)) end, + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + + %% Delete one record + ?match(ok, mnesia:dirty_delete({Tab, 5})), + Keys2 = lists:delete(5, Keys), + + ?match({atomic, Keys2}, mnesia:transaction(AllKeys)), + + %% Check that all 10 is restored after a stop + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2])), + ?match(ok, mnesia:wait_for_tables([Tab], infinity)), + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:dump_tables([foo])), + ?verify_mnesia(Nodes, []). + +dump_log(suite) -> []; +dump_log(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_log, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + Tab1 = dump_log1, + Schema1 = [{name, Tab1}, {attributes, [k, v]}, {disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema1)), + Tab2 = dump_log2, + Schema2 = [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema2)), + + ?match(ok, mnesia:dirty_write({Tab, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab1, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab2, 1, ok})), + + ?match(dumped, mnesia:dump_log()), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + Self = self(), + spawn(fun() -> dump_log(100, Self) end), + spawn(fun() -> dump_log(100, Self) end), + + ?match(ok, receive finished -> ok after 3000 -> timeout end), + ?match(ok, receive finished -> ok after 3000 -> timeout end), + + ?verify_mnesia(Nodes, []). + +dump_log(N, Tester) when N > 0 -> + mnesia:dump_log(), + dump_log(N-1, Tester); +dump_log(_, Tester) -> + Tester ! finished. + + +wait_for_tables(doc) -> + ["Intf. test of wait_for_tables, see also force_load_table"]; +wait_for_tables(suite) -> []; +wait_for_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = wf_tab, + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:wait_for_tables([wf_tab], infinity)), + ?match(ok, mnesia:wait_for_tables([], timer:seconds(5))), + ?match({timeout, [bad_tab]}, mnesia:wait_for_tables([bad_tab], timer:seconds(5))), + ?match(ok, mnesia:wait_for_tables([wf_tab], 0)), + ?match({error,_}, mnesia:wait_for_tables([wf_tab], -1)), + ?verify_mnesia(Nodes, []). + +force_load_table(suite) -> []; +force_load_table(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = wf_tab, + + Schema = [{name, Tab}, {disc_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + mnesia_test_lib:kill_mnesia([Node1]), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 1, test_nok}])), + mnesia_test_lib:kill_mnesia([Node2]), + %% timer:sleep(timer:seconds(5)), + ?match(ok, mnesia:start()), + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], 5)), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1})), + ?match(yes, mnesia:force_load_table(Tab)), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + + ?match({error, _}, mnesia:force_load_table(error_tab)), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +user_properties(doc) -> + ["Test of reading, writing and deletion of user properties", + "Plus initialization of user properties when a table is created", + "Do also test mnesia:table_info(Tab, user_properties)"]; +user_properties(suite) -> []; +user_properties(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = user_properties_1, + Tab2 = user_properties_2, + Tab3 = user_properties_3, + Def1 = [{ram_copies, [Node]}, {user_properties, []}], + Def2 = [{mnesia_test_lib:storage_type(disc_copies, Config), [Node]}], + Def3 = [{mnesia_test_lib:storage_type(disc_only_copies, Config), [Node]}, + {user_properties, []}], + + PropKey = my_prop, + Prop = {PropKey, some, elements}, + Prop2 = {PropKey, some, other, elements}, + YourProp= {your_prop}, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match([], mnesia:table_info(Tab1, user_properties)), + ?match([], mnesia:table_info(Tab2, user_properties)), + ?match([], mnesia:table_info(Tab3, user_properties)), + + ?match({'EXIT', {no_exists, {Tab1, user_property, PropKey}}}, + mnesia:read_table_property(Tab1, PropKey)), + ?match({'EXIT', {no_exists, {Tab2, user_property, PropKey}}}, + mnesia:read_table_property(Tab2, PropKey)), + ?match({'EXIT', {no_exists, {Tab3, user_property, PropKey}}}, + mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab1, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, YourProp)), + + ?match(Prop, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop2)), + ?match(Prop2, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:delete_table_property(Tab1, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab2, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab3, PropKey)), + + ?match([YourProp], mnesia:table_info(Tab1, user_properties)), + ?match([YourProp], mnesia:table_info(Tab2, user_properties)), + ?match([YourProp], mnesia:table_info(Tab3, user_properties)), + + Tab4 = user_properties_4, + ?match({atomic, ok}, + mnesia:create_table(Tab4, [{user_properties, [Prop]}])), + + ?match([Prop], mnesia:table_info(Tab4, user_properties)), + + %% Some error cases + + ?match({aborted, {bad_type, Tab1, {}}}, + mnesia:write_table_property(Tab1, {})), + ?match({aborted, {bad_type, Tab1, ali}}, + mnesia:write_table_property(Tab1, ali)), + + Tab5 = user_properties_5, + ?match({aborted, {bad_type, Tab5, {user_properties, {}}}}, + mnesia:create_table(Tab5, [{user_properties, {}}])), + ?match({aborted, {bad_type, Tab5, {user_properties, ali}}}, + mnesia:create_table(Tab5, [{user_properties, ali}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [{}]}}}, + mnesia:create_table(Tab5, [{user_properties, [{}]}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [ali]}}}, + mnesia:create_table(Tab5, [{user_properties, [ali]}])), + + ?verify_mnesia(Nodes, []). + + +unsupp_user_props(doc) -> + ["Simple test of adding user props in a schema_transaction"]; +unsupp_user_props(suite) -> []; +unsupp_user_props(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = silly1, + Tab2 = silly2, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab1, [{Storage, [Node1]}]])), + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab2, [{Storage, [Node1]}]])), + + F1 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1}), + mnesia_schema:do_write_table_property( + silly2, {prop, propval2}), % same key as above + mnesia_schema:do_write_table_property( + schema, {prop, propval3}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F1])), + + ?match([{prop,propval1}], rpc:call(Node1, mnesia, + table_info, [silly1, user_properties])), + ?match([{prop,propval2}], rpc:call(Node1, mnesia, + table_info, [silly2, user_properties])), + ?match([{prop,propval3}], rpc:call(Node1, mnesia, + table_info, [schema, user_properties])), + + F2 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1a}), + mnesia_schema:do_write_table_property( + silly1, {prop, propval1b}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F2])), + + ?match([{prop,propval1b}], rpc:call(Node1, mnesia, + table_info, + [silly1, user_properties])), + ?verify_mnesia([Node1], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +snmp_access(doc) -> + ["Make Mnesia table accessible via SNMP"]; + +snmp_access(suite) -> + [ + snmp_open_table, + snmp_close_table, + snmp_get_next_index, + snmp_get_row, + snmp_get_mnesia_key, + snmp_update_counter, + snmp_order + ]. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({aborted, _}, mnesia:snmp_open_table(ErrTab, [{key, integer}])), + ?verify_mnesia(Nodes, []). + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{snmp, [{key, integer}]}, {Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(no_snmp_tab, [])), + add_some_records(Tab1, Tab2, 3), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + add_some_records(Tab1, Tab2, 5), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab1)), + + Transform = fun(Tab, Key) -> + [{T,K,V}] = mnesia:read(Tab, Key, write), + mnesia:delete(T,K, write), + mnesia:write({T, {K,K}, V, 43+V}) + end, + + ?match({atomic, ok}, mnesia:transform_table(Tab1, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab1), + Keys = mnesia:select(Tab1, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab1, Key) || Key <- Keys], + ok + end)), + ?match([{Tab1, {1, 1}, 1, 44}], mnesia:dirty_read(Tab1, {1, 1})), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key,{integer,integer}}])), + + ?match({atomic, ok}, mnesia:snmp_close_table(Tab2)), + ?match({atomic, ok}, mnesia:transform_table(Tab2, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab2), + Keys = mnesia:select(Tab2, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab2, Key) || Key <- Keys], + ok + end)), + + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key,{integer,integer}}])), + + %% Should be aborted ???? + ?match({atomic, ok}, mnesia:snmp_close_table(no_snmp_tab)), + ?match({aborted, _}, mnesia:snmp_close_table(ErrTab)), + ?verify_mnesia(Nodes, []). + +snmp_get_next_index(suite) -> []; +snmp_get_next_index(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + add_some_records(Tab1, Tab2, 1), + Test = + fun() -> + %% Test local tables + {success, Res11} = ?match({ok, _}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + {success, _Res12} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_next_index(Tab1, endOfTable)), + + %% Test external table + {success, Res21} = + ?match({ok, _}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + {success, _Res22} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + + {ok, Row} = mnesia:snmp_get_row(Tab1, Index11), + ?match(ok, mnesia:dirty_delete(Tab1, hd(Index11))), + + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1,[])), + + ok = mnesia:dirty_write(Row), %% Reset to coming tests + + %% Test of non existing table + %%?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + %%io:format("**********Before ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + %%io:format("**********After ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + ?verify_mnesia(Nodes, []). + +add_some_records(Tab1, Tab2, N) -> + Recs1 = [{Tab1, I, I} || I <- lists:reverse(lists:seq(1, N))], + Recs2 = [{Tab2, I, I} || I <- lists:reverse(lists:seq(20, 20+N-1))], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + {atomic, ok} = mnesia:transaction(Trans), + %% Sync things, so everything gets everywhere! + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs1)) end), + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs2)) end), + ?sort(Recs1 ++ Recs2). + +snmp_get_row(suite) -> []; +snmp_get_row(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + Tab3 = snmp_table, + Def3 = [{Storage, [Node1]}, + {attributes, [key, data1, data2]}], + + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table( + Tab3, [{key, {fix_string,integer}}])), + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, {Tab1,1,1}}, mnesia:snmp_get_row(Tab1, Index11)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_row(Tab1, endOfTable)), + ?match(undefined, mnesia:snmp_get_row(Tab1, [73])), + + Add = fun() -> mnesia:write({Tab3, {"f_string", 3}, data1, data2}) end, + ?match({atomic, ok}, mnesia:transaction(Add)), + {success, {ok, Index31}} = ?match({ok, RowIndex31} when is_list(RowIndex31), + mnesia:snmp_get_next_index(Tab3,[])), + ?match({ok, Row31} when is_tuple(Row31), + mnesia:snmp_get_row(Tab3, Index31)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab3, Index31)), + Del = fun() -> mnesia:delete({Tab3,{"f_string",3}}) end, + ?match({atomic, ok}, mnesia:transaction(Del)), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [3])), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [73])), + + %% Test external table + {success, Res21} = ?match({ok,[20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, Row2} when is_tuple(Row2), mnesia:snmp_get_row(Tab2, Index21)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + %% Test of non existing table + %% ?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match(ok, Test()), + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match({atomic,ok}, mnesia:transaction(Test)), + + ?verify_mnesia(Nodes, []). + +snmp_get_mnesia_key(suite) -> []; +snmp_get_mnesia_key(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + + Tab3 = fix_string, + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def1)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab3, [{key, {fix_string,integer}}])), + + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, 1}, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + %% Test external tables + {success, Res21} = + ?match({ok, [20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, 20}, mnesia:snmp_get_mnesia_key(Tab2, Index21)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab2, [97])), + ?match({'EXIT', _}, mnesia:snmp_get_mnesia_key(Tab2, 97)), + + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,1}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab1,73,7}) end)), + ?match({ok, 73}, mnesia:snmp_get_mnesia_key(Tab1, [73])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,73}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, [73])), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab3,{"S",5},7}) end)), + ?match({ok,{"S",5}}, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:delete({Tab3,{"S",5}}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + ?verify_mnesia(Nodes, []). + +snmp_update_counter(doc) -> + ["Verify that counters may be updated for tables with SNMP property"]; +snmp_update_counter(suite) -> []; +snmp_update_counter(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_update_counter, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match(ok, mnesia:dirty_write({Tab, 1, 1})), + ?match([{Tab, _Key, 1}], mnesia:dirty_read(Oid)), + ?match(3, mnesia:dirty_update_counter(Oid, 2)), + ?match([{Tab, _Key, 3}], mnesia:dirty_read(Oid)), + ?verify_mnesia(Nodes, []). + +snmp_order(doc) -> + ["Verify that sort order is correct in transactions and dirty variants"]; +snmp_order(suite) -> []; +snmp_order(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_order, + Def = [{attributes, [key, value]}, + {snmp, [{key, {integer, integer, integer}}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, 1})), + [mnesia:dirty_write({Tab, {A,B,2}, default}) || + A <- lists:seq(1, 9, 2), + B <- lists:seq(2, 8, 2)], + + Test1 = fun() -> + Keys0 = get_keys(Tab, []), + ?match(Keys0, lists:sort(Keys0)), + ?match([[1,2,2]|_], Keys0), + Keys1 = get_keys(Tab, [5]), + ?match(Keys1, lists:sort(Keys1)), + ?match([[5,2,2]|_], Keys1), + Keys2 = get_keys(Tab, [7, 4]), + ?match(Keys2, lists:sort(Keys2)), + ?match([[7,4,2]|_], Keys2), + ok + end, + ?match(ok, Test1()), + ?match({atomic, ok},mnesia:transaction(Test1)), + ?match(ok,mnesia:sync_dirty(Test1)), + + Test2 = fun() -> + mnesia:write(Tab, {Tab,{0,0,2},updated}, write), + mnesia:write(Tab, {Tab,{5,3,2},updated}, write), + mnesia:write(Tab, {Tab,{10,10,2},updated}, write), + Keys0 = get_keys(Tab, []), + ?match([[0,0,2],[1,2,2]|_], Keys0), + ?match(Keys0, lists:sort(Keys0)), + + Keys1 = get_keys(Tab, [5]), + ?match([[5,2,2],[5,3,2]|_], Keys1), + ?match(Keys1, lists:sort(Keys1)), + + Keys2 = get_keys(Tab, [7,4]), + ?match([[7,4,2]|_], Keys2), + ?match(Keys2, lists:sort(Keys2)), + ?match([10,10,2], lists:last(Keys0)), + ?match([10,10,2], lists:last(Keys1)), + ?match([10,10,2], lists:last(Keys2)), + + ?match([[10,10,2]], get_keys(Tab, [10])), + ok + end, + + ?match({atomic, ok},mnesia:transaction(Test2)), + + ?verify_mnesia(Nodes, []). + +get_keys(Tab, Key) -> + case mnesia:snmp_get_next_index(Tab, Key) of + endOfTable -> []; + {ok, Next} -> + [Next|get_keys(Tab, Next)] + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(tab, {i, e1, e2}). % Simple test table + +subscriptions(doc) -> + ["Test the event subscription mechanism"]; +subscriptions(suite) -> + [subscribe_standard, + subscribe_extended]. + +subscribe_extended(doc) -> + ["Test the extended set of events, test with and without checkpoints. "]; +subscribe_extended(suite) -> + []; +subscribe_extended(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire_nodes(2, Config), + Tab1 = etab, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + + Tab2 = bag, + Def2 = [{Storage, [N1, N2]}, + {type, bag}, + {record_name, Tab1}, + {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + + ?match({error, {already_exists, _}}, mnesia:subscribe({table, Tab1, simple})), + ?match({error, {badarg, {table, Tab1, bad}}}, mnesia:subscribe({table, Tab1, bad})), + + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?match({ok, N1}, mnesia:unsubscribe(activity)), + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({atomic, ok}, mnesia:clear_table(Tab1)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab1}, [{schema, Tab1, _}],_}}, recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab1, _}, [], _}}, recv_event()), + + ?match({atomic, ok}, mnesia_schema:clear_table(Tab2)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab2}, [{schema, Tab2, _}],_}}, + recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab2, _}, [], _}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table, Tab2, detailed})), + {ok, _, _} = mnesia:activate_checkpoint([{name, testing}, + {ram_overrides_dump, true}, + {max, [Tab1, Tab2]}]), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?verify_mnesia(Nodes, []). + +test_ext_sub(Tab1, Tab2) -> + %% The basics + Rec1 = {Tab1, 1, 0, 0}, + Rec2 = {Tab1, 1, 1, 0}, + Rec3 = {Tab1, 2, 1, 0}, + Rec4 = {Tab1, 2, 2, 0}, + + Write = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:write(Tab, Rec, write) + end) + end, + Delete = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete(Tab, Rec, write) + end) + end, + DelObj = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete_object(Tab, Rec, write) + end) + end, + + S = self(), + D = {dirty, self()}, + %% SET + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec3)), + ?match({mnesia_table_event, {write, Tab1, Rec3, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [Rec1], D}}, recv_event()), + ?match({atomic, ok}, Write(Tab1, Rec2)), + ?match({mnesia_table_event, {write, Tab1, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match(ok, mnesia:dirty_delete({Tab1, 2})), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 2}, [Rec3], D}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab1, Rec2)), + ?match({mnesia_table_event, {delete, Tab1, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({atomic, ok}, Delete(Tab1, 1)), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 1}, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({ok, _N1}, mnesia:unsubscribe({table, Tab1, detailed})), + + %% BAG + + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec4)), + ?match({mnesia_table_event, {write, Tab2, Rec4, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec3)), + ?match({mnesia_table_event, {write, Tab2, Rec3, [Rec4], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec2)), + ?match({mnesia_table_event, {write, Tab2, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [Rec1, Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab2, Rec2)), + ?match({mnesia_table_event, {delete, Tab2, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 1)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 1}, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 2)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 2}, [Rec4, Rec3], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ok. + + +subscribe_standard(doc) -> + ["Tests system events and the orignal table events"]; +subscribe_standard(suite) -> []; +subscribe_standard(Config) when is_list(Config)-> + [N1, N2]=?acquire_nodes(2, Config), + Tab = tab, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + %% Check system events + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({ok, N1}, mnesia:subscribe(activity)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({mnesia_system_event, {mnesia_down, N2}}, recv_event()), + ?match(timeout, recv_event()), + + ?match([], mnesia_test_lib:start_mnesia([N2], [Tab])), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({mnesia_system_event,{mnesia_up, N2}}, recv_event()), + + ?match(true, lists:member(self(), mnesia:system_info(subscribers))), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + timer:sleep(500), + mnesia_test_lib:flush(), + ?match([], mnesia_test_lib:start_mnesia([N1], [Tab])), + ?match(timeout, recv_event()), + + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({error, {already_exists, system}}, mnesia:subscribe(system)), + ?match(stopped, mnesia:stop()), + ?match({mnesia_system_event, {mnesia_down, N1}}, recv_event()), + ?match({error, {node_not_running, N1}}, mnesia:subscribe(system)), + ?match([], mnesia_test_lib:start_mnesia([N1, N2], [Tab])), + + %% Check table events + ?match({ok, N1}, mnesia:subscribe(activity)), + Old_Level = mnesia:set_debug_level(trace), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=155}) end)), + Self = self(), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid, _, Self}}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table,Tab})), + ?match({ok, N1}, mnesia:unsubscribe(activity)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=255}) end)), + + ?match(timeout, recv_event()), + mnesia:set_debug_level(Old_Level), + + %% Check deletion of replica + + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({ok, N1}, mnesia:subscribe(activity)), + ?match(ok, mnesia:dirty_write(#tab{i=355})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=455})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=555})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N1, N2)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=655})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N1, ram_copies)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({error, {already_exists, {table,Tab, simple}}}, + mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=755})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(timeout, recv_event()), + + mnesia_test_lib:kill_mnesia([N1]), + + ?verify_mnesia([N2], [N1]). + +recv_event() -> + receive + Event -> Event + after 1000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +iteration(doc) -> + ["Verify that the iteration functions works as expected"]; +iteration(suite) -> + [foldl]. + + +foldl(suite) -> + []; +foldl(doc) -> + [""]; +foldl(Config) when is_list(Config) -> + Nodes = [_N1, N2] = ?acquire_nodes(2, Config), + Tab1 = fold_local, + Tab2 = fold_remote, + Tab3 = fold_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = ?sort([{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]]), + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab3Els], + + Fold = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab)) + end, + Fold2 = fun(Tab, Lock) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab, Lock)) + end, + Exit = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(_E, _A) -> exit(testing) end, [], Tab)) + end, + %% Errors + ?match({aborted, _}, mnesia:transaction(Fold, [error])), + ?match({aborted, _}, mnesia:transaction(fun(Tab) -> mnesia:foldl(badfun,[],Tab) end, + [Tab1])), + ?match({aborted, testing}, mnesia:transaction(Exit, [Tab1])), + ?match({aborted, _}, mnesia:transaction(Fold2, [Tab1, read_lock])), + + %% Success + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold, [Tab1]))), + ?match({atomic, Tab2Els}, sort_res(mnesia:transaction(Fold, [Tab2]))), + ?match({atomic, Tab3Els}, mnesia:transaction(Fold, [Tab3])), + + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, read]))), + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, write]))), + + ?match(Tab1Els, sort_res(mnesia:sync_dirty(Fold, [Tab1]))), + ?match(Tab2Els, sort_res(mnesia:async_dirty(Fold, [Tab2]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) -> + {atomic, ?sort(List)}; +sort_res(Else) when is_list(Else) -> + ?sort(Else); +sort_res(Else) -> + Else. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug_support(doc) -> + ["Check that the debug support has not decayed."]; +debug_support(suite) -> + [ + info, + schema_0, + schema_1, + view_0, + view_1, + view_2, + lkill, + kill + ]. + +info(suite) -> []; +info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:info()), + ?verify_mnesia(Nodes, []). + +schema_0(suite) -> []; +schema_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:schema()), + ?verify_mnesia(Nodes, []). + +schema_1(suite) -> []; +schema_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = schema_1, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match(ok, mnesia:schema(Tab)), + ?verify_mnesia(Nodes, []). + +view_0(suite) -> []; +view_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia_lib:view()), + ?verify_mnesia(Nodes, []). + +view_1(suite) -> []; +view_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "Just testing..."}), + CoreFile = lists:concat(["MnesiaCore.", node(), ".view_1.", ?MODULE]), + ?match(ok, file:write_file(CoreFile, BinCore)), + ?match(ok, mnesia_lib:view(CoreFile)), + ?match(ok, file:delete(CoreFile)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(eof, mnesia_lib:view(filename:join(Dir, "LATEST.LOG"))), + ?match(ok, mnesia_lib:view(filename:join(Dir, "schema.DAT"))), + ?verify_mnesia([], Nodes). + +view_2(suite) -> []; +view_2(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "More testing..."}), + File = lists:concat([?MODULE, "view_2.", node()]), + ?match(ok, file:write_file(File, BinCore)), + ?match(ok, mnesia_lib:view(File, core)), + ?match(ok, file:delete(File)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(ok, file:rename(filename:join(Dir, "LATEST.LOG"), File)), + ?match(eof, mnesia_lib:view(File, log)), + ?match(ok, file:delete(File)), + + ?match(ok, file:rename(filename:join(Dir, "schema.DAT"), File)), + ?match(ok, mnesia_lib:view(File, dat)), + ?match(ok, file:delete(File)), + ?verify_mnesia([], Nodes). + +lkill(suite) -> []; +lkill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match(ok, rpc:call(Node2, mnesia, lkill, [])), + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([Node1], [Node2]). + +kill(suite) -> []; +kill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match({_, []}, rpc:call(Node2, mnesia, kill, [])), + ?match(no, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([], [Node1, Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +record_name(doc) -> + ["Verify that record names may be differ from the name of ", + "the hosting table. Check at least access, restore, " + "registry, subscriptions and traveres_backup"]; +record_name(suite) -> + [ + record_name_dirty_access + ]. + +record_name_dirty_access(suite) -> + [ + record_name_dirty_access_ram, + record_name_dirty_access_disc, + record_name_dirty_access_disc_only + ]. + +record_name_dirty_access_ram(suite) -> + []; +record_name_dirty_access_ram(Config) when is_list(Config) -> + record_name_dirty_access(ram_copies, Config). + +record_name_dirty_access_disc(suite) -> + []; +record_name_dirty_access_disc(Config) when is_list(Config) -> + record_name_dirty_access(disc_copies, Config). + +record_name_dirty_access_disc_only(suite) -> + []; +record_name_dirty_access_disc_only(Config) when is_list(Config) -> + record_name_dirty_access(disc_only_copies, Config). + +record_name_dirty_access(Storage, Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + List = lists:concat([record_name_dirty_access_, Storage]), + Tab = list_to_atom(List), + RecName = some_record, + Attr = val, + TabDef = [{type, bag}, + {record_name, RecName}, + {index, [Attr]}, + {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, TabDef)), + + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 20})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 21})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 22})), + + %% Backup test + BupFile = List ++ ".BUP", + CpName = cpname, + CpArgs = [{name, CpName}, {min, [Tab]}, {ram_overrides_dump, true}], + ?match({ok, CpName, _}, mnesia:activate_checkpoint(CpArgs)), + ?match(ok, mnesia:backup_checkpoint(CpName, BupFile)), + ?match(ok, mnesia:deactivate_checkpoint(CpName)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 1, 10})), + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 3, 10})), + + Twos =?sort( [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}]), + ?match(Twos, ?sort(mnesia:dirty_read(Tab, 2))), + + ?match(ok, mnesia:dirty_delete_object(Tab, {RecName, 2, 21})), + + Tens = ?sort([{RecName, 1, 10}, {RecName, 3, 10}]), + TenPat = {RecName, '_', 10}, + ?match(Tens, ?sort(mnesia:dirty_match_object(Tab, TenPat))), + ?match(Tens, ?sort(mnesia:dirty_select(Tab, [{TenPat, [], ['$_']}]))), + + %% Subscription test + E = mnesia_table_event, + ?match_receive({E, {write, {Tab, 3, 10}, _}}), + ?match_receive({E, {delete_object, {Tab, 2, 21}, _}}), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + + ?match(Tens, ?sort(mnesia:dirty_index_match_object(Tab, TenPat, Attr) )), + ?match(Tens, ?sort(mnesia:dirty_index_read(Tab, 10, Attr))), + + ?match([1, 2, 3], ?sort(mnesia:dirty_all_keys(Tab))), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_delete(Tab, 2)), + ?match([], mnesia:dirty_read(Tab, 2)), + + ?match_receive({E, {delete, {Tab, 2}, _}}), + ?match([], mnesia_test_lib:flush()), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + %% Restore test + ?match({atomic, [Tab]}, mnesia:restore(BupFile, [{recreate_tables, [Tab]}])), + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(Twos, ?sort(mnesia:dirty_match_object(Tab, mnesia:table_info(Tab, wild_pattern)))), + ?match(Twos, ?sort(mnesia:dirty_select(Tab, + [{mnesia:table_info(Tab, wild_pattern), + [],['$_']}]))), + + %% Traverse backup test + + Fun = fun(Rec, {Good, Bad}) -> + ?verbose("BUP: ~p~n", [Rec]), + case Rec of + {T, K, V} when T == Tab -> + Good2 = Good ++ [{RecName, K, V}], + {[Rec], {?sort(Good2), Bad}}; + {T, K} when T == Tab -> + Good2 = [G || G <- Good, element(2, G) /= K], + {[Rec], {?sort(Good2), Bad}}; + _ when element(1, Rec) == schema -> + {[Rec], {Good, Bad}}; + _ -> + Bad2 = Bad ++ [Rec], + {[Rec], {Good, ?sort(Bad2)}} + end + end, + + ?match({ok, {Twos, []}}, mnesia:traverse_backup(BupFile, mnesia_backup, + dummy, read_only, + Fun, {[], []})), + ?match(ok, file:delete(BupFile)), + + %% Update counter test + + CounterTab = list_to_atom(lists:concat([Tab, "_counter"])), + CounterTabDef = [{record_name, some_counter}], + C = my_counter, + ?match({atomic, ok}, mnesia:create_table(CounterTab, CounterTabDef)), + ?match(some_counter, mnesia:table_info(CounterTab, record_name)), + ?match(0, mnesia:dirty_update_counter(CounterTab, gurka, -10)), + ?match(10, mnesia:dirty_update_counter(CounterTab, C, 10)), + ?match(11, mnesia:dirty_update_counter(CounterTab, C, 1)), + ?match(4711, mnesia:dirty_update_counter(CounterTab, C, 4700)), + ?match([{some_counter, C, 4711}], mnesia:dirty_read(CounterTab, C)), + ?match(0, mnesia:dirty_update_counter(CounterTab, C, -4747)), + + %% Registry tests + + RegTab = list_to_atom(lists:concat([Tab, "_registry"])), + RegTabDef = [{record_name, some_reg}], + ?match(ok, mnesia_registry:create_table(RegTab, RegTabDef)), + ?match(some_reg, mnesia:table_info(RegTab, record_name)), + {success, RegRecs} = + ?match([_ | _], mnesia_registry_test:dump_registry(node(), RegTab)), + + R = ?sort(RegRecs), + ?match(R, ?sort(mnesia_registry_test:restore_registry(node(), RegTab))), + + ?verify_mnesia(Nodes, []). + +sorted_ets(suite) -> + []; +sorted_ets(Config) when is_list(Config) -> + [N1, N2, N3] = All = ?acquire_nodes(3, Config), + + Tab = sorted_tab, + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {type, ordered_set}, {ram_copies, All}]; + false -> [{name, Tab}, {type, ordered_set}, + {ram_copies, [N1]}, + {disc_copies,[N2, N3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match({aborted, _}, mnesia:create_table(fel, [{disc_only_copies, N1}])), + + ?match([ok | _], + [mnesia:dirty_write({Tab, {dirty, N}, N}) || N <- lists:seq(1, 10)]), + ?match({atomic, _}, + mnesia:sync_transaction(fun() -> + [mnesia:write({Tab, {trans, N}, N}) || + N <- lists:seq(1, 10)] + end)), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + mnesia_test_lib:stop_mnesia(All), + mnesia_test_lib:start_mnesia(All, [sorted_tab]), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + ?match(List, rpc:call(N3, mnesia, dirty_select, [Tab, [{{Tab, '_', '_'},[],['$_']}]])), + + TransMatch = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:match_object({Tab, '_', '_'}) + end, + TransSelect = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:select(Tab, [{{Tab, '_', '_'},[],['$_']}]) + end, + + TList = mnesia:transaction(TransMatch), + STList = ?sort(TList), + ?match(STList, TList), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransMatch])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransMatch])), + + TSel = mnesia:transaction(TransSelect), + ?match(STList, TSel), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransSelect])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransSelect])), + + ?match({atomic, ok}, mnesia:create_table(rec, [{type, ordered_set}])), + [ok = mnesia:dirty_write(R) || R <- [{rec,1,1}, {rec,2,1}]], + ?match({atomic, ok}, mnesia:add_table_index(rec, 3)), + TestIt = fun() -> + ok = mnesia:write({rec,1,1}), + mnesia:index_read(rec, 1, 3) + end, + ?match({atomic, [{rec,1,1}, {rec,2,1}]}, mnesia:transaction(TestIt)). + + diff --git a/lib/mnesia/test/mnesia_examples_test.erl b/lib/mnesia/test/mnesia_examples_test.erl new file mode 100644 index 0000000000..d1b1409c9d --- /dev/null +++ b/lib/mnesia/test/mnesia_examples_test.erl @@ -0,0 +1,160 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_examples_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +opt_net_load(ExampleMod) -> + opt_net_load([node() | nodes()], ExampleMod, ok). + +opt_net_load([Node | Nodes], ExampleMod, Res) -> + case rpc:call(Node, ?MODULE, opt_load, [ExampleMod]) of + {module, ExampleMod} -> + opt_net_load(Nodes, ExampleMod, Res); + {error, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}); + {badrpc, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}) + end; +opt_net_load([], _ExampleMod, Res) -> + Res. + +opt_load(Mod) -> + case code:is_loaded(Mod) of + {file, _} -> + {module, Mod}; + false -> + Abs = filename:join([code:lib_dir(mnesia), examples, Mod]), + code:load_abs(Abs) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run all examples mentioned in the documentation", + "Are really all examples covered?"]; +all(suite) -> + [ + bup, + company, + meter, + tpcb + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bup(doc) -> ["Run the backup examples in bup.erl"]; +bup(suite) -> []; +bup(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + opt_net_load(bup), + ?match(ok, bup:test(Nodes)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +company(doc) -> + ["Run the company examples in company.erl and company_o.erl"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +tpcb(doc) -> + ["Run the sample configurations of the stress tests in mnesia_tpcb.erl"]; +tpcb(suite) -> + [ + replica_test, + sticky_replica_test, + dist_test, + conflict_test, + frag_test, + frag2_test, + remote_test, + remote_frag2_test + ]. + +replica_test(suite) -> []; +replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(replica_test, ram_copies))). + +sticky_replica_test(suite) -> []; +sticky_replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(sticky_replica_test, ram_copies))). + +dist_test(suite) -> []; +dist_test(Config) when is_list(Config) -> + ?init(3, [{tc_timeout, timer:minutes(10)} | Config]), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(dist_test, ram_copies))). + +conflict_test(suite) -> []; +conflict_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(conflict_test, ram_copies))). + +frag_test(suite) -> []; +frag_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag_test, ram_copies))). + +frag2_test(suite) -> []; +frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag2_test, ram_copies))). + +remote_test(suite) -> []; +remote_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_test, ram_copies))). + +remote_frag2_test(suite) -> []; +remote_frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_frag2_test, ram_copies))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +meter(doc) -> + ["Run the meter example in mnesia_meter.erl"]; +meter(suite) -> + []; +meter(Config) when is_list(Config) -> + [N | _] = ?init(3, Config), + opt_net_load(mnesia_meter), + ?match(ok, mnesia_meter:go(ram_copies, [N])). + + diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl new file mode 100644 index 0000000000..4add340254 --- /dev/null +++ b/lib/mnesia/test/mnesia_frag_test.erl @@ -0,0 +1,875 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_frag_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(match_dist(ExpectedRes, Expr), + case ?match(ExpectedRes, Expr) of + + mnesia_test_lib:error(Format, Args,?FILE,?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify the functionality of fragmented tables"]; +all(suite) -> + [ + light, + medium + ]. + +light(suite) -> + [ + nice, + evil + ]. + +medium(suite) -> + [ + consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice(suite) -> + [ + nice_single, + nice_multi, + nice_access, + iter_access + ]. + +nice_single(suite) -> []; +nice_single(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + %% Create a table with 2 fragments and 12 records + Tab = nice_frag, + Props = [{n_fragments, 2}, {node_pool, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, Props}])), + Records = [{Tab, N, -N} || N <- lists:seq(1, 12)], + [frag_write(Tab, R) || R <- Records], + ?match([{Node1, 2}], frag_dist(Tab)), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node2})), + Dist = frag_dist(Tab), + ?match([{Node2, 0}, {Node1, 2}], Dist), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node2, 1}, {Node1, 2}], Dist2), + ?match([3, 4, 5], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node1, 2}, {Node2, 2}], Dist3), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 3}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node2, 2}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 2}], Dist5), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node2, 3}, {Node1, 2}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 4, 5], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([8, 4], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node2, 0}, {Node1, 1}], frag_dist(Tab)), + ?match([12], frag_rec_dist(Tab)), + + %% Defragmenting the table clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the table fragmented again + Props2 = [{n_fragments, 1}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + Dist7 = frag_dist(Tab), + ?match([{Node1, 1}, {Node2, 1}], Dist7), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Deleting the fragmented table + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_multi(doc) -> + ["Extending the nice case with one more node, ", + "one more replica and a foreign key"]; +nice_multi(suite) -> []; +nice_multi(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + %% Create a table with 2 fragments and 8 records + Tab = frag_master, + Name = frag_rec, + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Props = [{n_fragments, 2}, + {Type, 2}, + {node_pool, [Node2, Node1]}], + Def = [{frag_properties, Props}, + {attributes, [id, data]}, + {record_name, Name}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Name, Id, -Id}) || Id <- lists:seq(1, 8)], + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(Tab)), + + %% And connect another table to it, via a foreign key + TabF = frag_slave, + PropsF = [{foreign_key, {Tab, foreign_id}}], + DefF = [{frag_properties, PropsF}, + {attributes, [id, foreign_id]}], + + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match([10, 6], frag_rec_dist(TabF)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(TabF)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node3})), + Dist = frag_dist(Tab), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], Dist), + ?match([6, 2], frag_rec_dist(Tab)), + DistF = frag_dist(TabF), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], DistF), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], Dist2), + ?match([_, _, _], frag_rec_dist(Tab)), + DistF2 = frag_dist(TabF), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], DistF2), + ?match([_, _, _], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist3), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF3 = frag_dist(TabF), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF3), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF4 = frag_dist(TabF), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], DistF4), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(TabF)), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist5), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF5), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF6 = frag_dist(TabF), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], DistF6), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([_, _, _], frag_rec_dist(Tab)), + ?match([_, _, _], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([10, 6], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(Tab)), + ?match([8], frag_rec_dist(Tab)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(TabF)), + ?match([16], frag_rec_dist(TabF)), + + %% Defragmenting the tables clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(TabF, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(TabF, deactivate)), + ?match(0, length(mnesia:table_info(TabF, frag_properties))), + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the tables fragmented again + Props2 = [{n_fragments, 1}, {node_pool, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(Tab)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(TabF)), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Deleting the fragmented tables + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match(false, lists:member(TabF, mnesia:system_info(tables))), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_access(doc) -> + ["Cover entire callback interface"]; +nice_access(suite) -> []; +nice_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + %% And connect another table to it, via a foreign key + TabF = frag_access_slave, + PropsF = [{foreign_key, {Tab, val}}], + DefF = [{frag_properties, PropsF}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, Id, Id}) || Id <- lists:seq(1, 400)], + + ?match(done, mnesia:activity(transaction, fun do_access/3, [Tab, Tab, Pool], mnesia_frag)), + ?match(done, mnesia:activity(transaction, fun do_access/3, [TabF, Tab, Pool], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +do_access(Tab, Master, Pool) -> + ?match(20, mnesia:table_info(Tab, n_fragments)), + ?match(Pool, mnesia:table_info(Tab, node_pool)), + ?match(2, mnesia:table_info(Tab, n_ram_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_only_copies)), + ?match(20, length(mnesia:table_info(Tab, frag_names))), + ?match(20, length(mnesia:table_info(Tab, frag_size))), + ?match(20, length(mnesia:table_info(Tab, frag_memory))), + PoolSize = length(Pool), + ?match(PoolSize, length(mnesia:table_info(Tab, frag_dist))), + ?match(400, mnesia:table_info(Tab, size)), + ?match(I when is_integer(I), mnesia:table_info(Tab, memory)), + ?match(Tab, mnesia:table_info(Tab, base_table)), + + Foreign = + if + Master == Tab -> + ?match(undefined, mnesia:table_info(Tab, foreign_key)), + ?match([_], mnesia:table_info(Tab, foreigners)), + ?match({'EXIT', {aborted, {combine_error, Tab, frag_properties, {foreign_key, undefined}}}}, + mnesia:read({Tab, 5}, 5, read)), + fun({T, _K}) -> T end; + true -> + ?match({Master, 3}, mnesia:table_info(Tab, foreign_key)), + ?match([], mnesia:table_info(Tab, foreigners)), + fun({T, K}) -> {T, K} end + end, + + Attr = val, + ?match(400, mnesia:table_info(Tab, size)), + Count = fun(_, N) -> N + 1 end, + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:write({Tab, [-1], 1})), + ?match(401, length(mnesia:match_object(Tab, {Tab, '_', '_'}, read))), + ?match(401, length(mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], read))), + + First = mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], 10, read), + TestCont = fun('$end_of_table', Total, _This) -> + Total; + ({Res,Cont1}, Total, This) -> + Cont = mnesia:select(Cont1), + This(Cont, length(Res) + Total, This) + end, + ?match(401, TestCont(First, 0, TestCont)), + + %% OTP + [_, Frag2|_] = frag_names(Tab), + Frag2key = mnesia:dirty_first(Frag2), + ?match({[Frag2key],_},mnesia:select(Tab,[{{Tab,Frag2key,'$1'},[],['$1']}],100,read)), + + ?match([{Tab, [-1], 1}], mnesia:read(Foreign({Tab, 1}), [-1], read)), + ?match(401, mnesia:foldl(Count, 0, Tab)), + ?match(401, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete(Foreign({Tab, 2}), 2, write)), + ?match([], mnesia:read(Foreign({Tab, 2}), 2, read)), + ?match([{Tab, 3, 3}], mnesia:read(Foreign({Tab, 3}), 3, read)), + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete_object({Tab, 3, 3})), + ?match([], mnesia:read(Foreign({Tab, 3}), 3, read)), + One = lists:sort([{Tab, 1, 1}, {Tab, [-1], 1}]), + Pat = {Tab, '$1', 1}, + ?match(One, lists:sort(mnesia:match_object(Tab, Pat, read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [], ['$1']}], read))), + ?match([[[-1]]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], [['$1']]}], read))), + ?match([[1, 100]], lists:sort(mnesia:select(Tab, [{Pat, [{is_integer, '$1'}], [['$1',100]]}], read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], ['$1']},{Pat, [{is_integer, '$1'}], ['$1']}], read))), + ?match(One, lists:sort(mnesia:index_match_object(Tab, Pat, Attr, read) )), + ?match(One, lists:sort(mnesia:index_read(Tab, 1, Attr))), + Keys = mnesia:all_keys(Tab), + ?match([-1], lists:max(Keys)), %% OTP-3779 + ?match(399, length(Keys)), + ?match(399, mnesia:foldl(Count, 0, Tab)), + ?match(399, mnesia:foldr(Count, 0, Tab)), + + ?match(Pool, lists:sort(mnesia:lock({table, Tab}, write))), + + done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +iter_access(doc) -> + ["Cover table iteration via callback interface"]; +iter_access(suite) -> []; +iter_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + FragNames = frag_names(Tab), + RawRead = + fun(Frag) -> + Node = mnesia:table_info(Frag, where_to_read), + {Frag, rpc:call(Node, ets, tab2list, [Frag])} + end, + + ?match(done, mnesia:activity(transaction, fun nice_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + + FragNames = frag_names(Tab), + [First, Second | _] = FragNames, + [Last, LastButOne | _] = lists:reverse(FragNames), + + ?match({atomic, ok}, mnesia:clear_table(First)), + ?match({atomic, ok}, mnesia:clear_table(Second)), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(8, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(9, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(10, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(11, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(LastButOne)), + ?match({atomic, ok}, mnesia:clear_table(Last)), + + ?match(done, mnesia:activity(transaction, fun evil_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + Size = fun(Table) -> mnesia:table_info(Table, size) end, + ?match(true, 0 < mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + ?match({atomic, ok}, mnesia:activity(ets, fun() -> mnesia:clear_table(Tab) end, mnesia_frag)), + ?match(0, mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +nice_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + +evil_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +consistency(doc) -> + ["Add and delete fragments during TPC-B"]; +consistency(suite) -> []; +consistency(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil(doc) -> + ["Evil coverage of fragmentation API."]; +evil(suite) -> + [ + evil_create, + evil_delete, + evil_change, + evil_combine, + evil_loop, + evil_delete_db_node + ]. + +evil_create(suite) -> []; +evil_create(Config) when is_list(Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + + Tab = evil_create, + %% Props in general + ?match({aborted, {badarg, Tab, {frag_properties, no_list}}}, + Create(Tab, [], no_list)), + ?match({aborted, {badarg,Tab , [no_tuple]}}, + Create(Tab, [], [no_tuple])), + ?match({aborted,{badarg, Tab, bad_key}}, + Create(Tab, [], [{bad_key, 7}])), + + %% n_fragments + ?match({aborted,{badarg, Tab, [{n_fragments}]}}, + Create(Tab, [], [{n_fragments}])), + ?match({aborted,{badarg, Tab, [{n_fragments, 1, 1}]}}, + Create(Tab, [], [{n_fragments, 1, 1}])), + ?match({aborted, {bad_type,Tab, {n_fragments, a}}}, + Create(Tab, [], [{n_fragments, a}])), + ?match({aborted, {bad_type, Tab, {n_fragments, 0}}}, + Create(Tab, [], [{n_fragments, 0}])), + + %% *_copies + ?match({aborted, {bad_type, Tab, {n_ram_copies, -1}}}, + Create(Tab, [], [{n_ram_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_copies, -1}}}, + Create(Tab, [], [{n_disc_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_only_copies, -1}}}, + Create(Tab, [], [{n_disc_only_copies, -1}, {n_fragments, 1}])), + + %% node_pool + ?match({aborted, {bad_type, Tab, {node_pool, 0}}}, + Create(Tab, [], [{node_pool, 0}])), + ?match({aborted, {combine_error, Tab, "Too few nodes in node_pool"}}, + Create(Tab, [], [{n_ram_copies, 2}, {node_pool, [Node1]}])), + + %% foreign_key + ?match({aborted, {bad_type, Tab, {foreign_key, bad_key}}}, + Create(Tab, [], [{foreign_key, bad_key}])), + ?match({aborted,{bad_type, Tab, {foreign_key, {bad_key}}}}, + Create(Tab, [], [{foreign_key, {bad_key}}])), + ?match({aborted, {no_exists, {bad_tab, frag_properties}}}, + Create(Tab, [], [{foreign_key, {bad_tab, val}}])), + ?match({aborted, {combine_error, Tab, {Tab, val}}}, + Create(Tab, [], [{foreign_key, {Tab, val}}])), + ?match({atomic, ok}, + Create(Tab, [], [{n_fragments, 1}])), + + ?match({aborted, {already_exists, Tab}}, + Create(Tab, [], [{n_fragments, 1}])), + + Tab2 = evil_create2, + ?match({aborted, {bad_type, no_attr}}, + Create(Tab2, [], [{foreign_key, {Tab, no_attr}}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {node_pool, [Node1]}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {n_fragments, 2}])), + ?match({atomic, ok}, + Create(Tab2, [{attributes, [a, b, c]}], [{foreign_key, {Tab, c}}])), + Tab3 = evil_create3, + ?match({aborted, {combine_error, Tab3, _, _, _}}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab2, b}}])), + ?match({atomic, ok}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab, b}}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete(suite) -> []; +evil_delete(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_change(suite) -> []; +evil_change(Config) when is_list(Config) -> + [N1,N2,_N3] = Nodes = ?acquire_nodes(3, Config), + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + Props1 = [{n_fragments, 2}, {node_pool, [N1]}], + Tab1 = evil_change_ram, + ?match({atomic, ok}, Create(Tab1, [], Props1)), + + ?match({atomic,ok}, mnesia:change_table_frag(Tab1, {add_frag, Nodes})), + Dist10 = frag_dist(Tab1), + ?match([{N1,3}], Dist10), + ?match({atomic, ok}, mnesia:change_table_frag(Tab1, {add_node, N2})), + Dist11 = frag_dist(Tab1), + ?match([{N2,0},{N1,3}], Dist11), + mnesia_test_lib:kill_mnesia([N2]), + ?match({aborted,_}, mnesia:change_table_frag(Tab1, {add_frag, [N2,N1]})), + ?verbose("~p~n",[frag_dist(Tab1)]), + mnesia_test_lib:start_mnesia([N2]), + + Tab2 = evil_change_disc, + ?match({atomic,ok}, Create(Tab2,[],[{n_disc_copies,1},{n_fragments,1},{node_pool,[N1,N2]}])), + ?verbose("~p~n", [frag_dist(Tab2)]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + _Dist20 = frag_dist(Tab2), + mnesia_test_lib:kill_mnesia([N2]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + ?match({aborted,_}, mnesia:change_table_frag(Tab2, {add_frag, [N2,N1]})), + + mnesia_test_lib:start_mnesia([N2]), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_combine(doc) -> ["Bug in mnesia_4.1.5. and earlier"]; +evil_combine(suite) -> []; +evil_combine(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ok},mnesia:create_table(tab1, [{disc_copies, [Node1]}, + {frag_properties, [{n_fragments, 2}, + {node_pool, [Node1]}, + {n_disc_copies, 1}]}])), + ?match({atomic, ok},mnesia:create_table(tab2, [{disc_copies, [Node1]}])), + mnesia:wait_for_tables([tab1, tab2], infinity), + + Add2 = fun() -> + mnesia:transaction(fun() -> + mnesia:write({tab2,1,1}) + end) + end, + Fun = fun() -> + Add2(), + mnesia:write({tab1,9,10}) + end, + ?match(ok, mnesia:activity({transaction, 1}, Fun, [], mnesia_frag)), + + Read = fun(T, K) -> + mnesia:read(T, K, read) + end, + + ?match([{tab1,9,10}],mnesia:activity(async_dirty, Read, [tab1, 9], mnesia_frag)), + ?match([{tab2,1,1}],mnesia:activity(async_dirty, Read, [tab2, 1], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_loop(doc) -> ["Test select/[14]"]; +evil_loop(suite) -> []; +evil_loop(Config) when is_list(Config) -> + [Node1,_Node2] = ?acquire_nodes(2, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1, Tab2, Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Keys = [-3, -2] ++ lists:seq(1, 5, 2) ++ lists:seq(6, 10), + Recs = [{RecName, K, K} || K <- Keys], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Activate = + fun(Tab) -> + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, []})), + Dist = frag_dist(Tab), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})) + end, + + Activate(Tab1), + Activate(Tab2), + Activate(Tab3), + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_', '_', '_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, + Trans = fun(Fun, Args) -> mnesia:activity(transaction, Fun, Args, mnesia_frag) end, + LoopHelp = fun('$end_of_table',_) -> + []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel, LoopHelp) + end, + + R1 = {RecName, 2, 2}, + R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, + R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> + mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1, R2| Recs]), + ?match(S1, sort_res(Trans(W1, [Tab1, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab1, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab1, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab2, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab3, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Match]))), + [mnesia:dirty_delete_object(Frag, R) || R <- [R1, R2], + Tab <- Tabs, + Frag <- frag_names(Tab)], + + W2 = fun(Tab, Search) -> + mnesia:write(Tab, R3, write), + mnesia:write(Tab, R1, write), + Search(Tab) + end, + S2 = lists:sort([R1 | Recs]), + S2Bag = lists:sort([R1, R3 | Recs]), + io:format("S2 = ~p\n", [S2]), + ?match(S2, sort_res(Trans(W2, [Tab1, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab1, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab1, Match]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab2, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Match]))), + io:format("S2Bag = ~p\n", [S2Bag]), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Select]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelLoop]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Match]))), + + W3 = fun(Tab,Search) -> + mnesia:write(Tab, R4, write), + mnesia:delete(Tab, element(2, R1), write), + Search(Tab) + end, + S3Bag = lists:sort([R4 | lists:delete(R1, Recs)]), + S3 = lists:delete({RecName, 3, 3}, S3Bag), + ?match(S3, sort_res(Trans(W3, [Tab1, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab1, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab1, Match]))), + ?match(S3, sort_res(Trans(W3, [Tab2, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Match]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Select]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelLoop]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab, -1, write), + mnesia:delete(Tab, 4 , write), + mnesia:delete(Tab, 17, write), + mnesia:delete_object(Tab, {RecName, -1, x}, write), + mnesia:delete_object(Tab, {RecName, 4, x}, write), + mnesia:delete_object(Tab, {RecName, 42, x}, write), + mnesia:delete_object(Tab, R2, write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5 | S3Bag]), + S4 = lists:sort([R5 | S3]), + ?match(S4, sort_res(Trans(W4, [Tab1, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab1, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab1, Match]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab2, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Match]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Select]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelLoop]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Match]))), + [mnesia:dirty_delete_object(Tab, R) || R <- [{RecName, 3, 3}, R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete_db_node(doc) -> + ["Delete db_node with a repicated table with foreign key"]; +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + Nodes = lists:sort(?acquire_nodes(2, Config)), + Local = node(), + Remote = hd(Nodes -- [Local]), + + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Tab = frag_master, + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, [{Type, 2}, {node_pool, Nodes}]}])), + ExtraTab = frag_foreigner, + ?match({atomic, ok}, mnesia:create_table(ExtraTab, [{frag_properties, [{foreign_key, {Tab, key}}, {node_pool, Nodes}]}])), + + GetPool = fun(T) -> + case lists:keysearch(node_pool, 1, mnesia:table_info (T, frag_properties)) of + {value, {node_pool, N}} -> lists:sort(N); + false -> [] + end + end, + ?match(Nodes, GetPool(Tab)), + ?match(Nodes, GetPool(ExtraTab)), + + + ?match(stopped, rpc:call(Remote, mnesia, stop, [])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, Remote)), + + ?match([Local], GetPool(Tab)), + ?match([Local], GetPool(ExtraTab)), + + ?verify_mnesia([Local], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Misc convenient helpers + +frag_write(Tab, Rec) -> + Fun = fun() -> mnesia:write(Tab, Rec, write) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_dist) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_names(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_names) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_rec_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_size) end, + [Size || {_, Size} <- mnesia:activity(sync_dirty, Fun, mnesia_frag)]. + +table_size(Tab) -> + Node = mnesia:table_info(Tab, where_to_read), + rpc:call(Node, mnesia, table_info, [Tab, size]). + +sort_res(List) when is_list(List) -> + lists:sort(List); +sort_res(Else) -> + Else. + +rev_res(List) when is_list(List) -> + lists:reverse(List); +rev_res(Else) -> + Else. diff --git a/lib/mnesia/test/mnesia_inconsistent_database_test.erl b/lib/mnesia/test/mnesia_inconsistent_database_test.erl new file mode 100644 index 0000000000..b19cd8e01b --- /dev/null +++ b/lib/mnesia/test/mnesia_inconsistent_database_test.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_inconsistent_database_test). +-author('[email protected]'). + +-behaviour(gen_event). + +%%-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + ?verbose("~p installed as event_module~n", [?MODULE]), + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({mnesia_system_event, Event}, State) + when element(1, Event) == inconsistent_database -> + ?error("Got event: ~p~n", [Event]), + {ok, State}; +handle_any_event(Msg, State) -> + ?verbose("Got event: ~p~n", [Msg]), + {ok, State}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_install_test.erl b/lib/mnesia/test/mnesia_install_test.erl new file mode 100644 index 0000000000..42a2a19f37 --- /dev/null +++ b/lib/mnesia/test/mnesia_install_test.erl @@ -0,0 +1,342 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_install_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run some small but demanding test cases in order to verify", + "that the basic functionality in Mnesia still works.", + "", + "Try some very simple things to begin with and increase the", + "difficulty stepwise. This test suite should be run before", + "all the others if you expect to find bugs.", + "", + "The function mnesia_install_test:silly() does not use the whole", + "infra structure of the test suite. Invoke it on a single node to", + "begin with. If that works, proceed with pong = net_adm:ping(SomeOtherNode)", + "and rerun silly() in order to perform some distributed tests."]; +all(suite) -> + [ + silly_durability, + silly_move, + silly_upgrade + %,stress + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Stepwise of more and more advanced features +silly() -> + Nodes = [node()] ++ nodes(), + mnesia_test_lib:kill_mnesia(Nodes), + Config = [{nodes, Nodes}], + mnesia_test_lib:eval_test_case(?MODULE, silly2, Config). + +silly2(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(3, Config), + mnesia_test_lib:kill_mnesia(Nodes), + ?ignore([mnesia:delete_schema([N]) || N <- Nodes]), + ?match(ok, mnesia:create_schema([Node1])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, + [[schema], infinity])), + Res = silly_durability(Config), + StressFun = fun(F) -> apply(?MODULE, F, [Config]) end, + R = + case length(Nodes) of + L when L > 1 -> + Node2 = lists:nth(2, Nodes), + AddDb = [schema, Node2, ram_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, add_table_copy, AddDb)), + Args = [[{extra_db_nodes, [Node1]}]], + ?match(ok, rpc:call(Node2, mnesia, start, Args)), + ChangeDb = [schema, Node2, disc_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, change_table_copy_type, + ChangeDb)), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], + [schema])), + MoveRes = silly_move(Config), + UpgradeRes = silly_upgrade(Config), + StressRes = [StressFun(F) || F <- stress(suite)], + ?verify_mnesia([Node2], []), + [Res, MoveRes, UpgradeRes] ++ StressRes; + _ -> + StressRes = [StressFun(F) || F <- stress(suite)], + ?warning("Too few nodes. Perform net_adm:ping(OtherNode) " + "and rerun!!!~n", []), + [Res | StressRes] + end, + ?verify_mnesia([Node1], []), + R. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_durability(doc) -> + ["Simple test of durability"]; +silly_durability(suite) -> []; +silly_durability(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = silly, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab, [{Storage, [Node1]}]])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + case mnesia_test_lib:diskless(Config) of + true -> + skip; + false -> + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, [[Tab], infinity])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])) + end, + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_move(doc) -> + ["Simple test of movement of a replica from one node to another"]; +silly_move(suite) -> []; +silly_move(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = silly_move, + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + create_table, [Tab, [{ram_copies, [Node2]}]])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + change_table_copy_type, [Tab, Node2, disc_only_copies])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])) + end, + ?match({atomic, [{Tab, a, b}]}, rpc:call(Node1, mnesia, transaction, [Read])), + + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + move_table_copy, [Tab, Node2, Node1])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_upgrade(doc) -> + ["Simple test of a schema upgrade and restore from backup"]; +silly_upgrade(suite) -> []; +silly_upgrade(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Name = silly_upgrade, + Tab1 = silly_upgrade1, + Tab2 = silly_upgrade2, + Bup = "silly_upgrade.BUP", + Bup2 = "silly_upgrade_part.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{disc_only_copies, Nodes}])), + + CpState = add_some_records(Tab1, Tab2, []), + ?match(match, verify_state(Tab1, Tab2, CpState)), + file:delete(Bup), + ?match(ok, mnesia:backup(Bup)), + Args = [{name, Name}, {ram_overrides_dump, true}, + {min, [Tab1, schema]}, {max, [Tab2]}], + ?match({ok, Name, _}, mnesia:activate_checkpoint(Args)), + + IgnoreState = add_more_records(Tab1, Tab2, CpState), + ?match(match, verify_state(Tab1, Tab2, IgnoreState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + file:delete(Bup2), + ?match(ok, mnesia:backup_checkpoint(Name, Bup2)), + + UpgradeState = transform_some_records(Tab1, Tab2, IgnoreState), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, IgnoreState)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:install_fallback(Bup2)), + file:delete(Bup2), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + ?match(match, verify_state(Tab1, Tab2, CpState)), + + ?match(ok, mnesia:install_fallback(Bup)), + file:delete(Bup), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + CpState2 = [X || X <- CpState, element(1, X) /= Tab1], + ?match(match, verify_state(Tab1, Tab2, CpState2)), + ?verify_mnesia(Nodes, []). + +wait_till_dead([]) -> ok; +wait_till_dead([N|Ns]) -> + Apps = rpc:call(N, application, which_applications, []), + case lists:keymember(mnesia, 1, Apps) of + true -> + timer:sleep(10), + wait_till_dead([N|Ns]); + false -> + wait_till_dead(Ns) + end. + +add_some_records(Tab1, Tab2, Old) -> + Recs1 = [{Tab1, I, I} || I <- lists:seq(1, 30)], + Recs2 = [{Tab2, I, I} || I <- lists:seq(20, 40)], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + ?match({atomic, _}, mnesia:transaction(Trans)), + lists:sort(Old ++ Recs1 ++ Recs2). + +add_more_records(Tab1, Tab2, Old) -> + Change1 = [{T, K, V+100} || {T, K, V} <- Old, K==23], + Change2 = [{T, K, V+100} || {T, K, V} <- Old, K==24], + Del = [{T, K} || {T, K, _V} <- Old, K>=25], + New = [{Tab1, 50, 50}, {Tab2, 50, 50}], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Change1), + lists:foreach(fun(R) -> mnesia:dirty_delete(R) end, Del), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Change2 ++ New) end, + ?match({atomic, ok}, mnesia:transaction(Trans)), + Recs = [{T, K, V} || {T, K, V} <- Old, K<23] ++ Change1 ++ Change2 ++ New, + lists:sort(Recs). + + +verify_state(Tab1, Tab2, Exp) -> + Fun = fun() -> + Act1 = [mnesia:read({Tab1, K}) || K <- mnesia:all_keys(Tab1)], + Act2 = [mnesia:read({Tab2, K}) || K <- mnesia:all_keys(Tab2)], + Act = lists:append(Act1) ++ lists:append(Act2), + {ok, Act -- Exp, Exp -- Act} + end, + case mnesia:transaction(Fun) of + {atomic, {ok, [], []}} -> match; + {atomic, {ok, More, Less}} -> {mismatch, More, Less}; + {aborted, Reason} -> {error, Reason} + end. + +transform_some_records(Tab1, _Tab2, Old) -> + Fun = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [4711]) + end, + ?match({atomic, ok}, + mnesia:transform_table(Tab1, Fun, [key, val, extra])), + Filter = fun(Rec) when element(1, Rec) == Tab1 -> {true, Fun(Rec)}; + (_) -> true + end, + lists:sort(lists:zf(Filter, Old)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +stress(doc) -> + ["Stress the system a little"]; +stress(suite) -> + [ + conflict, + dist + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dist(doc) -> + ["Avoid lock conflicts in order to maximize thruput", + "Ten drivers per node, tables replicated to all nodes, lots of branches"]; +dist(suite) -> []; +dist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(dist_args(Nodes, Storage))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +conflict(doc) -> + ["Provoke a lot of lock conflicts.", + "Ten drivers per node, tables replicated to all nodes, single branch"]; +conflict(suite) -> []; +conflict(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(conflict_args(Nodes, Storage))). + +conflict_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + +dist_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl new file mode 100644 index 0000000000..4fc6e8fe58 --- /dev/null +++ b/lib/mnesia/test/mnesia_isolation_test.erl @@ -0,0 +1,2419 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_isolation_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify the isolation property.", + "Operations of concurrent transactions must yield results which", + "are indistinguishable from the results which would be obtained by", + "forcing each transaction to be serially executed to completion in", + "some order. This means that repeated reads of the same records", + "within any committed transaction must have returned identical", + "data when run concurrently with any mix of arbitary transactions.", + "Updates in one transaction must not be visible in any other", + "transaction before the transaction has been committed."]; +all(suite) -> + [ + locking, + visibility + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +locking(doc) -> + ["Verify locking semantics for various configurations", + " NoLock = lock_funs(no_lock, any_granularity)", + " SharedLock = lock_funs(shared_lock, any_granularity)", + " ExclusiveLock = lock_funs(exclusive_lock, any_granularity)", + " AnyLock = lock_funs(any_lock, any_granularity)"]; +locking(suite) -> + [no_conflict, + simple_queue_conflict, + advanced_queue_conflict, + simple_deadlock_conflict, + advanced_deadlock_conflict, + lock_burst, + sticky_locks, + unbound_locking, + admin_conflict, +%% removed_resources, + nasty + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_conflict(suite) -> []; +no_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = no_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OtherOid, Lock1, Lock2) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> Lock1(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> Lock2(OtherOid), ok end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:abort(ok) end, + ?match_receive({A, {aborted, ok}}), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + NoLocks = lock_funs(no_lock, any_granularity), + SharedLocks = lock_funs(shared_lock, any_granularity), + AnyLocks = lock_funs(any_lock, any_granularity), + OneOneFun = fun(Lock1, Lock2) -> Fun(one_oid(Tab), Lock1, Lock2) end, + fun_loop(OneOneFun, NoLocks, AnyLocks), + fun_loop(OneOneFun, AnyLocks, NoLocks), + fun_loop(OneOneFun, SharedLocks, SharedLocks), + + %% Lock different objects + OneOtherFun = fun(Lock1, Lock2) -> Fun(other_oid(Tab), Lock1, Lock2) end, + OneSharedLocks = lock_funs(shared_lock, one), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + fun_loop(OneOtherFun, OneSharedLocks, OneExclusiveLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneSharedLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneExclusiveLocks), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_queue_conflict(suite) -> []; +simple_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_queue_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OneLock, OtherLock) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> OneLock(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> OtherLock(one_oid(Tab)), ok end, + wait_for_lock(B, [Node1], 20), % Max 10 sec + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, ok}]), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + OneSharedLocks = lock_funs(shared_lock, one), + AllSharedLocks = lock_funs(shared_lock, all), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + AllExclusiveLocks = lock_funs(exclusive_lock, all), + fun_loop(Fun, OneExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, OneExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, OneSharedLocks, AllExclusiveLocks), + fun_loop(Fun, AllSharedLocks, OneExclusiveLocks), + ok. + +wait_for_lock(Pid, _Nodes, 0) -> + Queue = mnesia:system_info(lock_queue), + ?error("Timeout while waiting for lock on Pid ~p in queue ~p~n", [Pid, Queue]); +wait_for_lock(Pid, Nodes, N) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_locker]), + List = [rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, N). + +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> ok; +check_q(Pid, [_ | Tail], N, Count) -> check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_lock(Pid, N, Count - 1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_queue_conflict(suite) -> []; +advanced_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = advanced_queue_conflict, + create_conflict_table(Tab, [Node1]), + OneRec = {Tab, 3, 3}, + OneOid = {Tab, 3}, + OtherRec = {Tab, 4, 4}, + OtherOid = {Tab, 4}, + + %% Start four transactions + {success, [D, C, B, A]} = ?start_activities(lists:duplicate(4, Node1)), + ?start_transactions([D, C, B, A]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + %% Acquire some locks + A ! fun() -> mnesia:write(OneRec) end, + ?match_receive({A, ok}), + A ! fun() -> mnesia:read(OneOid) end, + ?match_receive({A, [OneRec]}), + + B ! fun() -> mnesia:write(OtherRec) end, + ?match_receive({B, ok}), + B ! fun() -> mnesia:read(OneOid) end, + ?match_receive(timeout), + + C ! fun() -> mnesia:read(OtherOid) end, + ?match_receive(timeout), + D ! fun() -> mnesia:wread(OtherOid) end, + ?match_receive(timeout), + + %% and release them in a certain order + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, [OneRec]}]), + B ! end_trans, + ?match_multi_receive([{B, {atomic, end_trans}}, {C, [OtherRec]}]), + C ! end_trans, + ?match_multi_receive([{C, {atomic, end_trans}}, {D, [OtherRec]}]), + D ! end_trans, + ?match_receive({D, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_deadlock_conflict(suite) -> []; +simple_deadlock_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_deadlock_conflict, + create_conflict_table(Tab, [Node1]), + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities(lists:duplicate(2, Node1)), + mnesia_test_lib:start_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_deadlock_conflict(suite) -> []; +advanced_deadlock_conflict(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = advanced_deadlock_conflict, + create_conflict_table(Tab, [Node2]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node2]), + mnesia_test_lib:start_sync_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +one_oid(Tab) -> {Tab, 1}. +other_oid(Tab) -> {Tab, 2}. + +create_conflict_table(Tab, Nodes) -> + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, Nodes}, + {attributes, [key, val]}, + {index, [val]} + ])), + ?match([], mnesia_test_lib:sync_tables(Nodes, [Tab])), + init_conflict_table(Tab). + +init_conflict_table(Tab) -> + Recs = mnesia:dirty_match_object({Tab, '_', '_'}), + lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Recs), + Keys = [one_oid(Tab), other_oid(Tab)], + [mnesia:dirty_write({T, K, K}) || {T, K} <- Keys]. + +%% Apply Fun for each X and Y +fun_loop(Fun, Xs, Ys) -> + lists:foreach(fun(X) -> lists:foreach(fun(Y) -> do_fun(Fun, X, Y) end, Ys) end, Xs). + +do_fun(Fun, X, Y) -> + Pid = spawn_link(?MODULE, do_fun, [self(), Fun, X, Y]), + receive + {done_fun, Pid} -> done_fun + end. + +do_fun(Monitor, Fun, X, Y) -> + ?log("{do_fun ~p~n", [[Fun, X, Y]]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + Fun(X, Y), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + unlink(Monitor), + Monitor ! {done_fun, self()}, + exit(done_fun). + +%% Returns a list of fun's +lock_funs(no_lock, one) -> + [ + fun(Oid) -> mnesia:dirty_read(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}), + mnesia:dirty_update_counter({Tab, Key}, 0) end, + fun(Oid) -> mnesia:dirty_delete(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_match_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, Key, Key}, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_read(Tab, Key, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, '_', Key}, val) end + ]; +lock_funs(no_lock, all) -> + [ + fun({Tab, _}) -> mnesia:dirty_match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> slot_iter(Tab) end, + fun({Tab, _}) -> key_iter(Tab) end + ]; +lock_funs(shared_lock, one) -> + + [ + fun(Oid) -> mnesia:read(Oid) end, + fun({Tab, Key}) -> + init_conflict_table(Tab), + mnesia:dirty_delete(other_oid(Tab)), + mnesia:match_object({Tab, Key, Key}) end + ]; +lock_funs(shared_lock, all) -> + [ + fun({Tab, _}) -> mnesia:read_lock_table(Tab) end, + fun({Tab, Key}) -> mnesia:match_object({Tab, '_', Key}) end, + fun({Tab, _}) -> mnesia:match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> mnesia:all_keys(Tab) end, + fun({Tab, Key}) -> mnesia:index_match_object({Tab, '_', Key}, val) end, + fun({Tab, Key}) -> mnesia:index_read(Tab, Key, val) end + ]; +lock_funs(exclusive_lock, one) -> + [ + fun(Oid) -> mnesia:wread(Oid) end, + fun({Tab, Key}) -> mnesia:write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:delete(Oid) end, + fun({Tab, Key}) -> mnesia:delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:s_write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:s_delete(Oid) end, + fun({Tab, Key}) -> mnesia:s_delete_object({Tab, Key, Key}) end + ]; +lock_funs(exclusive_lock, all) -> + [ + fun({Tab, _}) -> mnesia:write_lock_table(Tab) end + ]; +lock_funs(Compatibility, any_granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Granularity <- [one, all]]); +lock_funs(any_lock, Granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Compatibility <- [no_lock, shared_lock, exclusive_lock]]). + +slot_iter(Tab) -> + slot_iter(Tab, mnesia:dirty_slot(Tab, 0), 1). +slot_iter(_Tab, '$end_of_table', _) -> + []; +slot_iter(Tab, Recs, Slot) -> + Recs ++ slot_iter(Tab, mnesia:dirty_slot(Tab, Slot), Slot+1). + +key_iter(Tab) -> + key_iter(Tab, mnesia:dirty_first(Tab)). +key_iter(_Tab, '$end_of_table') -> + []; +key_iter(Tab, Key) -> + [Key | key_iter(Tab, mnesia:dirty_next(Tab, Key))]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_burst(suite) -> []; +lock_burst(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = burst, + ?match({atomic, ok}, mnesia:create_table(Tab, + [{attributes, [a, b]}, + {ram_copies, [Node1]}])), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ?match(ok, burst_em(Tab, 1000)), + ?match([{burst,1,1000}], mnesia:dirty_read(Tab,1)), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +burst_em(Tab, N) -> + spawn_link(?MODULE, burst_counter, [self(), Tab, N]), + receive + burst_counter_done -> ok + end. + +burst_counter(Monitor, Tab, N) when N > 0 -> + ?match(ok, burst_gen(Tab, N, self())), + Monitor ! burst_receiver(N). + +burst_receiver(0) -> + burst_counter_done; +burst_receiver(N) -> + receive + burst_incr_done -> + burst_receiver(N-1) + end. + +burst_gen(_, 0, _) -> + ok; +burst_gen(Tab, N, Father) when is_integer(N), N > 0 -> + spawn_link(?MODULE, burst_incr, [Tab, Father]), + burst_gen(Tab, N-1, Father). + +burst_incr(Tab, Father) -> + Fun = fun() -> + Val = + case mnesia:read({Tab, 1}) of + [{Tab, 1, V}] -> V; + [] -> 0 + end, + mnesia:write({Tab, 1, Val+1}) + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Father ! burst_incr_done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +sticky_locks(doc) -> + ["Simple Tests of sticky locks"]; + +sticky_locks(suite) -> + [ + basic_sticky_functionality + %% Needs to be expandand a little bit further + ]. + +basic_sticky_functionality(suite) -> []; +basic_sticky_functionality(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = basic_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Storage, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(sync, [{ram_copies, Nodes}])), + Trans1 = fun() -> + ?match(ok, mnesia:s_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:s_write({Tab, 2, 2})), + ?match(ok, mnesia:write({Tab, 42, 4711})) + end, + Trans2 = fun() -> + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:write({Tab, 1, 4711})), + ?match(ok, mnesia:s_write({Tab, 2, 4})), + ?match(ok, mnesia:delete({Tab, 42})) + end, + rpc:call(N1, mnesia, transaction, [Trans1]), + ?match([{Tab,N1}], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + rpc:call(N2, mnesia, transaction, [Trans2]), + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + Slock = fun() -> mnesia:read({sync,sync}),get_sticky() end, + ?match({atomic, [{Tab,1, 4711}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match({atomic, [{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 3}),Slock() end])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match({atomic,[]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 7}),Slock() end])), + + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 7}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 6}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?verify_mnesia(Nodes, []). + +get_sticky() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + +get_held() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unbound_locking(suite) -> + [unbound1, unbound2]; + +unbound_locking(doc) -> + ["Check that mnesia handles unbound key variables, GPRS bug." + "Ticket id: OTP-3342"]. + +unbound1(suite) -> []; +unbound1(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + Tester = self(), + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting...~n", [self()]), + Tester ! continue, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + {success, [A]} = ?start_activities([Node1]), + ?start_transactions([A]), + A ! Write, + + receive continue -> ok end, + + Match = fun() -> + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting~n", []), + A ! continue, + A ! end_trans, + exit(What); + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + ?match({atomic, [{ul,{key,{17,42}},val}]}, mnesia:transaction(Match)), + + ?match_receive({A, ok}), + ?match_receive({A, {atomic, end_trans}}), + ok. + +unbound2(suite) -> []; +unbound2(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + {success, [B, A]} = ?start_activities([Node1, Node1]), + + Me = self(), + + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting... Tid ~p ~n", + [self(), get(mnesia_activity_state)]), + Me ! ok_lock, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + Match = fun() -> + receive + continueB -> + ?log("~p, moving on TID ~p~n", + [self(), get(mnesia_activity_state)]), + Me ! {self(), continuing} + end, + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, + '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting ~p ~n", [What]), + {should_not_happen,What}; + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + + B ! fun() -> mnesia:transaction(Match) end, + timer:sleep(100), %% Let B be started first.. + A ! fun() -> mnesia:transaction(Write) end, + + receive ok_lock -> ok end, + + B ! continueB, + ?match_receive({B, continuing}), + + %% B should now be in lock queue. + A ! continue, + ?match_receive({A, {atomic, ok}}), + ?match_receive({B, {atomic, [{ul,{key,{17,42}},val}]}}), + ok. + +receiver() -> + receive + {_Pid, begin_trans} -> + receiver(); + Else -> + Else + after + 10000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_conflict(doc) -> + ["Provoke lock conflicts with schema transactions and checkpoints."]; +admin_conflict(suite) -> + [ + create_table, + delete_table, + move_table_copy, + add_table_index, + del_table_index, + transform_table, + snmp_open_table, + snmp_close_table, + change_table_copy_type, + change_table_access, + add_table_copy, + del_table_copy, + dump_tables, + extra_admin_tests + ]. + +create_table(suite) -> []; +create_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + DiskMaybe = mnesia_test_lib:storage_type(disc_copies, Config), + + Pid = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab1, [{DiskMaybe, [ThisNode]}]]]), + ?match_multi_receive([{Pid, {atomic, ok}}, + {'EXIT', Pid, normal}]), %% No Locks! op should be exec. + + Pid2 = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab2, [{ram_copies, [Node2]}]]]), + + ?match_multi_receive([{Pid2, {atomic, ok}}, + {'EXIT', Pid2, normal}]), %% No Locks! op should be exec. + + A ! end_trans, + ?match_receive({A,{atomic,end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +delete_table(suite) -> []; +delete_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, delete_table, + [Tab]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +move_table_copy(suite) -> []; +move_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = m_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 2, 3}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, move_table_copy, + [Tab, ThisNode, Node2]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_index(suite) -> []; +add_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config ++ [{tc_timeout, 60000}]), + Tab = a_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, + add_table_index, [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_index(suite) -> []; +del_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = d_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + ?match({atomic, ok}, mnesia:add_table_index(Tab, attr1)), + + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 51, 51, attr2}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_index, + [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + %% Locks released! op should be exec. + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +transform_table(suite) -> []; +transform_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = t_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Transform = fun({Table, Key, Attr1, Attr2}) -> % Need todo a transform + {Table, Key, {Attr1, Attr2}} end, + Pid = spawn_link(?MODULE, op, [self(), mnesia, transform_table, + [Tab, Transform, [key, attr1]]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_o_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_open_table, + [Tab, [{key, integer}]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_close_table, [Tab]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_copy_type(suite) -> []; +change_table_copy_type(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_copy_type, + [Tab, ThisNode, disc_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_access(suite) -> []; +change_table_access(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_a_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_access_mode, + [Tab, read_only]]), + + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_copy(suite) -> []; +add_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = a_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, + [Tab, Node2, ram_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_copy(suite) -> []; +del_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_c_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 2, 5}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_copy, + [Tab, ThisNode]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A, {atomic,end_trans}}), + + ?match_receive({Pid, {atomic, ok}}), + ?match_receive({'EXIT', Pid, normal}), + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = dump_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, dump_tables, + [[Tab]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +op(Father, Mod, Fun, Args) -> + Res = apply(Mod, Fun, Args), + Father ! {self(), Res}. + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + +extra_admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_own(Tab, Key, Acc) -> + Update = + fun() -> + Res = mnesia:read({Tab, Key}), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, ok} -> + update_own(Tab, Key, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +update_shared(Tab, Me, Acc) -> + Update = + fun() -> + W2R = mnesia:table_info(Tab, where_to_read), + Res = mnesia:read({Tab, 0}), + case Res of + [{Tab, Key, Extra, Val}] when element(Me, Extra) == Acc -> + Extra1 = setelement(Me, Extra, Acc+1), + Term = {Tab, Key, Extra1, Val+1}, + ok = mnesia:write(Term), +% ?log("At ~p: ~p w2r ~p w2w ~p ~n", +% [node(), Term, +% mnesia:table_info(Tab, where_to_read), + W2W = mnesia:table_info(Tab, where_to_write), + W2C = mnesia:table_info(Tab, where_to_commit), +%% mnesia:table_info(Tab, storage_type) +% ]), + {_Mod, Tid, Ts} = get(mnesia_activity_state), + io:format("~p ~p~n", [Tid, ets:tab2list(element(2,Ts))]), + {ok,Term,{W2R,W2W,W2C}}; + Val -> + Info = [{acc, Acc}, {me, Me}, + {tid, element(2, mnesia:get_activity_id())}, + {locks, mnesia:system_info(held_locks)}], + {read, Val, Info} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, {ok,Term,W2}} -> + io:format("~p:~p:(~p,~p) ~w@~w~n", [erlang:now(),node(),Me,Acc,Term,W2]), + update_shared(Tab, Me, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type) + ]) + end + end. + +init_admin(Def, N1, N2, N3) -> + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(N1, ?MODULE, update_own, [Tab, 1, 0]), + Pid2 = spawn_link(N2, ?MODULE, update_own, [Tab, 2, 0]), + Pid3 = spawn_link(N3, ?MODULE, update_own, [Tab, 3, 0]), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 0, {0,0,0}, 0}) end)), + + Pid4 = spawn_link(N1, ?MODULE, update_shared, [Tab, 1, 0]), + Pid5 = spawn_link(N2, ?MODULE, update_shared, [Tab, 2, 0]), + Pid6 = spawn_link(N3, ?MODULE, update_shared, [Tab, 3, 0]), + + {Pid1, Pid2, Pid3, Pid4, Pid5, Pid6}. + +verify_results({P1, P2, P3, P4, P5, P6}) -> + Tab = schema_ops, N1 = node(P1), N2 = node(P2), N3 = node(P3), + + try + P1 ! {self(), quit}, + R1 = receive {P1, Res1} -> Res1 after 9000 -> throw({timeout,P1}) end, + P2 ! {self(), quit}, + R2 = receive {P2, Res2} -> Res2 after 9000 -> throw({timeout,P2}) end, + P3 ! {self(), quit}, + R3 = receive {P3, Res3} -> Res3 after 9000 -> throw({timeout,P3}) end, + + P4 ! {self(), quit}, + R4 = receive {P4, Res4} -> Res4 after 9000 -> throw({timeout,P4}) end, + P5 ! {self(), quit}, + R5 = receive {P5, Res5} -> Res5 after 9000 -> throw({timeout,P5}) end, + P6 ! {self(), quit}, + R6 = receive {P6, Res6} -> Res6 after 9000 -> throw({timeout,P6}) end, + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write_lock_table(Tab) end)), + ?log("Should be ~p~n", [R1]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?log("Should be ~p~n", [R2]), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?log("Should be ~p~n", [R3]), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])), + + Res = R4+R5+R6, + ?log("Should be {~p+~p+~p}= ~p~n", [R4, R5, R6, Res]), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N1, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N2, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N3, mnesia, dirty_read, [{Tab, 0}])) + catch throw:{timeout, Pid} -> + mnesia_lib:dist_coredump(), + ?error("Timeout ~p ~n", [Pid]) + end. + + +get_info(Tab) -> + Info = mnesia:table_info(Tab, all), + mnesia_lib:verbose("~p~n", [Info]). + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +%%% The actual test +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + Tab = schema_ops, + Pids = init_admin(Def, Node1, Node2, Node3), + + ?log("Call from ~p delete table from ~p ~n", [CallFrom, DelNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +%%% The actual test +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p add table to ~p ~n", [CallFrom, AddNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +%%% The actual test +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p move table from ~p to ~p ~n", [CallFrom, FromNode, ToNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +visibility(doc) -> + ["Verify the visibility semantics for various configurations"]; +visibility(suite) -> + [ + dirty_updates_visible_direct, + dirty_reads_regardless_of_trans, + trans_update_invisibible_outside_trans, + trans_update_visible_inside_trans, + write_shadows, + delete_shadows, +%% delete_shadows2, + write_delete_shadows_bag, + write_delete_shadows_bag2, + iteration, + shadow_search, + snmp_shadows + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_updates_visible_direct(doc) -> + ["One process can immediately see dirty updates of another"]; +dirty_updates_visible_direct(suite) -> []; +dirty_updates_visible_direct(Config) when is_list(Config) -> + dirty_visibility(outside_trans, Config). + +dirty_reads_regardless_of_trans(doc) -> + ["Dirty reads are not affected by transaction context"]; +dirty_reads_regardless_of_trans(suite) -> []; +dirty_reads_regardless_of_trans(Config) when is_list(Config) -> + dirty_visibility(inside_trans, Config). + +dirty_visibility(Mode, Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = list_to_atom(lists:concat([dirty_visibility, '_', Mode])), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, {ram_copies, [Node1]}])), + ValPos = 3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + %% Start two processes + {success, [A]} = ?start_activities([Node1]), + + case Mode of + inside_trans -> + ?start_transactions([A]), + A ! fun() -> + mnesia:write({Tab, a, 11}), + mnesia:write({Tab, b, 22}), + mnesia:write({Tab, c, 1}), + mnesia:write({Tab, d, 2}), + mnesia:write({Tab, e, 3}), + lists:sort(mnesia:all_keys(Tab)) + end, + ?match_receive({A, [a, b, c, d, e]}); + outside_trans -> + ignore + end, + + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + RecB2 = {Tab, b, 2}, + PatB2 = {Tab, '$1', 2}, + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + %% dirty_write + A ! fun() -> mnesia:dirty_write(RecA) end, + ?match_receive({A, ok}), + ?match([RecA], mnesia:dirty_read({Tab, a})), + ?match([RecA], mnesia:dirty_match_object(PatA)), + ?match(a, mnesia:dirty_first(Tab)), + ?match([RecA], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([RecA], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_create + A ! fun() -> mnesia:dirty_write(RecB) end, + ?match_receive({A, ok}), + ?match([RecB], mnesia:dirty_read({Tab, b})), + ?match([RecB], mnesia:dirty_match_object(PatB)), + ?match([RecB], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([RecB], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_update_counter + A ! fun() -> mnesia:dirty_update_counter({Tab, b}, -1) end, + ?match_receive({A, _}), + ?match([RecB2], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([RecB2], mnesia:dirty_match_object(PatB2)), + ?match([RecB2], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([RecB2], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_delete + A ! fun() -> mnesia:dirty_delete({Tab, b}) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match(a, mnesia:dirty_first(Tab)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_delete_object + ?match([RecA], mnesia:dirty_match_object(PatA)), + A ! fun() -> mnesia:dirty_delete_object(RecA) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + case Mode of + inside_trans -> + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}); + outside_trans -> + ignore + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_invisibible_outside_trans(doc) -> + ["Updates in a transaction are invisible outside the transaction"]; +trans_update_invisibible_outside_trans(suite) -> []; +trans_update_invisibible_outside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_invisibible_outside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Verify = + fun() -> + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)) + end, + + Fun = fun() -> + ?match(ok, mnesia:write(RecA)), + Verify(), + + ?match(ok, mnesia:write(RecB)), + Verify(), + + ?match(ok, mnesia:delete({Tab, b})), + Verify(), + + ?match([RecA], mnesia:match_object(PatA)), + Verify(), + + ?match(ok, mnesia:delete_object(RecA)), + Verify(), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Verify(), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_visible_inside_trans(doc) -> + ["Updates in a transaction are visible in the same transaction"]; +trans_update_visible_inside_trans(suite) -> []; +trans_update_visible_inside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_visible_inside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun = fun() -> + %% write + ?match(ok, mnesia:write(RecA)), + ?match([RecA], mnesia:read({Tab, a})), + ?match([RecA], mnesia:wread({Tab, a})), + ?match([RecA], mnesia:match_object(PatA)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA], mnesia:index_match_object(PatA, ValPos)), + ?match([RecA], mnesia:index_read(Tab, 1, ValPos)), + + %% create + ?match(ok, mnesia:write(RecB)), + ?match([RecB], mnesia:read({Tab, b})), + ?match([RecB], mnesia:wread({Tab, b})), + ?match([RecB], mnesia:match_object(PatB)), + ?match([RecB], mnesia:index_match_object(PatB, ValPos)), + ?match([RecB], mnesia:index_read(Tab, 3, ValPos)), + + %% delete + ?match(ok, mnesia:delete({Tab, b})), + ?match([], mnesia:read({Tab, b})), + ?match([], mnesia:wread({Tab, b})), + ?match([], mnesia:match_object(PatB)), + ?match([a], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatB, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + + %% delete_object + ?match(ok, mnesia:delete_object(RecA)), + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_shadows(doc) -> + ["Tests whether the shadow shows the correct object when", + "writing to the table"]; +write_shadows(suite) -> []; +write_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% write shadow old write - is the confirmed value visable + %% in the shadow ? + ?match([RecA1], mnesia:read({Tab, a})), + ?match([RecA1], mnesia:wread({Tab, a})), + ?match([RecA1], mnesia:match_object(PatA1)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA1], mnesia:index_match_object(PatA1, ValPos)), + ?match([RecA1], mnesia:index_read(Tab, 1, ValPos)), + + %% write shadow new write - is a new value visable instead + %% of the old value ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), %% delete shadow old but not new write - is the new value visable + + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +delete_shadows(doc) -> + ["Test whether the shadow shows the correct object when deleting objects"]; +delete_shadows(suite) -> []; +delete_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = delete_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + OidA = {Tab, a}, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + + + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - is the new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_delete_shadows_bag(doc) -> + ["Test the visibility of written and deleted objects in an bag type table"]; +write_delete_shadows_bag(suite) -> []; +write_delete_shadows_bag(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_delete_shadows_bag, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + ValPos = 3, + OidA = {Tab, a}, + + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + RecA3 = {Tab, a, 3}, + PatA3 = {Tab, '$1', 3}, + + PatA = {Tab, a, '_'}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ?match(ok, mnesia:write(RecA2)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete_object(RecA1)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - are both new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + ?match(ok, mnesia:write(RecA3)), + + + ?match([RecA2, RecA3], lists:sort(mnesia:read({Tab, a}))), + ?match([RecA2, RecA3], lists:sort(mnesia:wread({Tab, a}))), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2, RecA3], lists:sort(mnesia:match_object(PatA))), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA3], mnesia:index_match_object(PatA3, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +write_delete_shadows_bag2(doc) -> + ["Test the visibility of written and deleted objects in an bag type table " + "and verifies the results"]; +write_delete_shadows_bag2(suite) -> []; +write_delete_shadows_bag2(Config) when is_list(Config) -> + + [Node1] = ?acquire_nodes(1, Config), + Tab = w_d_s_b, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + Del = fun() -> + R1 = mnesia:read({Tab, 1}), + mnesia:delete({Tab, 1}), + R2 = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, 1}), + mnesia:write({Tab, 1, 2}), + R3 = mnesia:read({Tab, 1}), + {R1, R2, R3} + end, + DelObj = fun() -> + R1 = mnesia:read({Tab, 2}), + mnesia:delete_object({Tab, 2, 2}), + R2 = mnesia:read({Tab, 2}), + mnesia:write({Tab, 2, 1}), + mnesia:write({Tab, 2, 2}), + R3 = mnesia:read({Tab, 2}), + {R1, R2, R3} + end, + Both1 = [{Tab, 1, 1}, {Tab, 1, 2}], + Both2 = [{Tab, 2, 1}, {Tab, 2, 2}], + ?match({atomic, {[], [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, {Both1, [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, Both1}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, {[], [], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, {Both2, [{Tab, 2, 1}], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, Both2}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?verify_mnesia([Node1], []). + +shadow_search(doc) -> + ["Verifies that ordered_set tables are ordered, and the order is kept" + "even when table is shadowed by transaction updates"]; +shadow_search(suite) -> []; +shadow_search(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1,Tab2,Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Recs = [{RecName, K, K} || K <- [1,3,5]], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_','_','_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, +% Trans = fun(Fun,Args) -> mnesia:transaction(Fun,Args) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel,LoopHelp) + end, + + R1 = {RecName, 2, 2}, R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1,R2|Recs]), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Select])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Match])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,SelLoop])), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Match]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R2], Tab <- Tabs], + + W2 = fun(Tab,Search) -> + mnesia:write(Tab,R3,write), + mnesia:write(Tab,R1,write), + Search(Tab) + end, + S2 = lists:sort([R1|Recs]), + S2Bag = lists:sort([R1,R3|Recs]), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Select])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,SelLoop])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Match])), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Select]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,SelLoop]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Match]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Select]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,SelLoop]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Match]))), +%% [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R3], Tab <- Tabs], + + W3 = fun(Tab,Search) -> + mnesia:write(Tab,R4,write), + mnesia:delete(Tab,element(2,R1),write), + Search(Tab) + end, + S3Bag = lists:sort([R4|lists:delete(R1,Recs)]), + S3 = lists:delete({RecName,3,3},S3Bag), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Select])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,SelLoop])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Match])), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,SelLoop]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Select]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Match]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Select]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,SelLoop]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab,-1,write), + mnesia:delete(Tab,4 ,write), + mnesia:delete(Tab,17,write), + mnesia:delete_object(Tab,{RecName, -1, x},write), + mnesia:delete_object(Tab,{RecName, 4, x},write), + mnesia:delete_object(Tab,{RecName, 42, x},write), + mnesia:delete_object(Tab,R2,write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5|S3Bag]), + S4 = lists:sort([R5|S3]), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Select])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,SelLoop])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Match])), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Select]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,SelLoop]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Match]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Select]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,SelLoop]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [{RecName,3,3},R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +removed_resources(suite) -> + [rr_kill_copy]; +removed_resources(doc) -> + ["Verify that the locking behave when resources are removed"]. + +rr_kill_copy(suite) -> []; +rr_kill_copy(Config) when is_list(Config) -> + Ns = ?acquire_nodes(3,Config ++ [{tc_timeout, 60000}]), + DeleteMe = fun(_Tab,Where2read) -> + ?match([], mnesia_test_lib:kill_mnesia([Where2read])) + end, + Del = removed_resources(Ns, DeleteMe), + ?verify_mnesia(Ns -- [Del], []). + +removed_resources([_N1,N2,N3], DeleteRes) -> + Tab = del_res, + ?match({atomic, ok}, mnesia:create_table(Tab,[{ram_copies, [N2,N3]}])), + + Init = fun() -> [mnesia:write({Tab,Key,Key}) || Key <- lists:seq(0,99)] end, + ?match([], [Bad || Bad <- mnesia:sync_dirty(Init), Bad /= ok]), + + Where2Read = mnesia:table_info(Tab, where_to_read), + [Keep] = [N2,N3] -- [Where2Read], + Tester = self(), + + Conflict = fun() -> + %% Read a value.. + [{Tab,1,Val}] = mnesia:read({Tab,1}), + case get(restart) of + undefined -> + Tester ! {pid_1, self()}, + %% Wait for sync, the read value have been + %% updated and this function should be restarted. + receive {Tester,sync} -> ok end, + put(restart, restarted); + restarted -> + ok + end, + mnesia:write({Tab,1,Val+10}) + end, + Lucky = fun() -> + [{Tab,1,Val}] = mnesia:read({Tab,1}), + mnesia:write({Tab,1,Val+100}) + end, + + CPid = spawn_link(fun() -> Tester ! {self(), mnesia:transaction(Conflict)} end), + + %% sync first transaction + receive {pid_1, CPid} -> synced end, + + DeleteRes(Tab, Where2Read), + + ?match(Keep, mnesia:table_info(Tab, where_to_read)), + + %% Run the other/Lucky transaction, this should work since + %% it won't grab a lock on the conflicting transactions Where2Read node. + + LPid = spawn_link(Keep, fun() -> Tester ! {self(),mnesia:transaction(Lucky)} end), + ?match_receive({LPid,{atomic,ok}}), + + %% Continue Transaction no 1 + CPid ! {self(), sync}, + + ?match(ok, receive {CPid,{atomic,ok}} -> ok after 2000 -> process_info(self()) end), + + ?match({atomic,[{del_res,1,111}]}, mnesia:transaction(fun() -> mnesia:read({Tab,1}) end)), + Where2Read. + +nasty(suite) -> []; + +nasty(doc) -> + ["Tries to fullfill a rather nasty locking scenario, where we have had " + "bugs, the testcase tries a combination of locks in locker queue"]; + +%% This testcase no longer works as it was intended to show errors when +%% tablelocks was allowed to be placed in the queue though locks existed +%% in the queue with less Tid's. This is no longer allowed and the testcase +%% has been update. + +nasty(Config) -> + ?acquire_nodes(1, Config), + Tab = nasty, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + Coord = self(), + Write = fun(Key) -> + mnesia:write({Tab, Key, write}), + Coord ! {write, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + Coord ! {done, {write, Key}, self()} + end, + + Update = fun(Key) -> + Coord ! {update, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:read({Tab, Key}), + mnesia:write({Tab, Key, update}), + receive + continue -> + ok + end, + + Coord ! {done, {update, Key}, self()} + end, + + TabLock = fun() -> + Coord ! {tablock, Tab, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:lock({table, Tab}, write), + Coord ! {done, {tablock, Tab}, self()} + end, + + Up = spawn_link(mnesia, transaction, [Update, [0]]), + ?match_receive({update, 0, Up, _Tid}), + TL = spawn_link(mnesia, transaction, [TabLock]), + ?match_receive({tablock, Tab, _Tl, _Tid}), + W0 = spawn_link(mnesia, transaction, [Write, [0]]), + ?match_receive({write, 0, W0, _Tid}), + W1 = spawn_link(mnesia, transaction, [Write, [1]]), + ?match_receive({write, 1, W1, _Tid}), + + %% Nothing should be in msg queue! + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, %% Should be queued + ?match(timeout, receive A -> A after 1000 -> timeout end), + TL ! continue, %% Should be restarted +% ?match({tablock, _, _, _}, receive A -> A after 1000 -> timeout end), + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ1 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ1), LQ1}), + W0 ! continue, % Up should be in queue + ?match_receive({done, {write, 0}, W0}), + ?match_receive({'EXIT', W0, normal}), + + TL ! continue, % Should stay in queue W1 + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, % Should stay in queue (TL got higher tid) + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ2 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ2), LQ2}), + + W1 ! continue, + ?match_receive({done, {write, 1}, W1}), + get_exit(W1), + get_exit(TL), + ?match_receive({done, {tablock,Tab}, TL}), + get_exit(Up), + ?match_receive({done, {update, 0}, Up}), + + ok. + +get_exit(Pid) -> + receive + {'EXIT', Pid, normal} -> + ok + after 10000 -> + ?error("Timeout EXIT ~p~n", [Pid]) + end. + +iteration(doc) -> + ["Verify that the updates before/during iteration are visable " + "and that the order is preserved for ordered_set tables"]; +iteration(suite) -> + [foldl,first_next]. + +foldl(doc) -> + [""]; +foldl(suite) -> + []; +foldl(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = foldl_local, + Tab2 = foldl_remote, + Tab3 = foldl_ordered, + Tab11 = foldr_local, + Tab21 = foldr_remote, + Tab31 = foldr_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab11, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab21, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab31, [{ram_copies, Nodes}, + {type, ordered_set}])), + + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = [{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + Tab11Els = [{Tab11, N, N} || N <- lists:seq(1, 10)], + Tab21Els = [{Tab21, 1, 2} | [{Tab21, N, N} || N <- lists:seq(1, 10)]], + Tab31Els = [{Tab31, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab11Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab21Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab31Els], + + Get = fun(E, A) -> [E | A] end, + + %% Before + AddB = fun(Tab, Func) -> + mnesia:write({Tab, 0, 0}), + mnesia:write({Tab, 1, 0}), + mnesia:write({Tab, 11, 0}), + mnesia:Func(Get, [], Tab) + end, + AddT1 = [{Tab1, 0, 0}, {Tab1, 1, 0}] ++ tl(Tab1Els) ++ [{Tab1, 11, 0}], + AddT2 = lists:sort([{Tab2, 0, 0}, {Tab2, 1, 0}] ++ Tab2Els ++ [{Tab2, 11, 0}]), + AddT3 = [{Tab3, 0, 0}, {Tab3, 1, 0}] ++ tl(Tab3Els) ++ [{Tab3, 11, 0}], + AddT11 = [{Tab11, 0, 0}, {Tab11, 1, 0}] ++ tl(Tab11Els) ++ [{Tab11, 11, 0}], + AddT21 = lists:sort([{Tab21, 0, 0}, {Tab21, 1, 0}] ++ Tab21Els ++ [{Tab21, 11, 0}]), + AddT31 = [{Tab31, 0, 0}, {Tab31, 1, 0}] ++ tl(Tab31Els) ++ [{Tab31, 11, 0}], + + ?match({atomic, AddT1}, sort_res(mnesia:transaction(AddB, [Tab1, foldl]))), + ?match({atomic, AddT2}, sort_res(mnesia:transaction(AddB, [Tab2, foldl]))), + ?match({atomic, AddT3}, rev_res(mnesia:transaction(AddB, [Tab3, foldl]))), + ?match({atomic, AddT11}, sort_res(mnesia:transaction(AddB, [Tab11, foldr]))), + ?match({atomic, AddT21}, sort_res(mnesia:transaction(AddB, [Tab21, foldr]))), + ?match({atomic, AddT31}, mnesia:transaction(AddB, [Tab31, foldr])), + + ?match({atomic, ok}, mnesia:create_table(copy, [{ram_copies, [N2]}, + {record_name, Tab1}])), + CopyRec = fun(NewRec, Acc) -> + %% OTP-5495 + W = fun() -> mnesia:write(copy, NewRec, write), [NewRec| Acc] end, + {atomic,Res} = sort_res(mnesia:transaction(W)), + Res + end, + Copy = fun() -> + AddT1 = mnesia:foldl(CopyRec, [], Tab1), + AddT1 = sort_res(mnesia:foldl(Get, [], copy)) + end, + ?match({atomic, AddT1}, sort_res(mnesia:transaction(Copy))), + + Del = fun(E, A) -> mnesia:delete_object(E), [E|A] end, + DelD = fun(Tab) -> + mnesia:write({Tab, 12, 12}), + mnesia:delete({Tab, 0}), + mnesia:foldr(Del, [], Tab), + mnesia:foldl(Get, [], Tab) + end, + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab1]))), + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab2]))), + ?match({atomic, []}, rev_res(mnesia:transaction(DelD, [Tab3]))), + + ListWrite = fun(Tab) -> %% OTP-3893 + mnesia:write({Tab, [12], 12}), + mnesia:foldr(Get, [], Tab) + end, + ?match({atomic, [{Tab1, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab1]))), + ?match({atomic, [{Tab2, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab2]))), + ?match({atomic, [{Tab3, [12], 12}]}, rev_res(mnesia:transaction(ListWrite, [Tab3]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) when is_list(List) -> + {atomic, lists:sort(List)}; +sort_res(Else) when is_list(Else) -> + lists:sort(Else); +sort_res(Else) -> + Else. + +rev_res({atomic, List}) -> + {atomic, lists:reverse(List)}; +rev_res(Else) -> + Else. + + +first_next(doc) -> [""]; +first_next(suite) -> []; +first_next(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = local, + Tab2 = remote, + Tab3 = ordered, + Tab4 = bag, + Tabs = [Tab1,Tab2,Tab3,Tab4], + + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab4, [{ram_copies, Nodes}, + {type, bag}])), + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + Continue = fun(first) -> next; + (last) -> prev + end, + LoopHelp = fun('$end_of_table',_,_,_Fun) -> []; + (Key,Tab,Op,Fun) -> + Next = mnesia:Op(Tab,Key), + [Next |Fun(Next,Tab,Op,Fun)] + end, + Loop = fun(Tab,Start) -> + First = mnesia:Start(Tab), + Res = [First|LoopHelp(First,Tab,Continue(Start),LoopHelp)], + case mnesia:table_info(Tab, type) of + ordered_set when Start == first -> Res; + ordered_set -> + {L1,L2} = lists:split(length(Res)-1,Res), + lists:reverse(L1) ++ L2; + _ -> lists:sort(Res) + end + end, + + %% Verify empty tables + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + %% Verify that trans write is visible inside trans + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 5)], + Tab2Els = [{Tab2, N, N} || N <- lists:seq(1, 5)], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 5)], + Tab4Els = [{Tab4, 1, 2} | [{Tab4, N, N} || N <- lists:seq(1, 5)]], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab4Els], + Keys = lists:sort(mnesia:dirty_all_keys(Tab1)), + R1 = Keys++ ['$end_of_table'], + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + R2 = R1 -- [3], + + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,19}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,4}),Loop(Tab,last) end)) + || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +snmp_shadows(doc) -> [""]; +snmp_shadows(suite) -> []; +snmp_shadows(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = snmp_shadows, + io:format("With fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{fix_string,integer}}]}])), + snmp_shadows_test(Tab), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + io:format("Without fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{string,integer}}]}])), + snmp_shadows_test(Tab), + ?verify_mnesia(Nodes, []). + +snmp_shadows_test(Tab) -> + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + + CheckOrder = fun(A={_,_,{_,_,State}}, Prev) -> + ?match({true, A, Prev}, {Prev < A, A, Prev}), + {State,A} + end, + R1 = mnesia:sync_dirty(fun() -> loop_snmp(Tab, []) end), + lists:mapfoldl(CheckOrder, {[],foo,foo}, R1), + R2 = mnesia:transaction(fun() -> loop_snmp(Tab, []) end), + ?match({atomic, R1}, R2), + + Shadow = fun() -> + ok = mnesia:write({Tab, {"string",1}, {1,update}}), + ok = mnesia:write({Tab, {"string",4}, {4,update}}), + ok = mnesia:write({Tab, {"string",6}, {6,update}}), + ok = mnesia:delete({Tab, {"string",6}}), + ok = mnesia:write({Tab, {"string",9}, {9,update}}), + ok = mnesia:write({Tab, {"string",3}, {3,update}}), + ok = mnesia:write({Tab, {"string",5}, {5,update}}), + [Row5] = mnesia:read({Tab, {"string",5}}), + ok = mnesia:delete_object(Row5), + loop_snmp(Tab, []) + end, + R3 = mnesia:sync_dirty(Shadow), + {L3,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L3), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + {atomic, R3} = mnesia:transaction(Shadow), + {L4,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L4), + ok. + +loop_snmp(Tab,Prev) -> + case mnesia:snmp_get_next_index(Tab,Prev) of + {ok, SKey} -> + {{ok,Row},_} = {mnesia:snmp_get_row(Tab, SKey),{?LINE,Prev,SKey}}, + {{ok,MKey},_} = {mnesia:snmp_get_mnesia_key(Tab,SKey),{?LINE,Prev,SKey}}, + ?match({[Row],Row,SKey,MKey}, {mnesia:read({Tab,MKey}),Row,SKey,MKey}), + [{SKey, MKey, Row} | loop_snmp(Tab, SKey)]; + endOfTable -> + [] + end. diff --git a/lib/mnesia/test/mnesia_measure_test.erl b/lib/mnesia/test/mnesia_measure_test.erl new file mode 100644 index 0000000000..fbf804dbec --- /dev/null +++ b/lib/mnesia/test/mnesia_measure_test.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_measure_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Measure various aspects of Mnesia", + "Verify that Mnesia has predictable response times,", + "that the transaction system has fair algoritms,", + "resource consumption, scalabilitym system limits etc.", + "Perform some benchmarks."]; +all(suite) -> + [ + prediction, + consumption, + scalability, + benchmarks + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prediction(doc) -> + ["The system must have predictable response times.", + "The maintenance of the system should not impact on the", + "availability. Make sure that the response times does not vary too", + "much from the undisturbed normal usage.", + "Verify that deadlocks never occurs."]; +prediction(suite) -> + [ + reader_disturbed_by_node_down, + writer_disturbed_by_node_down, + reader_disturbed_by_node_up, + writer_disturbed_by_node_up, + reader_disturbed_by_schema_ops, + writer_disturbed_by_schema_ops, + reader_disturbed_by_checkpoint, + writer_disturbed_by_checkpoint, + reader_disturbed_by_dump_log, + writer_disturbed_by_dump_log, + reader_disturbed_by_backup, + writer_disturbed_by_backup, + reader_disturbed_by_restore, + writer_disturbed_by_restore, + fairness + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +fairness(doc) -> + ["Verify that the transaction system behaves fair, even under intense", + "stress. Combine different access patterns (transaction profiles)", + "in order to verify that concurrent applications gets a fair share", + "of the database resource. Verify that starvation never may occur."]; +fairness(suite) -> + [ + reader_competing_with_reader, + reader_competing_with_writer, + writer_competing_with_reader, + writer_competing_with_writer + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consumption(doc) -> + ["Measure the resource consumption and publish the outcome. Make", + "sure that resources are released after failures."]; +consumption(suite) -> + [ + measure_resource_consumption, + determine_resource_leakage + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +scalability(doc) -> + ["Try out where the system limits are. We must at least meet the", + "documented system limits.", + "Redo the performance meters for various configurations and load,", + "especially near system limits."]; +scalability(suite) -> + [ + determine_system_limits, + performance_at_min_config, + performance_at_max_config, + performance_at_full_load, + resource_consumption_at_min_config, + resource_consumption_at_max_config, + resource_consumption_at_full_load + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +benchmarks(doc) -> + ["Measure typical database operations and publish them. Try to", + "verify that new releases of Mnesia always outperforms old", + "releases, or at least that the meters does not get worse."]; +benchmarks(suite) -> + [ + meter, + cost, + dbn_meters, + measure_all_api_functions, + tpcb, + mnemosyne_vs_mnesia_kernel + ]. + +dbn_meters(suite) -> []; +dbn_meters(Config) when is_list(Config) -> + _Nodes = ?init(3, Config), + ?match(ok, mnesia_dbn_meters:start()), + ok. + +tpcb(suite) -> + [ + ram_tpcb, + disc_tpcb, + disc_only_tpcb + ]. + +tpcb(ReplicaType, Config) -> + HarakiriDelay = {tc_timeout, timer:minutes(20)}, + Nodes = ?acquire_nodes(2, Config ++ [HarakiriDelay]), + Args = [{n_branches, 2}, + {n_drivers_per_node, 1}, + {replica_nodes, Nodes}, + {driver_nodes, [hd(Nodes)]}, + {use_running_mnesia, true}, + {use_sticky_locks, true}, + {replica_type, ReplicaType}], + ?match({ok, _}, mnesia_tpcb:start(Args)), + ?verify_mnesia(Nodes, []). + +ram_tpcb(suite) -> []; +ram_tpcb(Config) when is_list(Config) -> + tpcb(ram_copies, Config). + +disc_tpcb(suite) -> []; +disc_tpcb(Config) when is_list(Config) -> + tpcb(disc_copies, Config). + +disc_only_tpcb(suite) -> []; +disc_only_tpcb(Config) when is_list(Config) -> + tpcb(disc_only_copies, Config). + +meter(suite) -> + [ + ram_meter, + disc_meter, + disc_only_meter + ]. + +ram_meter(suite) -> []; +ram_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(ram_copies, Nodes)). + +disc_meter(suite) -> []; +disc_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_copies, Nodes)). + +disc_only_meter(suite) -> []; +disc_only_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_only_copies, Nodes)). + +cost(suite) -> []; +cost(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + ?match(ok, mnesia_cost:go(Nodes)), + file:delete("MNESIA_COST"). diff --git a/lib/mnesia/test/mnesia_meter.erl b/lib/mnesia/test/mnesia_meter.erl new file mode 100644 index 0000000000..68094c4431 --- /dev/null +++ b/lib/mnesia/test/mnesia_meter.erl @@ -0,0 +1,465 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Getting started: +%% +%% 1 Start one or more distributed Erlang nodes +%% 2a Connect the nodes, e.g. with net_adm:ping/1 +%% 3a Run mnesia_meter:go() +%% 3b Run mnesia_meter:go(ReplicaType) +%% 3c Run mnesia_meter:go(ReplicaType, Nodes) + +-module(mnesia_meter). +-author('[email protected]'). +-export([ + go/0, + go/1, + go/2, + repeat_meter/2 + ]). + +-record(person, {name, %% atomic, unique key + data, %% compound structure + married_to, %% name of partner or undefined + children}). %% list of children + +-record(meter, {desc, init, meter, micros}). + +-record(result, {desc, list}). + +-define(TIMES, 1000). + +go() -> + go(ram_copies). + +go(ReplicaType) -> + go(ReplicaType, [node() | nodes()]). + +go(ReplicaType, Nodes) -> + {ok, FunOverhead} = tc(fun(_) -> {atomic, ok} end, ?TIMES), + Size = size(term_to_binary(#person{})), + io:format("A fun apply costs ~p micro seconds. Record size is ~p bytes.~n", + [FunOverhead, Size]), + Res = go(ReplicaType, Nodes, [], FunOverhead, []), + NewRes = rearrange(Res, []), + DescHeader = lists:flatten(io_lib:format("~w on ~w", [ReplicaType, Nodes])), + ItemHeader = lists:seq(1, length(Nodes)), + Header = #result{desc = DescHeader, list = ItemHeader}, + SepList = ['--------' || _ <- Nodes], + Separator = #result{desc = "", list = SepList}, + display([Separator, Header, Separator | NewRes] ++ [Separator]). + +go(_ReplicaType, [], _Config, _FunOverhead, Acc) -> + Acc; +go(ReplicaType, [H | T], OldNodes, FunOverhead, Acc) -> + Nodes = [H | OldNodes], + Config = [{ReplicaType, Nodes}], + Res = run(Nodes, Config, FunOverhead), + go(ReplicaType, T, Nodes, FunOverhead, [{ReplicaType, Nodes, Res} | Acc]). + +rearrange([{_ReplicaType, _Nodes, Meters} | Tail], Acc) -> + Acc2 = [add_meter(M, Acc) || M <- Meters], + rearrange(Tail, Acc2); +rearrange([], Acc) -> + Acc. + +add_meter(M, Acc) -> + case lists:keysearch(M#meter.desc, #result.desc, Acc) of + {value, R} -> + R#result{list = [M#meter.micros | R#result.list]}; + false -> + #result{desc = M#meter.desc, list = [M#meter.micros]} + end. + +display(Res) -> + MaxDesc = lists:max([length(R#result.desc) || R <- Res]), + Format = lists:concat(["! ~-", MaxDesc, "s"]), + display(Res, Format, MaxDesc). + +display([R | Res], Format, MaxDesc) -> + case R#result.desc of + "" -> + io:format(Format, [lists:duplicate(MaxDesc, "-")]); + Desc -> + io:format(Format, [Desc]) + end, + display_items(R#result.list, R#result.desc), + io:format(" !~n", []), + display(Res, Format, MaxDesc); +display([], _Format, _MaxDesc) -> + ok. + +display_items([_Item | Items], "") -> + io:format(" ! ~s", [lists:duplicate(10, $-)]), + display_items(Items, ""); +display_items([Micros | Items], Desc) -> + io:format(" ! ~10w", [Micros]), + display_items(Items, Desc); +display_items([], _Desc) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +meters() -> + [#meter{desc = "transaction update two records with read and write", + init = fun write_records/2, + meter = fun update_records/1}, + #meter{desc = "transaction update two records with wread and write", + init = fun write_records/2, + meter = fun w_update_records/1}, + #meter{desc = "transaction update two records with read and s_write", + init = fun s_write_records/2, + meter = fun s_update_records/1}, + #meter{desc = "sync_dirty update two records with read and write", + init = fun sync_dirty_write_records/2, + meter = fun sync_dirty_update_records/1}, + #meter{desc = "async_dirty update two records with read and write", + init = fun async_dirty_write_records/2, + meter = fun async_dirty_update_records/1}, + #meter{desc = "plain fun update two records with dirty_read and dirty_write", + init = fun dirty_write_records/2, + meter = fun dirty_update_records/1}, + #meter{desc = "ets update two records with read and write (local only)", + init = fun ets_opt_write_records/2, + meter = fun ets_update_records/1}, + #meter{desc = "plain fun update two records with ets:lookup and ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun bif_update_records/1}, + #meter{desc = "plain fun update two records with dets:lookup and dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun dets_update_records/1}, + + #meter{desc = "transaction write two records with write", + init = fun write_records/2, + meter = fun(X) -> write_records(X, 0-X) end}, + #meter{desc = "transaction write two records with s_write", + init = fun s_write_records/2, + meter = fun(X) -> s_write_records(X, 0-X) end}, + #meter{desc = "sync_dirty write two records with write", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_write_records(X, 0-X) end}, + #meter{desc = "async_dirty write two records with write", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dirty_write", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_write_records(X, 0-X) end}, + #meter{desc = "ets write two records with write (local only)", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_write_records(X, 0-X) end}, + + #meter{desc = "transaction read two records with read", + init = fun write_records/2, + meter = fun(X) -> read_records(X, 0-X) end}, + #meter{desc = "sync_dirty read two records with read", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_read_records(X, 0-X) end}, + #meter{desc = "async_dirty read two records with read", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dirty_read", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_read_records(X, 0-X) end}, + #meter{desc = "ets read two records with read", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with ets:lookup", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dets:lookup", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_read_records(X, 0-X) end} + ]. + +update_fun(Name) -> + fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end. + +update_records(Name) -> + mnesia:transaction(update_fun(Name)). + +sync_dirty_update_records(Name) -> + {atomic, mnesia:sync_dirty(update_fun(Name))}. + +async_dirty_update_records(Name) -> + {atomic, mnesia:async_dirty(update_fun(Name))}. + +ets_update_records(Name) -> + {atomic, mnesia:ets(update_fun(Name))}. + +w_update_records(Name) -> + F = fun() -> + case mnesia:wread({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:wread({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +s_update_records(Name) -> + F = fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:s_write(Pers#person{married_to = undefined}), + mnesia:s_write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +dirty_update_records(Name) -> + case mnesia:dirty_read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:dirty_read({person, Pers#person.married_to}), + mnesia:dirty_write(Pers#person{married_to = undefined}), + mnesia:dirty_write(Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +bif_update_records(Name) -> + case ets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = ets:lookup(person, Pers#person.married_to), + ets:insert(person, Pers#person{married_to = undefined}), + ets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +dets_update_records(Name) -> + case dets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = dets:lookup(person, Pers#person.married_to), + dets:insert(person, Pers#person{married_to = undefined}), + dets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +write_records_fun(Pers, Partner) -> + fun() -> + P = #person{children = [ulla, bella]}, + mnesia:write(P#person{name = Pers, married_to = Partner}), + mnesia:write(P#person{name = Partner, married_to = Pers}) + end. + +write_records(Pers, Partner) -> + mnesia:transaction(write_records_fun(Pers, Partner)). + +sync_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(write_records_fun(Pers, Partner))}. + +async_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(write_records_fun(Pers, Partner))}. + +ets_write_records(Pers, Partner) -> + {atomic, mnesia:ets(write_records_fun(Pers, Partner))}. + +s_write_records(Pers, Partner) -> + F = fun() -> + P = #person{children = [ulla, bella]}, + mnesia:s_write(P#person{name = Pers, married_to = Partner}), + mnesia:s_write(P#person{name = Partner, married_to = Pers}) + end, + mnesia:transaction(F). + +dirty_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + mnesia:dirty_write(P#person{name = Pers, married_to = Partner}), + mnesia:dirty_write(P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +ets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + ets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + bif_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + ets:insert(person, P#person{name = Pers, married_to = Partner}), + ets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +dets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, disc_only_copies}] when N == node() -> + dets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +dets_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + dets:insert(person, P#person{name = Pers, married_to = Partner}), + dets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +read_records_fun(Pers, Partner) -> + fun() -> + case {mnesia:read({person, Pers}), + mnesia:read({person, Partner})} of + {[_], [_]} -> + ok; + _ -> + mnesia:abort(no_such_person) + end + end. + +read_records(Pers, Partner) -> + mnesia:transaction(read_records_fun(Pers, Partner)). + +sync_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(read_records_fun(Pers, Partner))}. + +async_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(read_records_fun(Pers, Partner))}. + +ets_read_records(Pers, Partner) -> + {atomic, mnesia:ets(read_records_fun(Pers, Partner))}. + +dirty_read_records(Pers, Partner) -> + case {mnesia:dirty_read({person, Pers}), + mnesia:dirty_read({person, Partner})} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +bif_read_records(Pers, Partner) -> + case {ets:lookup(person, Pers), + ets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +dets_read_records(Pers, Partner) -> + case {dets:lookup(person, Pers), + dets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +run(Nodes, Config, FunOverhead) -> + Meters = meters(), + io:format("Run ~w meters with table config: ~w~n", [length(Meters), Config]), + rpc:multicall(Nodes, mnesia, lkill, []), + start(Nodes, Config), + Res = [run_meter(Data, Nodes, FunOverhead) || Data <- Meters], + stop(Nodes), + Res. + +run_meter(M, Nodes, FunOverhead) when is_record(M, meter) -> + io:format(".", []), + case catch init_records(M#meter.init, ?TIMES) of + {atomic, ok} -> + rpc:multicall(Nodes, mnesia, dump_log, []), + case tc(M#meter.meter, ?TIMES) of + {ok, Micros} -> + M#meter{micros = lists:max([0, Micros - FunOverhead])}; + {error, Reason} -> + M#meter{micros = Reason} + end; + Res -> + M#meter{micros = Res} + end. + +start(Nodes, Config) -> + mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + Args = [[{dump_log_write_threshold, ?TIMES div 2}, + {dump_log_time_threshold, timer:hours(10)}]], + lists:foreach(fun(Node) -> rpc:call(Node, mnesia, start, Args) end, Nodes), + Attrs = record_info(fields, person), + TabDef = [{attributes, Attrs} | Config], + {atomic, _} = mnesia:create_table(person, TabDef). + +stop(Nodes) -> + rpc:multicall(Nodes, mnesia, stop, []). + +%% Generate some dummy persons +init_records(_Fun, 0) -> + {atomic, ok}; +init_records(Fun, Times) -> + {atomic, ok} = Fun(Times, 0 - Times), + init_records(Fun, Times - 1). + +tc(Fun, Times) -> + case catch timer:tc(?MODULE, repeat_meter, [Fun, Times]) of + {Micros, ok} -> + {ok, Micros div Times}; + {_Micros, {error, Reason}} -> + {error, Reason}; + {'EXIT', Reason} -> + {error, Reason} + end. + +%% The meter must return {atomic, ok} +repeat_meter(Meter, Times) -> + repeat_meter(Meter, {atomic, ok}, Times). + +repeat_meter(_, {atomic, ok}, 0) -> + ok; +repeat_meter(Meter, {atomic, _Result}, Times) when Times > 0 -> + repeat_meter(Meter, Meter(Times), Times - 1); +repeat_meter(_Meter, Reason, _Times) -> + {error, Reason}. + diff --git a/lib/mnesia/test/mnesia_nice_coverage_test.erl b/lib/mnesia/test/mnesia_nice_coverage_test.erl new file mode 100644 index 0000000000..aa9339f6b9 --- /dev/null +++ b/lib/mnesia/test/mnesia_nice_coverage_test.erl @@ -0,0 +1,227 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_nice_coverage_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +-record(nice_tab, {key, val}). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test nice usage of the entire API", + "Invoke all functions in the API, at least once.", + "Try to verify that all functions exists and that they perform", + "reasonable things when used in the most simple way."]; +all(suite) -> [nice]. + +nice(doc) -> [""]; +nice(suite) -> []; +nice(Config) when is_list(Config) -> + %% The whole test suite is one huge test case for the time beeing + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Attrs = record_info(fields, nice_tab), + + initialize(Attrs, Node1), + dirty_access(Node1), + success_and_fail(), + index_mgt(), + + adm(Attrs, Node1, Node2), + snmp(Node1, Node2), + backup(Node1), + ?verify_mnesia(Nodes, []). + +initialize(Attrs, Node1) -> + ?match(Version when is_list(Version), mnesia:system_info(version)), + + Schema = [{name, nice_tab}, + {attributes, Attrs}, {ram_copies, [Node1]}], + + ?match({_, _}, mnesia:system_info(schema_version)), + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match(ok, mnesia:info()), + ?match(set, mnesia:table_info(nice_tab, type)), + ?match(ok, mnesia:schema()), + ?match(ok, mnesia:schema(nice_tab)), + ok. + +dirty_access(Node1) -> + TwoThree = #nice_tab{key=23, val=23}, + TwoFive = #nice_tab{key=25, val=25}, + ?match([], mnesia:dirty_slot(nice_tab, 0)), + ?match(ok, mnesia:dirty_write(TwoThree)), + ?match([TwoThree], mnesia:dirty_read({nice_tab, 23})), + ?match(ok, mnesia:dirty_write(TwoFive)), + ?match(ok, mnesia:dirty_delete_object(TwoFive)), + + ?match(23, mnesia:dirty_first(nice_tab)), + ?match('$end_of_table', mnesia:dirty_next(nice_tab, 23)), + ?match([TwoThree], mnesia:dirty_match_object(TwoThree)), + ?match(ok, mnesia:dirty_delete({nice_tab, 23})), + + CounterSchema = [{ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(nice_counter_tab, CounterSchema)), + TwoFour = {nice_counter_tab, 24, 24}, + ?match(ok, mnesia:dirty_write(TwoFour)), + ?match(34, mnesia:dirty_update_counter({nice_counter_tab, 24}, 10)), + TF = {nice_counter_tab, 24, 34}, + ?match([TF], mnesia:dirty_read({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete_object(TF)), + ok. + +success_and_fail() -> + ?match({atomic, a_good_trans}, mnesia:transaction(fun() ->good_trans()end)), + + BadFun = + fun() -> + Two = #nice_tab{key=2, val=12}, + ?match([Two], mnesia:match_object(#nice_tab{key='$1', val=12})), + ?match([#nice_tab{key=3, val=13}], mnesia:wread({nice_tab, 3})), + ?match(ok, mnesia:delete({nice_tab, 1})), + ?match(ok, mnesia:delete_object(Two)), + mnesia:abort(bad_trans), + ?match(bad, trans) + end, + ?match({aborted, bad_trans}, mnesia:transaction(BadFun)), + ?match(L when is_list(L), mnesia:error_description(no_exists)), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok])), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok], 3)), + ok. + +good_trans() -> + ?match([], mnesia:read(nice_tab, 3)), + ?match([], mnesia:read({nice_tab, 3})), + ?match(ok, mnesia:write(#nice_tab{key=14, val=4})), + ?match([14], mnesia:all_keys(nice_tab)), + + Records = [ #nice_tab{key=K, val=K+10} || K <- lists:seq(1, 10) ], + Ok = [ ok || _ <- Records], + ?match(Ok, lists:map(fun(R) -> mnesia:write(R) end, Records)), + a_good_trans. + + +lock() -> + ?match(ok, mnesia:s_write(#nice_tab{key=22, val=22})), + ?match(ok, mnesia:read_lock_table(nice_tab)), + ?match(ok, mnesia:write_lock_table(nice_tab)), + ok. + +index_mgt() -> + UniversalRec = #nice_tab{key=4711, val=4711}, + ?match(ok, mnesia:dirty_write(UniversalRec)), + ValPos = #nice_tab.val, + ?match({atomic, ok}, mnesia:add_table_index(nice_tab, ValPos)), + + IndexFun = + fun() -> + ?match([UniversalRec], + mnesia:index_read(nice_tab, 4711, ValPos)), + Pat = #nice_tab{key='$1', val=4711}, + ?match([UniversalRec], + mnesia:index_match_object(Pat, ValPos)), + index_trans + end, + ?match({atomic, index_trans}, mnesia:transaction(IndexFun, infinity)), + ?match([UniversalRec], + mnesia:dirty_index_read(nice_tab, 4711, ValPos)), + ?match([UniversalRec], + mnesia:dirty_index_match_object(#nice_tab{key='$1', val=4711}, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(nice_tab, ValPos)), + ok. + +adm(Attrs, Node1, Node2) -> + This = node(), + ?match({ok, This}, mnesia:subscribe(system)), + ?match({atomic, ok}, + mnesia:add_table_copy(nice_tab, Node2, disc_only_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(nice_tab, Node2, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(nice_tab, Node1)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [nice_tab])), + ?match(ok, mnesia:wait_for_tables([schema], infinity)), + + Transformer = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [initial_value]) + end, + ?match({atomic, ok}, + mnesia:transform_table(nice_tab, Transformer, Attrs ++ [extra])), + + ?match({atomic, ok}, mnesia:delete_table(nice_tab)), + DumpSchema = [{name, nice_tab}, {attributes, Attrs}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(DumpSchema)), + ?match({atomic, ok}, mnesia:dump_tables([nice_tab])), + ?match({atomic, ok}, mnesia:move_table_copy(nice_tab, Node2, Node1)), + + ?match(yes, mnesia:force_load_table(nice_counter_tab)), + ?match(dumped, mnesia:dump_log()), + ok. + +backup(Node1) -> + Tab = backup_nice, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({ok,_,_}, mnesia:activate_checkpoint([{name, cp}, {max, [Tab]}])), + File = "nice_backup.BUP", + File2 = "nice_backup2.BUP", + File3 = "nice_backup3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp, File)), + ?match(ok, mnesia:backup_checkpoint(cp, File, mnesia_backup)), + ?match(ok, mnesia:deactivate_checkpoint(cp)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:backup(File, mnesia_backup)), + + Fun = fun(X, Acc) -> {[X], Acc} end, + ?match({ok, 0}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 0}, mnesia:traverse_backup(File, mnesia_backup, dummy, read_only, Fun, 0)), + ?match(ok, mnesia:install_fallback(File)), + ?match(ok, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File, mnesia_backup)), + ?match(ok, mnesia:dump_to_textfile(File3)), + ?match({atomic, ok}, mnesia:load_textfile(File3)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ok. + +snmp(Node1, Node2) -> + Tab = nice_snmp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({aborted, {badarg, Tab, _}}, mnesia:snmp_open_table(Tab, [])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab, [0])), + ?match(undefined, mnesia:snmp_get_row(Tab, [0])), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab, [0])), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab)), + ok. + diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl new file mode 100644 index 0000000000..1e4f776c7d --- /dev/null +++ b/lib/mnesia/test/mnesia_qlc_test.erl @@ -0,0 +1,475 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_qlc_test). + +-compile(export_all). + +-export([all/1]). + +-include("mnesia_test_lib.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +init_per_testcase(Func, Conf) -> + setup(Conf), + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +all(doc) -> + ["Test that the qlc mnesia interface works as expected."]; +all(suite) -> + case code:which(qlc) of + non_existing -> []; + _ -> + all_qlc() + end. + +all_qlc() -> + [dirty, trans, frag, info, mnesia_down]. + +init_testcases(Type,Config) -> + Nodes = [N1,N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])), + ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])), + Write = fun(Id) -> + ok = mnesia:write({a, {a,Id}, 100 - Id}), + ok = mnesia:write({b, {b,100-Id}, Id}) + end, + All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end, + ?match({atomic, ok}, mnesia:sync_transaction(All)), + Nodes. + +%% Test cases +dirty(suite) -> + [dirty_nice_ram_copies, + dirty_nice_disc_copies, + dirty_nice_disc_only_copies]. + +dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies). +dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies). +dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies). + +dirty_nice(suite, _) -> []; +dirty_nice(doc, _) -> []; +dirty_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + + %% Currently unsupported + ?match({'EXIT',{aborted,no_transaction}}, FA()), + ?match({'EXIT',{aborted,no_transaction}}, FB()), + %% + CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + ?match([{a,{a,5},95}], mnesia:async_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:async_dirty(FB)), + ?match(CRes, mnesia:async_dirty(FC)), + ?match(CRes, mnesia:async_dirty(FD)), + ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)), + ?match(CRes, mnesia:sync_dirty(FC)), + ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)), + ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)), + ?match(CRes, mnesia:activity(async_dirty,FC)), + case Type of + disc_only_copies -> skip; + _ -> + ?match([{a,{a,5},95}], mnesia:ets(FA)), + ?match([{a,{a,5},95}], mnesia:activity(ets, FA)) + end, + ?verify_mnesia(Ns, []). + +trans(suite) -> + [trans_nice_ram_copies, + trans_nice_disc_copies, + trans_nice_disc_only_copies, + atomic + ]. + +trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies). +trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies). +trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies). + +trans_nice(suite, _) -> []; +trans_nice(doc, _) -> []; +trans_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + FE = fun() -> qlc:e(QE) end, + + ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)), + ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)), + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)), + ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)), + + ?match({atomic, DRes}, mnesia:transaction(FD)), + ?match({atomic, DRes}, mnesia:transaction(FE)), + + Rest = fun(Cursor,Loop) -> + case qlc:next_answers(Cursor, 1) of + [] -> []; + [A]-> [A|Loop(Cursor,Loop)] + end + end, + Loop = fun() -> + Cursor = qlc:cursor(QD), + Rest(Cursor,Rest) + end, + ?match({atomic, DRes}, mnesia:transaction(Loop)), + + ?verify_mnesia(Ns, []). + +%% -record(a, {k,v}). +%% -record(b, {k,v}). +%% -record(k, {t,v}). + +recs() -> + <<"-record(a, {k,v}). " + "-record(b, {k,v}). " + "-record(k, {t,v}). " + >>. + +atomic(suite) -> [atomic_eval]; +atomic(doc) -> []. + +atomic_eval(suite) -> []; +atomic_eval(doc) -> []; +atomic_eval(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + Eval = fun(Q) -> + {qlc:e(Q), + mnesia:system_info(held_locks)} + end, + Self = self(), + ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}, + ok(Eval,[Q1])), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}, + ok(Eval,[Q2])), + + Flush = fun(Loop) -> %% Clean queue + receive _ -> Loop(Loop) + after 0 -> ok end + end, + + Flush(Flush), + + GrabLock = fun(Father) -> + mnesia:read(a, {a,9}, write), + Father ! locked, + receive cont -> ok end end, + + Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + + put(count,0), + Restart = fun(Locker,Fun) -> + Count = get(count), + case {Count,(catch Fun())} of + {0, {'EXIT', R}} -> + Locker ! cont, + put(count, Count+1), + erlang:yield(), + exit(R); + Else -> + Else + end + end, + + ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}}, + ok(Restart,[Pid1,fun() -> Eval(Q1) end])), + + Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}}, + ok(Restart,[Pid2, fun() -> Eval(Q2) end])), + +%% Basic test + Cursor = fun() -> + QC = qlc:cursor(Q1), + qlc:next_answers(QC) + end, + + ?match([{a,{a,9},91}], ok(Cursor, [])), + %% Lock + + Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + + ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])), + QC1 = ok(fun() -> qlc:cursor(Q1) end, []), + ?match({'EXIT', _}, qlc:next_answers(QC1)), + ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])), + ?verify_mnesia(Ns, []). + + +frag(suite) -> []; +frag(doc) -> []; +frag(Config) -> + Ns = init_testcases(ram_copies,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + + Activate = + fun(Tab) -> + ?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})), + Dist = mnesia_frag_test:frag_dist(Tab), + ?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist})) + end, + Activate(a), + Activate(b), + + Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end, + FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++ + mnesia:activity(sync_dirty, Fun, [b], mnesia_frag), + Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end, + + %% Verify that all data doesn't belong to the same frag. + ?match([], [{Tab,Size(Tab)} || Tab <- FTs, + Size(Tab) =< 0]), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)), + + ?verify_mnesia(Ns, []). + +info(suite) -> []; +info(doc) -> []; +info(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + Q3 = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + %% FIXME compile and check results! + + ?match(ok,io:format("~s~n",[qlc:info(Q1)])), + ?match(ok,io:format("~s~n",[qlc:info(Q2)])), + ?match(ok,io:format("~s~n",[qlc:info(Q3)])), + + ?verify_mnesia(Ns, []). + +ok(Fun,A) -> + case mnesia:transaction(Fun,A) of + {atomic, R} -> R; + E -> E + end. + + +mnesia_down(suite) -> []; +mnesia_down(doc) -> + ["Test bug OTP-7968, which crashed mnesia when a" + "mnesia_down came after qlc had been invoked"]; +mnesia_down(Config) when is_list(Config) -> + [N1,N2] = init_testcases(ram_copies,Config), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Val == Key - 90]">>), + + Tester = self(), + + Eval = fun() -> + Cursor = qlc:cursor(QB), %% Forces another process + Res = qlc:next_answers(Cursor), + Tester ! {qlc, self(), Res}, + {Mod, Tid, Ts} = get(mnesia_activity_state), + receive + continue -> + io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]), + io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]), + io:format("~p~n",[process_info(self(),messages)]), + Res + end + end, + spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end), + + TMInfo = fun() -> + TmInfo = mnesia_tm:get_info(5000), + mnesia_tm:display_info(user, TmInfo) + end, + receive + {qlc, QPid, QRes} -> + ?match([{b,{b,95},5}], QRes), + TMInfo(), + mnesia_test_lib:kill_mnesia([N2]), + %%timer:sleep(1000), + QPid ! continue + after 2000 -> + exit(timeout1) + end, + + receive + {test, QRes2} -> + ?match({atomic, [{b,{b,95},5}]}, QRes2) + after 2000 -> + exit(timeout2) + end, + + ?verify_mnesia([N1], [N2]). + + +nested_qlc(suite) -> []; +nested_qlc(doc) -> + ["Test bug in OTP-7968 (the second problem) where nested" + "transaction don't work as expected"]; +nested_qlc(Config) when is_list(Config) -> + Ns = init_testcases(ram_copies,Config), + Res = as_with_bs(), + ?match([_|_], Res), + top_as_with_some_bs(10), + + ?verify_mnesia(Ns, []). + + +%% Code from Daniel +bs_by_a_id(A_id) -> + find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])). + +as_with_bs() -> + find(qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)])). + +top_as_with_some_bs(Limit) -> + top( + qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)]), + Limit, + fun(A1,A2) -> A1 < A2 end + ). + +% --- utils + +find(Q) -> + F = fun() -> qlc:e(Q) end, + {atomic, Res} = mnesia:transaction(F), + Res. + +% --- it returns top Limit results from query Q ordered by Order sort function +top(Q, Limit, Order) -> + Do = fun() -> + OQ = qlc:sort(Q, [{order,Order}]), + QC = qlc:cursor(OQ), + Res = qlc:next_answers(QC, Limit), + qlc:delete_cursor(QC), + Res + end, + {atomic, Res} = mnesia:transaction(Do), + Res. + +%% To keep mnesia suite backward compatible, +%% we compile the queries in runtime when qlc is available +%% Compiles and returns a handle to a qlc +handle(Expr) -> + handle(<<>>,Expr). +handle(Records,Expr) -> + case catch handle2(Records,Expr) of + {ok, Handle} -> + Handle; + Else -> + ?match(ok, Else) + end. + +handle2(Records,Expr) -> + {FN,Mod} = temp_name(), + ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"), + Prog = << + ModStr/binary, + "-include_lib(\"stdlib/include/qlc.hrl\").\n", + "-export([tmp/0]).\n", + Records/binary,"\n", + "tmp() ->\n", +%% " _ = (catch throw(fvalue_not_reset))," + " qlc:q( ", + Expr/binary,").\n">>, + + ?match(ok,file:write_file(FN,Prog)), + {ok,Forms} = epp:parse_file(FN,"",""), + {ok,Mod,Bin} = compile:forms(Forms), + code:load_binary(Mod,FN,Bin), + {ok, Mod:tmp()}. + +setup(Config) -> + put(mts_config,Config), + put(mts_tf_counter,0). + +temp_name() -> + Conf = get(mts_config), + C = get(mts_tf_counter), + put(mts_tf_counter,C+1), + {filename:join([proplists:get_value(priv_dir,Conf, "."), + "tempfile"++integer_to_list(C)++".tmp"]), + list_to_atom("tmp" ++ integer_to_list(C))}. diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl new file mode 100644 index 0000000000..f6ecf2ce2e --- /dev/null +++ b/lib/mnesia/test/mnesia_recovery_test.erl @@ -0,0 +1,1701 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_recovery_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). +-include_lib("kernel/include/file.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify recoverability", + "Verify that the effects of committed transactions are preserved", + "after recovery from system failures. It must be possible to", + "restore the tables to a consistent state on a node, from (any kind", + "of) replica on other nodes as well as from local disk on the failed", + "node. The system must also recover from instantaneous", + "interruption causing disk files to not be completely synchronized."]; + +all(suite) -> + [ + mnesia_down, + explicit_stop, + coord_dies, + schema_trans, + async_dirty, + sync_dirty, + sym_trans, + asym_trans, + after_full_disc_partition, + after_corrupt_files, + disc_less, + garb_decision, + system_upgrade + ]. + +schema_trans(suite) -> + [{mnesia_schema_recovery_test, all}]. + +tpcb_config(ReplicaType, _NodeConfig, Nodes) -> + [{n_branches, 5}, + {n_drivers_per_node, 5}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 20}, + {replica_type, ReplicaType}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mnesia_down(doc) -> + [" Various tests about recovery when mnesia goes down on one or several nodes."]; +mnesia_down(suite) -> + [ + mnesia_down_during_startup, + master_node_tests, + read_during_down, + with_checkpoint, + delete_during_start + ]. + +master_node_tests(doc) -> + ["Verify that mnesia loads the correct data after it has been down, regarding master node settings."]; +master_node_tests(suite) -> + [ + no_master_2, + no_master_3, + one_master_2, + one_master_3, + two_master_2, + two_master_3, + all_master_2, + all_master_3 + ]. + +no_master_2(suite) -> []; +no_master_2(Config) when is_list(Config) -> mnesia_down_2(no, Config). + +no_master_3(suite) -> []; +no_master_3(Config) when is_list(Config) -> mnesia_down_3(no, Config). + +one_master_2(suite) -> []; +one_master_2(Config) when is_list(Config) -> mnesia_down_2(one, Config). + +one_master_3(suite) -> []; +one_master_3(Config) when is_list(Config) -> mnesia_down_3(one, Config). + +two_master_2(suite) -> []; +two_master_2(Config) when is_list(Config) -> mnesia_down_2(two, Config). + +two_master_3(suite) -> []; +two_master_3(Config) when is_list(Config) -> mnesia_down_3(two, Config). + +all_master_2(suite) -> []; +all_master_2(Config) when is_list(Config) -> mnesia_down_2(all, Config). + +all_master_3(suite) -> []; +all_master_3(Config) when is_list(Config) -> mnesia_down_3(all, Config). + +mnesia_down_2(Masters, Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N1]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab10, tab12]; % ram_copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5]; + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab10, tab11, tab12, tab13] + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + ?verify_mnesia(Nodes, []). + +mnesia_down_3(Masters, Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab16, [{ram_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab17, [{disc_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab18, [{disc_only_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab19, [{ram_copies, [N3]}, {disc_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab20, [{ram_copies, [N3]}, {disc_only_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab21, [{disc_only_copies, [N3]}, {disc_copies, [N1, N2]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15, + tab16, tab17, tab18, tab19, tab20, tab21], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5, tab16]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab16, tab10, tab12]; % ram copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5, tab16]; % ram copies + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab16, tab10, + tab11, tab19, tab12, tab13, tab20] % ram copies + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N1, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N3, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + + ?verify_mnesia(Nodes, []). + + +read_during_down(doc) -> + ["Verify that read operation can continue to read when mnesia goes down"]; +read_during_down(suite) -> + [ + dirty_read_during_down, + trans_read_during_down + ]. + +dirty_read_during_down(suite) -> + []; +dirty_read_during_down(Config) when is_list(Config) -> + read_during_down(dirty, Config). + +trans_read_during_down(suite) -> + []; +trans_read_during_down(Config) when is_list(Config) -> + read_during_down(trans, Config). + + +read_during_down(Op, Config) when is_list(Config) -> + Ns = [N1|TNs] = ?acquire_nodes(3, Config), + Tabs = [ram, disc, disco], + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, TNs}])), + + %% Create some work for mnesia_controller when a node goes down + [{atomic, ok} = mnesia:create_table(list_to_atom("temp" ++ integer_to_list(N)), + [{ram_copies, Ns}]) || N <- lists:seq(1, 50)], + + Write = fun(Tab) -> mnesia:write({Tab, key, val}) end, + ?match([ok,ok,ok], + [mnesia:sync_dirty(Write, [Tab]) || Tab <- Tabs]), + + Readers = [spawn_link(N1, ?MODULE, reader, [Tab, Op]) || Tab <- Tabs], + [_|_] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?log("W2R ~p~n", [W2R]), + loop_and_kill_mnesia(10, hd(W2R), Tabs), + [Pid ! self() || Pid <- Readers], + ?match([ok, ok, ok], [receive ok -> ok after 1000 -> {Pid, mnesia_lib:dist_coredump()} end || Pid <- Readers]), + ?verify_mnesia(Ns, []). + +reader(Tab, OP) -> + Res = case OP of + dirty -> + catch mnesia:dirty_read({Tab, key}); + trans -> + Read = fun() -> mnesia:read({Tab, key}) end, + {_, Temp} = mnesia:transaction(Read), + Temp + end, + case Res of + [{Tab, key, val}] -> ok; + Else -> + ?error("Expected ~p Got ~p ~n", [[{Tab, key, val}], Else]), + erlang:error(test_failed) + end, + receive Pid -> + Pid ! ok + after 50 -> + reader(Tab, OP) + end. + +loop_and_kill_mnesia(0, _Node, _Tabs) -> ok; +loop_and_kill_mnesia(N, Node, Tabs) -> + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(100), + ?match([], mnesia_test_lib:start_mnesia([Node], Tabs)), + [KN | _] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?match([KN, KN,KN], W2R), + timer:sleep(100), + loop_and_kill_mnesia(N-1, KN, Tabs). + +mnesia_down_during_startup(doc) -> + ["Verify that mnesia can come back up again in a consistent state", + "after it has gone down during startup (with different store and", + "when it goes down in different situations"]; +mnesia_down_during_startup(suite) -> + [ + mnesia_down_during_startup_disk_ram, + mnesia_down_during_startup_init_ram, + mnesia_down_during_startup_init_disc, + mnesia_down_during_startup_init_disc_only, + mnesia_down_during_startup_tm_ram, + mnesia_down_during_startup_tm_disc, + mnesia_down_during_startup_tm_disc_only + ]. + +mnesia_down_during_startup_disk_ram(suite) -> []; +mnesia_down_during_startup_disk_ram(Config) when is_list(Config)-> + [Node1, Node2] = ?acquire_nodes(2, Config ++ + [{tc_timeout, timer:minutes(2)}]), + Tab = down_during_startup, + Def = [{ram_copies, [Node2]}, {disc_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 876234, test_ok})), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:start_mnesia([Node1, Node2], [Tab]), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(500), + ?match([], mnesia_test_lib:start_mnesia([Node1], [Tab])), + ?match([{Tab, 876234, test_ok}], mnesia:dirty_read({Tab,876234})), + ?verify_mnesia([Node1, Node2], []). + +mnesia_down_during_startup_init_ram(suite) -> []; +mnesia_down_during_startup_init_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc(suite) -> []; +mnesia_down_during_startup_init_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc_only(suite) -> []; +mnesia_down_during_startup_init_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_ram(suite) -> []; +mnesia_down_during_startup_tm_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc(suite) -> []; +mnesia_down_during_startup_tm_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc_only(suite) -> []; +mnesia_down_during_startup_tm_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup2(Config, ReplicaType, Debug_Point, _Father) -> + ?log("TC~n mnesia_down_during_startup with type ~w and stops at ~w~n", + [ReplicaType, Debug_Point]), + Tpcb_tabs = [history,teller,account,branch], + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + TpcbConfig = tpcb_config(ReplicaType, 2, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + ?match_receive(timeout), + timer:sleep(timer:seconds(10)), % Let tpcb run for a while + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(timer:seconds(2)), + Self = self(), + TestFun = fun(_MnesiaEnv, _EvalEnv) -> + ?deactivate_debug_fun(Debug_Point), + Self ! fun_done, + spawn(mnesia_test_lib, kill_mnesia, [[Node1]]) + end, + ?activate_debug_fun(Debug_Point, TestFun, []), % Kill when debug has been reached + mnesia:start(), + Res = receive fun_done -> ok after timer:minutes(3) -> timeout end, % Wait till it's killed + ?match(ok, Res), + ?match(ok, timer:sleep(timer:seconds(2))), % Wait a while, at least till it dies; + ?match([], mnesia_test_lib:start_mnesia([Node1], Tpcb_tabs)), + ?match(ok, mnesia_tpcb:verify_tabs()), % Verify it + ?verify_mnesia(Nodes, []). + + +with_checkpoint(doc) -> + ["Restart mnesia with checkpoint"]; +with_checkpoint(suite) -> + [with_checkpoint_same, with_checkpoint_other]. + +with_checkpoint_same(suite) -> []; +with_checkpoint_same(Config) when is_list(Config) -> + with_checkpoint(Config, same). + +with_checkpoint_other(suite) -> []; +with_checkpoint_other(Config) when is_list(Config) -> + with_checkpoint(Config, other). + +with_checkpoint(Config, Type) when is_list(Config) -> + Nodes = [Node1, Node2] = ?acquire_nodes(2, Config), + Kill = case Type of + same -> %% Node1 is the one used for creating the checkpoint + Node1; %% and which we bring down + other -> + Node2 %% Here we bring node2 down.. + end, + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, Nodes}])), + Tabs = [ram, disc, disco], + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + ?match([], check_retainers(sune, Nodes)), + + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + timer:sleep(500), %% Just to help debugging the io:formats now comes in the + %% correct order... :-) + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key}) || Key <- lists:seq(1,10)] || Tab <- Tabs], + + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(100), + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + case Kill of + Node1 -> + ignore; + Node2 -> + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(500), %% Just to help debugging + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key+2}) || Key <- lists:seq(1,10)] || + Tab <- Tabs], + + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started ~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + ok + end, + ?verify_mnesia(Nodes, []). + +check_chkp(Nodes) -> + {Good, Bad} = rpc:multicall(Nodes, ?MODULE, check, []), + lists:flatten(Good ++ Bad). + +check() -> + [PCP] = ets:match_object(mnesia_gvar, {pending_checkpoint_pids, '_'}), + [PC] = ets:match_object(mnesia_gvar, {pending_checkpoints, '_'}), + [CPN] = ets:match_object(mnesia_gvar, {checkpoints, '_'}), + F = lists:filter(fun({_, []}) -> false; (_W) -> true end, + [PCP,PC,CPN]), + CPP = ets:match_object(mnesia_gvar, {{checkpoint, '_'}, '_'}), + Rt = ets:match_object(mnesia_gvar, {{'_', {retainer, '_'}}, '_'}), + F ++ CPP ++ Rt. + + +check_retainers(CHP, Nodes) -> + {[R1,R2], []} = rpc:multicall(Nodes, ?MODULE, get_all_retainers, [CHP]), + (R1 -- R2) ++ (R2 -- R1). + +get_all_retainers(CHP) -> + Tabs = mnesia:system_info(local_tables), + Iter = fun(Tab) -> + {ok, Res} = + mnesia_checkpoint:iterate(CHP, Tab, fun(R, A) -> [R|A] end, [], + retainer, checkpoint), +%% io:format("Retainer content ~w ~n", [Res]), + Res + end, + Elements = [Iter(Tab) || Tab <- Tabs], + lists:sort(lists:flatten(Elements)). + +delete_during_start(doc) -> + ["Test that tables can be delete during start, hopefully with tables" + " in the loader queue or soon to be"]; +delete_during_start(suite) -> []; +delete_during_start(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1, 30)], + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + + [?match({atomic, ok},mnesia:create_table(Tab, [{ram_copies,Nodes}])) || Tab <- Tabs], + lists:foldl(fun(Tab, I) -> + ?match({atomic, ok}, + mnesia:change_table_load_order(Tab,I)), + I+1 + end, 1, Tabs), + mnesia_test_lib:kill_mnesia([N2,N3]), +%% timer:sleep(500), + ?match({[ok,ok],[]}, rpc:multicall([N2,N3], mnesia,start, + [[{extra_db_nodes,[N1]}]])), + [Tab1,Tab2,Tab3|_] = Tabs, + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + + ?log("W4T ~p~n", [rpc:multicall([N2,N3], mnesia, wait_for_tables, [[Tab1,Tab2,Tab3],1])]), + + Remain = Tabs--[Tab1,Tab2], + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [Remain,10000])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [Remain,10000])), + + ?match(ok, rpc:call(N2, ?MODULE, verify_where2read, [Remain])), + ?match(ok, rpc:call(N3, ?MODULE, verify_where2read, [Remain])), + + ?verify_mnesia(Nodes, []). + +verify_where2read([Tab|Tabs]) -> + true = (node() == mnesia:table_info(Tab,where_to_read)), + verify_where2read(Tabs); +verify_where2read([]) -> ok. + + +%%------------------------------------------------------------------------------------------- +explicit_stop(doc) -> + ["Stop Mnesia in different situations"]; +explicit_stop(suite) -> + [explicit_stop_during_snmp]. +%% This is a bad implementation, but at least gives a indication if something is wrong +explicit_stop_during_snmp(suite) -> []; +explicit_stop_during_snmp(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(2, Config), + [Node1, Node2] = Nodes, + Tab = snmp_tab, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 1, 1}) end)), + + Do_trans_Pid1 = spawn_link(Node2, ?MODULE, do_trans_loop, [Tab, self()]), + Do_trans_Pid2 = spawn_link(?MODULE, do_trans_loop, [Tab, self()]), + Start_stop_Pid = spawn_link(?MODULE, start_stop, [Node1, 10, self()]), + receive + test_done -> + ok + after timer:minutes(5) -> + ?error("test case time out~n", []) + end, + ?verify_mnesia(Nodes, []), + exit(Do_trans_Pid1, kill), + exit(Do_trans_Pid2, kill), + exit(Start_stop_Pid, kill), + ok. + +do_trans_loop(Tab, Father) -> + %% Do not trap exit + do_trans_loop2(Tab, Father). +do_trans_loop2(Tab, Father) -> + Trans = + fun() -> + [{Tab, 1, Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val + 1}) + end, + case mnesia:transaction(Trans) of + {atomic, ok} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {node_not_running, N}} when N == node() -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {no_exists, Tab}} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + Else -> + ?error("Transaction failed: ~p ~n", [Else]), + Father ! test_done, + exit(shutdown) + end. + +start_stop(_Node1, 0, Father) -> + Father ! test_done, + exit(shutdown); +start_stop(Node1, N, Father) when N > 0-> + timer:sleep(timer:seconds(5)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + timer:sleep(timer:seconds(2)), + ?match([], mnesia_test_lib:start_mnesia([Node1])), + start_stop(Node1, N-1, Father). + +coord_dies(suite) -> []; +coord_dies(doc) -> [""]; +coord_dies(Config) when is_list(Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{ram_copies, [N2]}])), + Tester = self(), + + U1 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + Tester ! {self(),continue}, + receive + continue -> exit(crash) + end + end, + U2 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + mnesia:transaction(U1, [Tab]) + end, + [mnesia:dirty_write(Tab,{Tab,key,0}) || Tab <- [tab1,tab2,tab3]], + Pid1 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid2 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid3 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + [receive {Pid,continue} -> ok end || Pid <- [Pid1,Pid2,Pid3]], + Pid1 ! continue, Pid2 ! continue, Pid3 ! continue, + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + Pid4 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid5 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid6 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + erlang:monitor(process, Pid4),erlang:monitor(process, Pid5),erlang:monitor(process, Pid6), + + [receive {Pid,continue} -> ok end || Pid <- [Pid4,Pid5,Pid6]], + exit(Pid4,crash), + ?match_receive({'DOWN',_,_,Pid4, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + exit(Pid5,crash), + ?match_receive({'DOWN',_,_,Pid5, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + exit(Pid6,crash), + ?match_receive({'DOWN',_,_,Pid6, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sym_trans(doc) -> + ["Recovery of symmetrical transactions in a couple of different", + "situations; when coordinator or participant or node dies"]; + +sym_trans(suite) -> + [sym_trans_before_commit_kill_coord_node, %% coordinator node dies + sym_trans_before_commit_kill_coord_pid, %% coordinator process dies + sym_trans_before_commit_kill_part_after_ask, %% participating node dies + sym_trans_before_commit_kill_part_before_ask, + sym_trans_after_commit_kill_coord_node, + sym_trans_after_commit_kill_coord_pid, + sym_trans_after_commit_kill_part_after_ask, + sym_trans_after_commit_kill_part_do_commit_pre, + sym_trans_after_commit_kill_part_do_commit_post]. + +%kill_after_debug_point(Config, TestCase, {Debug_node, Debug_Point}, TransFun, Tab) + +sym_trans_before_commit_kill_coord_node(suite) -> []; +sym_trans_before_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]}, {ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_coord_pid(suite) -> []; +sym_trans_before_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_after_ask(suite) -> []; +sym_trans_before_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_before_ask(suite) -> []; +sym_trans_before_commit_kill_part_before_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_before_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_node(suite) -> []; +sym_trans_after_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_pid(suite) -> []; +sym_trans_after_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab,Def}], Nodes). + +sym_trans_after_commit_kill_part_after_ask(suite) -> []; +sym_trans_after_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_pre(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_pre(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_post(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_post(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sym_trans([Tab], _Fahter) -> + ?dl("Starting SYM_TRANS with active debug fun ", []), + Trans = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + Res = mnesia:transaction(Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", + [Else]) + end, + ?dl("SYM_TRANSACTION done: ~p (deactiv dbgfun) ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sync_dirty(doc) -> + ["Verify recovery of synchronously operations in a couple of different", + "situations"]; +sync_dirty(suite) -> + [sync_dirty_pre_kill_part, + sync_dirty_pre_kill_coord_node, + sync_dirty_pre_kill_coord_pid, + sync_dirty_post_kill_part, + sync_dirty_post_kill_coord_node, + sync_dirty_post_kill_coord_pid + ]. + +sync_dirty_pre_kill_part(suite) -> []; +sync_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_node(suite) -> []; +sync_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_pid(suite) -> []; +sync_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_part(suite) -> []; +sync_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_node(suite) -> []; +sync_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_pid(suite) -> []; +sync_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sync_dirty([Tab], _Father) -> + ?dl("Starting SYNC_DIRTY", []), + SYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:sync_dirty(SYNC)), + ?dl("SYNC_DIRTY done: ~p ", [Res]), + ok. + +async_dirty(doc) -> + ["Verify recovery of asynchronously dirty operations in a couple of different", + "situations"]; +async_dirty(suite) -> + [async_dirty_pre_kill_part, + async_dirty_pre_kill_coord_node, + async_dirty_pre_kill_coord_pid, + async_dirty_post_kill_part, + async_dirty_post_kill_coord_node, + async_dirty_post_kill_coord_pid]. + +async_dirty_pre_kill_part(suite) -> []; +async_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_node(suite) -> []; +async_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_pid(suite) -> []; +async_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_part(suite) -> []; +async_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_node(suite) -> []; +async_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_pid(suite) -> []; +async_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_async_dirty([Tab], _Fahter) -> + ?dl("Starting ASYNC", []), + ASYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:async_dirty(ASYNC)), + ?dl("ASYNC done: ~p ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +asym_trans(doc) -> + ["Recovery of asymmetrical transactions in a couple of different", + "situations, currently the error cases are not covered, i.e. ", + "not tested are the situations when we kill mnesia or a process", + "during a recovery"]; +asym_trans(suite) -> + [ + asym_trans_kill_part_ask, + asym_trans_kill_part_commit_vote, + asym_trans_kill_part_pre_commit, + asym_trans_kill_part_log_commit, + asym_trans_kill_part_do_commit, + asym_trans_kill_coord_got_votes, + asym_trans_kill_coord_pid_got_votes, + asym_trans_kill_coord_log_commit_rec, + asym_trans_kill_coord_pid_log_commit_rec, + asym_trans_kill_coord_log_commit_dec, + asym_trans_kill_coord_pid_log_commit_dec, + asym_trans_kill_coord_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_rec_acc_pre_commit_done_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit + ]. + +asym_trans_kill_part_ask(suite) -> []; +asym_trans_kill_part_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_commit_vote(suite) -> []; +asym_trans_kill_part_commit_vote(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, vote_yes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_pre_commit(suite) -> []; +asym_trans_kill_part_pre_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, pre_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_log_commit(suite) -> []; +asym_trans_kill_part_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_do_commit(suite) -> []; +asym_trans_kill_part_do_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, do_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_got_votes(suite) -> []; +asym_trans_kill_coord_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_got_votes(suite) -> []; +asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_rec(suite) -> []; +asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_rec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_dec(suite) -> []; +asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_dec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +do_asym_trans([Tab1, Tab2 | _R], Garbhandler) -> + ?dl("Starting asym trans ", []), + ASym_Trans = fun() -> + TidTs = {_Mod, Tid, _Store} = + mnesia:get_activity_id(), + ?verbose("===> asym_trans: ~w~n", [TidTs]), + Garbhandler ! {trans_id, Tid}, + [{_, _, Val1}] = mnesia:read({Tab1, 1}), + [{_, _, Val2}] = mnesia:read({Tab2, 1}), + mnesia:write({Tab1, 1, Val1+1}), + mnesia:write({Tab2, 1, Val2+1}) + end, + Res = mnesia:transaction(ASym_Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + _Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", [Res]) + end, + ?dl("Asym trans finished with: ~p ", [Res]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +kill_after_debug_point(Kill, {DebugNode, Debug_Point}, TransFun, TabsAndDefs, Nodes) -> + [Coord | _rest] = Nodes, + + Create = fun({Tab, Def}) -> ?match({atomic, ok}, mnesia:create_table(Tab, Def)) end, + lists:foreach(Create, TabsAndDefs), + Tabs = [T || {T, _} <- TabsAndDefs], + Write = fun(Tab) -> ?match(ok, mnesia:dirty_write({Tab, 1, 100})) end, + lists:foreach(Write, Tabs), + + Self = self(), + SyncFun = fun(_Env1, _Env2) -> % Just Sync with test prog + Self ! {self(), fun_in_position}, + ?dl("SyncFun, sending fun_in_position ", []), + receive continue -> + ?dl("SyncFun received continue ",[]), + ok + after timer:seconds(60) -> + ?error("Timeout in sync_fun on ~p~n", [node()]) + end + end, + + Garb_handler = spawn_link(?MODULE, garb_handler, [[]]), + + ?remote_activate_debug_fun(DebugNode, Debug_Point, SyncFun, []), + ?dl("fun_in_position activated at ~p with ~p", [DebugNode, Debug_Point]), + %% Spawn and do the transaction + Pid = spawn(Coord, ?MODULE, TransFun, [Tabs, Garb_handler]), + %% Wait till all the Nodes are in correct position + [{StoppedPid,_}] = ?receive_messages([fun_in_position]), + ?dl("Received fun_in_position; Removing the debug funs ~p", [DebugNode]), + ?remote_deactivate_debug_fun(DebugNode, Debug_Point), + + case Kill of + coord_pid -> + ?dl("Intentionally killing pid ~p ", [Pid]), + exit(Pid, normal); + Node -> + mnesia_test_lib:kill_mnesia([Node]) + end, + + StoppedPid ! continue, %% Send continue, it may still be alive + + %% Start and check that the databases are consistent + ?dl("Done, Restarting and verifying result ",[]), + case Kill of + coord_pid -> ok; + _ -> % Ok, mnesia on some node was killed restart it + timer:sleep(timer:seconds(3)), %% Just let it have the time to die + ?match(ok, rpc:call(Kill, mnesia, start, [[]])), + ?match(ok, rpc:call(Kill, mnesia, wait_for_tables, [Tabs, 60000])) + end, + Trans_res = verify_tabs(Tabs, Nodes), + case TransFun of + do_asym_trans -> + %% Verifies that decisions are garbed, only valid for asym_tran + Garb_handler ! {get_tids, self()}, + Tid_list = receive + {tids, List} -> + ?dl("Fun rec ~w", [List]), + List + end, + garb_of_decisions(Kill, Nodes, Tid_list, Trans_res); + _ -> + ignore + end, + ?verify_mnesia(Nodes, []). + +garb_of_decisions(Kill, Nodes, Tid_list, Trans_res) -> + [Coord, Part1, Part2] = Nodes, + %% Check that decision log is empty on all nodes after the trans is finished + verify_garb_decision_log(Nodes, Tid_list), + case Trans_res of + aborted -> + %% Check that aborted trans have not been restarted!! + ?match(1, length(Tid_list)), + %% Check the transient decision logs + %% A transaction should only be aborted in an early stage of + %% the trans before the any Node have logged anything + verify_garb_transient_logs(Nodes, Tid_list, aborted), + %% And only when the coordinator are have died + %% Else he would have restarted the transaction + ?match(Kill, Coord); + updated -> + case length(Tid_list) of + 1 -> + %% If there was only one transaction, it should be logged as + %% comitted on every node! + [Tid1] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], committed); + 2 -> + %% If there is two transaction id, then the first + %% TID should have been aborted and the transaction + %% restarted with a new TID + [Tid1, Tid2] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], aborted), + %% If mnesia is killed on a node i.e Coord and Part1 than they + %% won't know about the restarted trans! The rest of the nodes + %% should know that the trans was committed + case Kill of + coord_pid -> + verify_garb_transient_logs(Nodes, [Tid2], committed); + Coord -> + verify_garb_transient_logs([Part1, Part2], [Tid2], committed), + verify_garb_transient_logs([Coord], [Tid2], not_found); + Part1 -> + verify_garb_transient_logs([Coord, Part2], [Tid2], committed), + verify_garb_transient_logs([Part1], [Tid2], not_found) + end + end + end. + +verify_garb_decision_log([], _Tids) -> ok; +verify_garb_decision_log([Node|R], Tids) -> + Check = fun(Tid) -> %% Node, Tid used in debugging! + ?match({{not_found, _}, Node, Tid}, + {outcome(Tid, [mnesia_decision]), Node, Tid}) + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_decision_log(R, Tids). + +verify_garb_transient_logs([], _Tids, _) -> ok; +verify_garb_transient_logs([Node|R], Tids, Exp_Res) -> + Check = fun(Tid) -> + LatestTab = mnesia_lib:val(latest_transient_decision), + PrevTabs = mnesia_lib:val(previous_transient_decisions), + case outcome(Tid, [LatestTab | PrevTabs]) of + {found, {_, [{_,_Tid, Exp_Res}]}} -> ok; + {not_found, _} when Exp_Res == not_found -> ok; + {not_found, _} when Exp_Res == aborted -> ok; + Else -> ?error("Expected ~p in trans ~p on ~p got ~p~n", + [Exp_Res, Tid, Node, Else]) + end + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_transient_logs(R, Tids, Exp_Res). + +outcome(Tid, Tabs) -> + outcome(Tid, Tabs, Tabs). + +outcome(Tid, [Tab | Tabs], AllTabs) -> + case catch ets:lookup(Tab, Tid) of + {'EXIT', _} -> + outcome(Tid, Tabs, AllTabs); + [] -> + outcome(Tid, Tabs, AllTabs); + Val -> + {found, {Tab, Val}} + end; +outcome(_Tid, [], AllTabs) -> + {not_found, AllTabs}. + + +verify_tabs([Tab|R], Nodes) -> + [_Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + {success, A} = ?match({atomic, _}, mnesia:transaction(Read)), + ?match(A, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(A, rpc:call(Part2, mnesia, transaction, [Read])), + {atomic, [{Tab, 1, Res}]} = A, + verify_tabs(R, Nodes, Res). + +verify_tabs([], _Nodes, Res) -> + case Res of + 100 -> aborted; + 101 -> updated + end; + +verify_tabs([Tab | Rest], Nodes, Res) -> + [Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + Exp = {atomic, [{Tab, 1, Res}]}, + ?match(Exp, rpc:call(Coord, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part2, mnesia, transaction, [Read])), + verify_tabs(Rest, Nodes, Res). + +%% Gather TIDS and send them to requesting process and exit! +garb_handler(List) -> + receive + {trans_id, ID} -> garb_handler([ID|List]); + {get_tids, Pid} -> Pid ! {tids, lists:reverse(List)} + end. + +%%%%%%%%%%%%%%%%%%%%%%% +receive_messages([], _File, _Line) -> []; +receive_messages(ListOfMsgs, File, Line) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + mnesia_test_lib:log("<>WARNING<>~n" + "Received unexpected msg~n ~p ~n" + "While waiting for ~p~n", + [{Pid, Msg}, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line); + true -> + ?dl("Got msg ~p from ~p ", [Msg, node(Pid)]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg], File, Line)] + end; + Else -> mnesia_test_lib:log("<>WARNING<>~n" + "Recevied unexpected or bad formatted msg~n ~p ~n" + "While waiting for ~p~n", + [Else, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line) + after timer:minutes(2) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_full_disc_partition(doc) -> + ["Verify that the database does not get corrupt", + "when Mnesia encounters a full disc partition"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% interrupted_fallback_start +%% is implemented in consistency interupted_install_fallback! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +after_corrupt_files(doc) -> + ["Verify that mnesia (and dets) can handle corrupt files"]; +after_corrupt_files(suite) -> % cope with unsynced disks + [after_corrupt_files_decision_log_head, + after_corrupt_files_decision_log_tail, + after_corrupt_files_latest_log_head, + after_corrupt_files_latest_log_tail, + after_corrupt_files_table_dat_head, + after_corrupt_files_table_dat_tail, + after_corrupt_files_schema_dat_head, + after_corrupt_files_schema_dat_tail + ]. + +after_corrupt_files_decision_log_head(suite) -> []; +after_corrupt_files_decision_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", head, repair). + +after_corrupt_files_decision_log_tail(suite) -> []; +after_corrupt_files_decision_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", tail, repair). + +after_corrupt_files_latest_log_head(suite) -> []; +after_corrupt_files_latest_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", head, repair). + +after_corrupt_files_latest_log_tail(suite) -> []; +after_corrupt_files_latest_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", tail, repair). + +after_corrupt_files_table_dat_head(suite) -> []; +after_corrupt_files_table_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", head, crash). + +after_corrupt_files_table_dat_tail(suite) -> []; +after_corrupt_files_table_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", tail, repair). + +after_corrupt_files_schema_dat_head(suite) -> []; +after_corrupt_files_schema_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", head, crash). + +after_corrupt_files_schema_dat_tail(suite) -> []; +after_corrupt_files_schema_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", tail, crash). + + + +%%% BUGBUG: We should also write testcase's for autorepair=false i.e. +%%% not the standard case! +after_corrupt_files(Config, File, Where, Behaviour) -> + [Node] = ?acquire_nodes(1, Config ++ [{tc_timeout, timer:minutes(2)}]), + Tab = rec_files, + Def = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert_data(Tab, 100), + Dir = mnesia:system_info(directory), + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(timer:seconds(10)), % Let dets finish whatever it does + + DirFile = Dir ++ "/" ++ File, + + {ok, Fd} = file:open(DirFile, read_write), + {ok, FileInfo} = file:read_file_info(DirFile), + case Where of + head -> + ?match({ok, _NewP}, file:position(Fd, {bof, 1})), + ?match(ok, file:write(Fd, [255, 255, 255, 255, 255, 255, 255, 255, 254])), + ok; + tail -> + Size = FileInfo#file_info.size, + Half = Size div 2, + + ?dl(" Size = ~p Half = ~p ", [Size, Half]), + ?match({ok, _NewP}, file:position(Fd, {bof, Half})), + ?match(ok, file:truncate(Fd)), + ok + end, + ?match(ok, file:close(Fd)), + + ?warning("++++++SOME OF THE after_corrupt* TEST CASES WILL INTENTIONALLY CRASH MNESIA+++++++~n", []), + Pid = spawn_link(?MODULE, mymnesia_start, [self()]), + receive + {Pid, ok} -> + ?match(ok, mnesia:wait_for_tables([schema, Tab], 10000)), + ?match(ok, verify_data(Tab, 100)), + case mnesia_monitor:get_env(auto_repair) of + false -> + ?error("Mnesia should have crashed in ~p ~p ~n", + [File, Where]); + true -> + ok + end, + ?verify_mnesia([Node], []); + {Pid, {error, ED}} -> + case {mnesia_monitor:get_env(auto_repair), Behaviour} of + {true, repair} -> + ?error("Mnesia crashed with ~p: in ~p ~p ~n", + [ED, File, Where]); + _ -> %% Every other can crash! + ok + end, + ?verify_mnesia([], [Node]); + Msg -> + ?error("~p ~p: Got ~p during start of Mnesia~n", + [File, Where, Msg]) + end. + +mymnesia_start(Tester) -> + Res = mnesia:start(), + unlink(Tester), + Tester ! {self(), Res}. + +verify_data(_, 0) -> ok; +verify_data(Tab, N) -> + Actual = mnesia:dirty_read({Tab, N}), + Expected = [{Tab, N, N}], + if + Expected == Actual -> + verify_data(Tab, N - 1); + true -> + mnesia:schema(Tab), + {not_equal, node(), Expected, Actual} + end. + +insert_data(_Tab, 0) -> ok; +insert_data(Tab, N) -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N}) end), + insert_data(Tab, N-1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +disc_less(doc) -> + ["Here is a simple test case of a simple recovery of a disc less node. " + "However a lot more test cases involving disc less nodes should " + "be written"]; +disc_less(suite) -> []; +disc_less(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node3, ram_copies)) + end, + Tab1 = disc_less1, + Tab2 = disc_less2, + Tab3 = disc_less3, + Def1 = [{ram_copies, [Node3]}, {disc_copies, [Node1, Node2]}], + Def2 = [{ram_copies, [Node3]}, {disc_copies, [Node1]}], + Def3 = [{ram_copies, [Node3]}, {disc_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + insert_data(Tab1, 100), + insert_data(Tab2, 100), + insert_data(Tab3, 100), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node3]), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node2, mnesia, start, [])), + + timer:sleep(500), + ?match(ok, rpc:call(Node3, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node3, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])), + + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab3, 100])), + + + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab3, 100])), + + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab3, 100])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +system_upgrade(doc) -> + ["Test on-line and off-line upgrade of the Mnesia application"]. + +garb_decision(doc) -> + ["Test that decisions are garbed correctly."]; +garb_decision(suite) -> []; +garb_decision(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(a, [{disc_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(b, [{ram_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(c, [{ram_copies, [Node1, Node3]}, + {disc_copies, [Node2]}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(d, [{disc_copies, [Node1, Node3]}, + {ram_copies, [Node2]}])), + check_garb(Nodes), + + W = fun(Tab) -> mnesia:write({Tab,1,1}) end, + A = fun(Tab) -> mnesia:write({Tab,1,1}), exit(1) end, + + ?match({atomic, ok}, mnesia:transaction(W,[a])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[a])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[b])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[c])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[d])), + check_garb(Nodes), + + rpc:call(Node2, mnesia, lkill, []), + ?match({atomic, ok}, mnesia:transaction(W,[a])), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match([], mnesia_test_lib:start_mnesia([Node2])), + check_garb(Nodes), + timer:sleep(2000), + check_garb(Nodes), + %%%%%% Check transient_decision logs %%%%% + + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + TD0 = mnesia_lib:val(latest_transient_decision), + ?match(0, ets:info(TD0, size)), + {atomic, ok} = mnesia:transaction(W,[a]), + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(TD0, mnesia_lib:val(latest_transient_decision)), + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(false, TD0 =:= mnesia_lib:val(latest_transient_decision)), + ?match(true, lists:member(TD0, mnesia_lib:val(previous_transient_decisions))), + ?verify_mnesia(Nodes, []). + +check_garb(Nodes) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_recover]), + ?match({_, []},rpc:multicall(Nodes, erlang, apply, [fun check_garb/0, []])). + +check_garb() -> + try + Ds = ets:tab2list(mnesia_decision), + Check = fun({trans_tid,serial, _}) -> false; + ({mnesia_down,_,_,_}) -> false; + (_Else) -> true + end, + Node = node(), + ?match({Node, []}, {node(), lists:filter(Check, Ds)}) + catch _:_ -> ok + end, + ok. diff --git a/lib/mnesia/test/mnesia_registry_test.erl b/lib/mnesia/test/mnesia_registry_test.erl new file mode 100644 index 0000000000..2305ef93b7 --- /dev/null +++ b/lib/mnesia/test/mnesia_registry_test.erl @@ -0,0 +1,137 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_registry_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test the mnesia_registry module"]; +all(suite) -> + [ + good_dump, + bad_dump + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +good_dump(doc) -> + ["Dump a faked C-node registry"]; +good_dump(suite) -> []; +good_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + T1 = gordon, + ?match(ok, mnesia_registry:create_table(T1)), + One = {T1, 1, 0, integer, 0, 10}, + Two = {T1, "two", 3, integer, 0, 20}, + Three = {T1, 3, 0, string, 6, "thirty"}, + ?match(ok, mnesia:dirty_write(One)), + ?match(ok, mnesia:dirty_write(Two)), + ?match(ok, mnesia:dirty_write(Three)), + ?match([One], mnesia:dirty_read({T1, 1})), + ?match([_ | _], dump_registry(Node, T1)), + + NewOne = {T1, 1, 0, integer, 0, 1}, + NewFour = {T1, "4", 1, string, 4, "four"}, + + ?match([NewOne], mnesia:dirty_read({T1, 1})), + ?match([Two], mnesia:dirty_read({T1, "two"})), + ?match([], mnesia:dirty_read({T1, 3})), + ?match([NewFour], mnesia:dirty_read({T1, "4"})), + + T2 = blixt, + ?match({'EXIT', {aborted, {no_exists, _}}}, + mnesia:dirty_read({T2, 1})), + ?match([_ |_], dump_registry(Node, T2)), + + NewOne2 = setelement(1, NewOne, T2), + NewFour2 = setelement(1, NewFour, T2), + + ?match([NewOne2], mnesia:dirty_read({T2, 1})), + ?match([], mnesia:dirty_read({T2, "two"})), + ?match([], mnesia:dirty_read({T2, 3})), + ?match([NewFour2], mnesia:dirty_read({T2, "4"})), + ?match([_One2, NewFour2], lists:sort(restore_registry(Node, T2))), + + ?verify_mnesia(Nodes, []). + +dump_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_dump, [Tab, self()]) of + Pid when is_pid(Pid) -> + Pid ! {write, 1, 0, integer, 0, 1}, + Pid ! {delete, 3}, + Pid ! {write, "4", 1, string, 4, "four"}, + Pid ! {commit, self()}, + receive + {ok, Pid} -> + [{Tab, "4", 1, string, 4, "four"}, + {Tab, 1, 0, integer, 0, 1}]; + {'EXIT', Pid, Reason} -> + exit(Reason) + end; + {badrpc, Reason} -> + exit(Reason) + end. + +restore_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_restore, [Tab, self()]) of + {size, Pid, N, _LargestKeySize, _LargestValSize} -> + Pid ! {send_records, self()}, + receive_records(Tab, N); + {badrpc, Reason} -> + exit(Reason) + end. + +receive_records(Tab, N) when N > 0 -> + receive + {restore, KeySize, ValSize, ValType, Key, Val} -> + [{Tab, Key, KeySize, ValType, ValSize, Val} | receive_records(Tab, N -1)]; + {'EXIT', _Pid, Reason} -> + exit(Reason) + end; +receive_records(_Tab, 0) -> + []. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bad_dump(doc) -> + ["Intentionally fail with the dump of a faked C-node registry"]; +bad_dump(suite) -> []; +bad_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + + OldTab = ming, + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({atomic, ok}, mnesia:create_table(OldTab, [{attributes, [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q]}])), + ?match({'EXIT',{aborted,{bad_type,_}}}, dump_registry(Node, OldTab)), + ?match(stopped, mnesia:stop()), + + ?match({'EXIT', {aborted, _}}, mnesia_registry:create_table(down_table)), + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({'EXIT', {aborted, _}}, dump_registry(Node, down_dump)), + + ?verify_mnesia([], Nodes). + diff --git a/lib/mnesia/test/mnesia_schema_recovery_test.erl b/lib/mnesia/test/mnesia_schema_recovery_test.erl new file mode 100644 index 0000000000..387238ae6b --- /dev/null +++ b/lib/mnesia/test/mnesia_schema_recovery_test.erl @@ -0,0 +1,787 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_schema_recovery_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify recoverabiliy of schema transactions.", + " Verify that a schema transaction", + " can be completed when it has been logged correctly and Mnesia", + " crashed before the log has been dumped. Then the transaction ", + " should be handled during the log dump at startup" + ]; +all(suite) -> + [interrupted_before_log_dump, + interrupted_after_log_dump]. + +interrupted_before_log_dump(suite) -> + [interrupted_before_create_ram, + interrupted_before_create_disc, + interrupted_before_create_disc_only, + interrupted_before_create_nostore, + interrupted_before_delete_ram, + interrupted_before_delete_disc, + interrupted_before_delete_disc_only, + interrupted_before_add_ram, + interrupted_before_add_disc, + interrupted_before_add_disc_only, + interrupted_before_add_kill_copier, + interrupted_before_move_ram, + interrupted_before_move_disc, + interrupted_before_move_disc_only, + interrupted_before_move_kill_copier, + interrupted_before_delcopy_ram, + interrupted_before_delcopy_disc, + interrupted_before_delcopy_disc_only, + interrupted_before_delcopy_kill_copier, + interrupted_before_addindex_ram, + interrupted_before_addindex_disc, + interrupted_before_addindex_disc_only, + interrupted_before_delindex_ram, + interrupted_before_delindex_disc, + interrupted_before_delindex_disc_only, + interrupted_before_change_type_ram2disc, + interrupted_before_change_type_ram2disc_only, + interrupted_before_change_type_disc2ram, + interrupted_before_change_type_disc2disc_only, + interrupted_before_change_type_disc_only2ram, + interrupted_before_change_type_disc_only2disc, + interrupted_before_change_type_other_node, + interrupted_before_change_schema_type %% Change schema table copy type!! + ]. + +interrupted_after_log_dump(suite) -> + [interrupted_after_create_ram, + interrupted_after_create_disc, + interrupted_after_create_disc_only, + interrupted_after_create_nostore, + interrupted_after_delete_ram, + interrupted_after_delete_disc, + interrupted_after_delete_disc_only, + interrupted_after_add_ram, + interrupted_after_add_disc, + interrupted_after_add_disc_only, + interrupted_after_add_kill_copier, + interrupted_after_move_ram, + interrupted_after_move_disc, + interrupted_after_move_disc_only, + interrupted_after_move_kill_copier, + interrupted_after_delcopy_ram, + interrupted_after_delcopy_disc, + interrupted_after_delcopy_disc_only, + interrupted_after_delcopy_kill_copier, + interrupted_after_addindex_ram, + interrupted_after_addindex_disc, + interrupted_after_addindex_disc_only, + interrupted_after_delindex_ram, + interrupted_after_delindex_disc, + interrupted_after_delindex_disc_only, + interrupted_after_change_type_ram2disc, + interrupted_after_change_type_ram2disc_only, + interrupted_after_change_type_disc2ram, + interrupted_after_change_type_disc2disc_only, + interrupted_after_change_type_disc_only2ram, + interrupted_after_change_type_disc_only2disc, + interrupted_after_change_type_other_node, + interrupted_after_change_schema_type %% Change schema table copy type!! + +% interrupted_before_change_access_mode, +% interrupted_before_transform, +% interrupted_before_restore, + ]. + +interrupted_before_create_ram(suite) -> []; +interrupted_before_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_before_create_disc(suite) -> []; +interrupted_before_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_before_create_disc_only(suite) -> []; +interrupted_before_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_before_create_nostore(suite) -> []; +interrupted_before_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, one, KillAt). + +interrupted_after_create_ram(suite) -> []; +interrupted_after_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_after_create_disc(suite) -> []; +interrupted_after_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_after_create_disc_only(suite) -> []; +interrupted_after_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_after_create_nostore(suite) -> []; +interrupted_after_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, one, KillAt). + +%%% After dump don't need debug point +interrupted_create(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + ?match({atomic, ok},mnesia:create_table(itrpt, [{Type, Nodes}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_create(Config, Type, Where, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + if %% CREATE TABLE + Where == all -> % tables on both nodes + A ! fun() -> mnesia:create_table(itrpt, [{Type, Nodes}]) end; + true -> % no table on the killed node + A ! fun() -> mnesia:create_table(itrpt, [{Type, [Node1]}]) end + end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [itrpt])), + %% Verify + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delete_ram(suite) -> []; +interrupted_before_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_before_delete_disc(suite) -> []; +interrupted_before_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_before_delete_disc_only(suite) -> []; +interrupted_before_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_after_delete_ram(suite) -> []; +interrupted_after_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_after_delete_disc(suite) -> []; +interrupted_after_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_after_delete_disc_only(suite) -> []; +interrupted_after_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_delete(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:delete_table(Tab) end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + Bad = {badrpc, {'EXIT', {aborted,{no_exists, Tab, all}}}}, + ?match(Bad, rpc:call(Node1, mnesia, table_info, [Tab, all])), + ?match(Bad, rpc:call(Node2, mnesia, table_info, [Tab, all])), + ?verify_mnesia(Nodes, []). + +interrupted_before_add_ram(suite) -> []; +interrupted_before_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc(suite) -> []; +interrupted_before_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc_only(suite) -> []; +interrupted_before_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_add_kill_copier(suite) -> []; +interrupted_before_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_add_ram(suite) -> []; +interrupted_after_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc(suite) -> []; +interrupted_after_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc_only(suite) -> []; +interrupted_after_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_add_kill_copier(suite) -> []; +interrupted_after_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_add(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}, {local_content,true}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, Type)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_add(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + {Node2, Node1} + end, + {success, [A]} = ?start_activities([Kill]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + setup_dbgpoint(KillAt, Kill), + + A ! fun() -> mnesia:add_table_copy(Tab, Node2, Type) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_move_ram(suite) -> []; +interrupted_before_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc(suite) -> []; +interrupted_before_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc_only(suite) -> []; +interrupted_before_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_move_kill_copier(suite) -> []; +interrupted_before_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_move_ram(suite) -> []; +interrupted_after_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc(suite) -> []; +interrupted_after_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc_only(suite) -> []; +interrupted_after_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_move_kill_copier(suite) -> []; +interrupted_after_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_move(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match(ok, mnesia:dirty_write({itrpt, aFter, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})), + ?match([{itrpt, aFter, 1}], mnesia:dirty_read({itrpt, aFter})), + ?verify_mnesia(Nodes, []); +interrupted_move(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + if Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node1, Node2}; + true -> + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:move_table_copy(Tab, Node1, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delcopy_ram(suite) -> []; +interrupted_before_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc(suite) -> []; +interrupted_before_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc_only(suite) -> []; +interrupted_before_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_kill_copier(suite) -> []; +interrupted_before_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_delcopy_ram(suite) -> []; +interrupted_after_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc(suite) -> []; +interrupted_after_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc_only(suite) -> []; +interrupted_after_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_kill_copier(suite) -> []; +interrupted_after_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + + +%%% After dump don't need debug point +interrupted_delcopy(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1,Node2]}])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([Node2], mnesia:table_info(itrpt,Type)), + ?verify_mnesia(Nodes, []); +interrupted_delcopy(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1, Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + if + Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_copy(Tab, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_addindex_ram(suite) -> []; +interrupted_before_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_before_addindex_disc(suite) -> []; +interrupted_before_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_before_addindex_disc_only(suite) -> []; +interrupted_before_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_addindex_ram(suite) -> []; +interrupted_after_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_after_addindex_disc(suite) -> []; +interrupted_after_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_after_addindex_disc_only(suite) -> []; +interrupted_after_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + + +%%% After dump don't need debug point +interrupted_addindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:add_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_index_read(itrpt, 1, val)); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_index_read(itrpt, 1, val)) + end, + ?verify_mnesia(Nodes, []); +interrupted_addindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:add_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + + verify_tab(Node1, Node2), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?verify_mnesia(Nodes, []). + +interrupted_before_delindex_ram(suite) -> []; +interrupted_before_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_before_delindex_disc(suite) -> []; +interrupted_before_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_before_delindex_disc_only(suite) -> []; +interrupted_before_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_delindex_ram(suite) -> []; +interrupted_after_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_after_delindex_disc(suite) -> []; +interrupted_after_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_after_delindex_disc_only(suite) -> []; +interrupted_after_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +%%% After dump don't need debug point +interrupted_delindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes},{index,[val]}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:del_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match({'EXIT',{aborted,{badarg,_}}}, mnesia:dirty_index_read(itrpt, 1, val)), + ?verify_mnesia(Nodes, []); + +interrupted_delindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{index, [val]}, + {Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + verify_tab(Node1, Node2), + ?match({badrpc, _}, rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match({badrpc, _}, rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([], rpc:call(Node1, mnesia, table_info, [Tab, index])), + ?match([], rpc:call(Node2, mnesia, table_info, [Tab, index])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_type_ram2disc(suite) -> []; +interrupted_before_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_ram2disc_only(suite) -> []; +interrupted_before_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc2ram(suite) -> []; +interrupted_before_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc2disc_only(suite) -> []; +interrupted_before_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2ram(suite) -> []; +interrupted_before_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2disc(suite) -> []; +interrupted_before_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_other_node(suite) -> []; +interrupted_before_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_after_change_type_ram2disc(suite) -> []; +interrupted_after_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_ram2disc_only(suite) -> []; +interrupted_after_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc2ram(suite) -> []; +interrupted_after_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc2disc_only(suite) -> []; +interrupted_after_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2ram(suite) -> []; +interrupted_after_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2disc(suite) -> []; +interrupted_after_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_other_node(suite) -> []; +interrupted_after_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_change_type(Config, FromType, ToType, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{FromType, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == changer -> {Node1, Node2}; + true -> {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:change_table_copy_type(Tab, Node2, ToType) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [itrpt])), + verify_tab(Node1, Node2), + ?match(FromType, rpc:call(Node1, mnesia, table_info, [Tab, storage_type])), + ?match(ToType, rpc:call(Node2, mnesia, table_info, [Tab, storage_type])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_schema_type(suite) -> []; +interrupted_before_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_change_schema_type(Config, KillAt). + +interrupted_after_change_schema_type(suite) -> []; +interrupted_after_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_change_schema_type(Config, KillAt). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +interrupted_change_schema_type(Config, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{ram_copies, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + A ! fun() -> mnesia:change_table_copy_type(schema, Node2, ram_copies) end, + kill_at_debug(), + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(ram_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + %% Go back to disc_copies !! + {success, [B]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + B ! fun() -> mnesia:change_table_copy_type(schema, Node2, disc_copies) end, + kill_at_debug(), + + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config). + +%%% Helpers +verify_tab(Node1, Node2) -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, a, a}) end])), + ?match({atomic, ok}, + rpc:call(Node2, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, b, a}) end])), + ?match([{itrpt,a,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,a,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,b,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,b,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,before,1}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, before}])), + ?match([{itrpt,before,1}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, before}])). + +setup_dbgpoint(DbgPoint, Where) -> + Self = self(), + TestFun = fun(_, [InitBy]) -> + case InitBy of + schema_prepare -> + ignore; + schema_begin -> + ignore; + _Other -> + ?deactivate_debug_fun(DbgPoint), + unlink(Self), + Self ! {fun_done, node()}, + timer:sleep(infinity) + end + end, + %% Kill when debug has been reached + ?remote_activate_debug_fun(Where, DbgPoint, TestFun, []). + +kill_at_debug() -> + %% Wait till it's killed + receive + {fun_done, Node} -> + ?match([], mnesia_test_lib:kill_mnesia([Node])) + after + timer:minutes(1) -> ?error("Timeout in kill_at_debug", []) + end. + diff --git a/lib/mnesia/test/mnesia_test_lib.erl b/lib/mnesia/test/mnesia_test_lib.erl new file mode 100644 index 0000000000..1e98f017f7 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.erl @@ -0,0 +1,1058 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Test case support library +%%% +%%% This test suite may be run as a part of the Grand Test Suite +%%% of Erlang. The Mnesia test suite is structured in a hierarchy. +%%% Each test case is implemented as an exported function with arity 1. +%%% Test case identifiers must have the following syntax: {Module, Function}. +%%% +%%% The driver of the test suite runs in two passes as follows: +%%% first the test case function is invoked with the atom 'suite' as +%%% single argument. The returned value is treated as a list of sub +%%% test cases. If the list of sub test cases is [] the test case +%%% function is invoked again, this time with a list of nodes as +%%% argument. If the list of sub test cases is not empty, the test +%%% case driver applies the algorithm recursively on each element +%%% in the list. +%%% +%%% All test cases are written in such a manner +%%% that they start to invoke ?acquire_nodes(X, Config) +%%% in order to prepare the test case execution. When that is +%%% done, the test machinery ensures that at least X number +%%% of nodes are connected to each other. If too few nodes was +%%% specified in the Config, the test case is skipped. If there +%%% was enough node names in the Config, X of them are selected +%%% and if some of them happens to be down they are restarted +%%% via the slave module. When all nodes are up and running a +%%% disk resident schema is created on all nodes and Mnesia is +%%% started a on all nodes. This means that all test cases may +%%% assume that Mnesia is up and running on all acquired nodes. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% doc(TestCases) +%%% +%%% Generates a test spec from parts of the test case structure +%%% +%%% struct(TestCases) +%%% +%%% Prints out the test case structure +%%% +%%% test(TestCases) +%%% +%%% Run parts of the test suite. Uses test/2. +%%% Reads Config from mnesia_test.config and starts them if neccessary. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%% test(TestCases, Config) +%%% +%%% Run parts of the test suite on the given Nodes, +%%% assuming that the nodes are up and running. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_test_lib). +-author('[email protected]'). +-export([ + log/2, + log/4, + verbose/4, + default_config/0, + diskless/1, + eval_test_case/3, + test_driver/2, + test_case_evaluator/3, + activity_evaluator/1, + flush/0, + pick_msg/0, + start_activities/1, + start_transactions/1, + start_transactions/2, + start_sync_transactions/1, + start_sync_transactions/2, + sync_trans_tid_serial/1, + prepare_test_case/5, + select_nodes/4, + init_nodes/3, + error/4, + slave_start_link/0, + slave_start_link/1, + slave_sup/0, + + start_mnesia/1, + start_mnesia/2, + start_appls/2, + start_appls/3, + start_wait/2, + storage_type/2, + stop_mnesia/1, + stop_appls/2, + sort/1, + kill_mnesia/1, + kill_appls/2, + verify_mnesia/4, + shutdown/0, + verify_replica_location/5, + lookup_config/2, + sync_tables/2, + remote_start/3, + remote_stop/1, + remote_kill/1, + + reload_appls/2, + + remote_activate_debug_fun/6, + do_remote_activate_debug_fun/6, + + test/1, + test/2, + doc/1, + struct/1, + init_per_testcase/2, + fin_per_testcase/2, + kill_tc/2 + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% included for test server compatibility +%% assume that all test cases only takes Config as sole argument +init_per_testcase(_Func, Config) -> + global:register_name(mnesia_global_logger, group_leader()), + Config. + +fin_per_testcase(_Func, Config) -> + global:unregister_name(mnesia_global_logger), + %% Nodes = select_nodes(all, Config, ?FILE, ?LINE), + %% rpc:multicall(Nodes, mnesia, lkill, []), + Config. + +%% Use ?log(Format, Args) as wrapper +log(Format, Args, LongFile, Line) -> + File = filename:basename(LongFile), + Format2 = lists:concat([File, "(", Line, ")", ": ", Format]), + log(Format2, Args). + +log(Format, Args) -> + case global:whereis_name(mnesia_global_logger) of + undefined -> + io:format(user, Format, Args); + Pid -> + io:format(Pid, Format, Args) + end. + +verbose(Format, Args, File, Line) -> + Arg = mnesia_test_verbose, + case get(Arg) of + false -> + ok; + true -> + log(Format, Args, File, Line); + undefined -> + case init:get_argument(Arg) of + {ok, List} when is_list(List) -> + case lists:last(List) of + ["true"] -> + put(Arg, true), + log(Format, Args, File, Line); + _ -> + put(Arg, false), + ok + end; + _ -> + put(Arg, false), + ok + end + end. + +-record('REASON', {file, line, desc}). + +error(Format, Args, File, Line) -> + global:send(mnesia_global_logger, {failed, File, Line}), + Fail = #'REASON'{file = filename:basename(File), + line = Line, + desc = Args}, + case global:whereis_name(mnesia_test_case_sup) of + undefined -> + ignore; + Pid -> + Pid ! Fail +%% global:send(mnesia_test_case_sup, Fail), + end, + log("<>ERROR<>~n" ++ Format, Args, File, Line). + +storage_type(Default, Config) -> + case diskless(Config) of + true -> + ram_copies; + false -> + Default + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +default_config() -> + [{nodes, default_nodes()}]. + +default_nodes() -> + mk_nodes(3, []). + +mk_nodes(0, Nodes) -> + Nodes; +mk_nodes(N, []) -> + mk_nodes(N - 1, [node()]); +mk_nodes(N, Nodes) when N > 0 -> + Head = hd(Nodes), + [Name, Host] = node_to_name_and_host(Head), + Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)]. + +mk_node(N, Name, Host) -> + list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])). + +slave_start_link() -> + slave_start_link(node()). + +slave_start_link(Node) -> + [Local, Host] = node_to_name_and_host(Node), + {Mega, Sec, Micro} = erlang:now(), + List = [Local, "_", Mega, "_", Sec, "_", Micro], + Name = list_to_atom(lists:concat(List)), + slave_start_link(list_to_atom(Host), Name). + +slave_start_link(Host, Name) -> + slave_start_link(Host, Name, 10). + +slave_start_link(Host, Name, Retries) -> + Debug = atom_to_list(mnesia:system_info(debug)), + Args = "-mnesia debug " ++ Debug ++ + " -pa " ++ + filename:dirname(code:which(?MODULE)) ++ + " -pa " ++ + filename:dirname(code:which(mnesia)), + case starter(Host, Name, Args) of + {ok, NewNode} -> + ?match(pong, net_adm:ping(NewNode)), + {ok, Cwd} = file:get_cwd(), + Path = code:get_path(), + ok = rpc:call(NewNode, file, set_cwd, [Cwd]), + true = rpc:call(NewNode, code, set_path, [Path]), + spawn_link(NewNode, ?MODULE, slave_sup, []), + rpc:multicall([node() | nodes()], global, sync, []), + {ok, NewNode}; + {error, Reason} when Retries == 0-> + {error, Reason}; + {error, Reason} -> + io:format("Could not start slavenode ~p ~p retrying~n", + [{Host, Name, Args}, Reason]), + timer:sleep(500), + slave_start_link(Host, Name, Retries - 1) + end. + +starter(Host, Name, Args) -> + case os:type() of + vxworks -> + X = test_server:start_node(Name, slave, [{args,Args}]), + timer:sleep(5000), + X; + _ -> + slave:start(Host, Name, Args) + end. + +slave_sup() -> + process_flag(trap_exit, true), + receive + {'EXIT', _, _} -> + case os:type() of + vxworks -> + erlang:halt(); + _ -> + ignore + end + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Index the test case structure + +doc(TestCases) when is_list(TestCases) -> + test(TestCases, suite), + SuiteFname = "index.html", + io:format("Generating HTML test specification to file: ~s~n", + [SuiteFname]), + {ok, Fd} = file:open(SuiteFname, [write]), + io:format(Fd, "<TITLE>Test specification for ~p</TITLE>.~n", [TestCases]), + io:format(Fd, "<H1>Test specification for ~p</H1>~n", [TestCases]), + io:format(Fd, "Test cases which not are implemented yet are written in <B>bold face</B>.~n~n", []), + + io:format(Fd, "<BR><BR>~n", []), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, TestCases, []), + io:format(Fd, "</DL>~n", []), + file:close(Fd); +doc(TestCases) -> + doc([TestCases]). + +do_doc(Fd, [H | T], List) -> + case H of + {Module, TestCase} when is_atom(Module), is_atom(TestCase) -> + do_doc(Fd, Module, TestCase, List); + TestCase when is_atom(TestCase), List == [] -> + do_doc(Fd, mnesia_SUITE, TestCase, List); + TestCase when is_atom(TestCase) -> + do_doc(Fd, hd(List), TestCase, List) + end, + do_doc(Fd, T, List); +do_doc(_, [], _) -> + ok. + +do_doc(Fd, Module, TestCase, List) -> + case get_suite(Module, TestCase) of + [] -> + %% Implemented leaf test case + Head = ?flat_format("<A HREF=~p.html#~p_1>{~p, ~p}</A>}", + [Module, TestCase, Module, TestCase]), + print_doc(Fd, Module, TestCase, Head); + Suite when is_list(Suite) -> + %% Test suite + Head = ?flat_format("{~p, ~p}", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, Suite, [Module | List]), + io:format(Fd, "</DL>~n", []); + 'NYI' -> + %% Not yet implemented + Head = ?flat_format("<B>{~p, ~p}</B>", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head) + end. + +print_doc(Fd, Mod, Fun, Head) -> + case catch (apply(Mod, Fun, [doc])) of + {'EXIT', _} -> + io:format(Fd, "<DT>~s</DT>~n", [Head]); + Doc when is_list(Doc) -> + io:format(Fd, "<DT><U>~s</U><BR><DD>~n", [Head]), + print_rows(Fd, Doc), + io:format(Fd, "</DD><BR><BR>~n", []) + end. + +print_rows(_Fd, []) -> + ok; +print_rows(Fd, [H | T]) when is_list(H) -> + io:format(Fd, "~s~n", [H]), + print_rows(Fd, T); +print_rows(Fd, [H | T]) when is_integer(H) -> + io:format(Fd, "~s~n", [[H | T]]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Show the test case structure + +struct(TestCases) -> + T = test(TestCases, suite), + struct(T, ""). + +struct({Module, TestCase}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ...~n", [Indentation, Module, TestCase]); +struct({Module, TestCase, Other}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ~p~n", [Indentation, Module, TestCase, Other]); +struct([], _) -> + ok; +struct([TestCase | TestCases], Indentation) -> + struct(TestCase, Indentation), + struct(TestCases, Indentation); +struct({TestCase, []}, Indentation) -> + struct(TestCase, Indentation); +struct({TestCase, SubTestCases}, Indentation) when is_list(SubTestCases) -> + struct(TestCase, Indentation), + struct(SubTestCases, Indentation ++ " "). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Execute the test cases + +test(TestCases) -> + test(TestCases, []). + +test(TestCases, suite) when is_list(TestCases) -> + test_driver(TestCases, suite); +test(TestCases, Config) when is_list(TestCases) -> + D1 = lists:duplicate(10, $=), + D2 = lists:duplicate(10, $ ), + log("~n~s TEST CASES: ~p~n ~sCONFIG: ~p~n~n", [D1, TestCases, D2, Config]), + test_driver(TestCases, Config); +test(TestCase, Config) -> + test([TestCase], Config). + +test_driver([], _Config) -> + []; +test_driver([T|TestCases], Config) -> + L1 = test_driver(T, Config), + L2 = test_driver(TestCases, Config), + [L1|L2]; +test_driver({Module, TestCases}, Config) when is_list(TestCases)-> + test_driver(default_module(Module, TestCases), Config); +test_driver({_, {Module, TestCase}}, Config) -> + test_driver({Module, TestCase}, Config); +test_driver({Module, TestCase}, Config) -> + Sec = timer:seconds(1) * 1000, + case get_suite(Module, TestCase) of + [] when Config == suite -> + {Module, TestCase, 'IMPL'}; + [] -> + log("Eval test case: ~w~n", [{Module, TestCase}]), + {T, Res} = + timer:tc(?MODULE, eval_test_case, [Module, TestCase, Config]), + log("Tested ~w in ~w sec~n", [TestCase, T div Sec]), + {T div Sec, Res}; + Suite when is_list(Suite), Config == suite -> + Res = test_driver(default_module(Module, Suite), Config), + {{Module, TestCase}, Res}; + Suite when is_list(Suite) -> + log("Expand test case ~w~n", [{Module, TestCase}]), + Def = default_module(Module, Suite), + {T, Res} = timer:tc(?MODULE, test_driver, [Def, Config]), + {T div Sec, {{Module, TestCase}, Res}}; + 'NYI' when Config == suite -> + {Module, TestCase, 'NYI'}; + 'NYI' -> + log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]), + {0, {skip, {Module, TestCase}, "NYI"}} + end; +test_driver(TestCase, Config) -> + DefaultModule = mnesia_SUITE, + log("<>WARNING<> Missing module in test case identifier. " + "{~w, ~w} assumed~n", [DefaultModule, TestCase]), + test_driver({DefaultModule, TestCase}, Config). + +default_module(DefaultModule, TestCases) when is_list(TestCases) -> + Fun = fun(T) -> + case T of + {_, _} -> true; + T -> {true, {DefaultModule, T}} + end + end, + lists:zf(Fun, TestCases). + +%% Returns a list (possibly empty) or the atom 'NYI' +get_suite(Mod, Fun) -> + case catch (apply(Mod, Fun, [suite])) of + {'EXIT', _} -> 'NYI'; + List when is_list(List) -> List + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +eval_test_case(Mod, Fun, Config) -> + flush(), + global:register_name(mnesia_test_case_sup, self()), + Flag = process_flag(trap_exit, true), + Pid = spawn_link(?MODULE, test_case_evaluator, [Mod, Fun, [Config]]), + R = wait_for_evaluator(Pid, Mod, Fun, Config), + global:unregister_name(mnesia_test_case_sup), + process_flag(trap_exit, Flag), + R. + +flush() -> + receive Msg -> [Msg | flush()] + after 0 -> [] + end. + +wait_for_evaluator(Pid, Mod, Fun, Config) -> + receive + {'EXIT', Pid, {test_case_ok, _PidRes}} -> + Errors = flush(), + Res = + case Errors of + [] -> ok; + Errors -> failed + end, + {Res, {Mod, Fun}, Errors}; + {'EXIT', Pid, {skipped, Reason}} -> + log("<WARNING> Test case ~w skipped, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {skip, {Mod, Fun}, Reason}; + {'EXIT', Pid, Reason} -> + log("<>ERROR<> Eval process ~w exited, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {crash, {Mod, Fun}, Reason} + end. + +test_case_evaluator(Mod, Fun, [Config]) -> + NewConfig = Mod:init_per_testcase(Fun, Config), + R = apply(Mod, Fun, [NewConfig]), + Mod:fin_per_testcase(Fun, NewConfig), + exit({test_case_ok, R}). + +activity_evaluator(Coordinator) -> + activity_evaluator_loop(Coordinator), + exit(normal). + +activity_evaluator_loop(Coordinator) -> + receive + begin_trans -> + transaction(Coordinator, 0); + {begin_trans, MaxRetries} -> + transaction(Coordinator, MaxRetries); + end_trans -> + end_trans; + Fun when is_function(Fun) -> + Coordinator ! {self(), Fun()}, + activity_evaluator_loop(Coordinator); +% {'EXIT', Coordinator, Reason} -> +% Reason; + ExitExpr -> +% ?error("activity_evaluator_loop ~p ~p: exit(~p)~n}", [Coordinator, self(), ExitExpr]), + exit(ExitExpr) + end. + +transaction(Coordinator, MaxRetries) -> + Fun = fun() -> + Coordinator ! {self(), begin_trans}, + activity_evaluator_loop(Coordinator) + end, + Coordinator ! {self(), mnesia:transaction(Fun, MaxRetries)}, + activity_evaluator_loop(Coordinator). + +pick_msg() -> + receive + Message -> Message + after 4000 -> timeout + end. + +start_activities(Nodes) -> + Fun = fun(N) -> spawn_link(N, ?MODULE, activity_evaluator, [self()]) end, + Pids = mapl(Fun, Nodes), + {success, Pids}. + +mapl(Fun, [H|T]) -> + Res = Fun(H), + [Res|mapl(Fun, T)]; +mapl(_Fun, []) -> + []. + +diskless(Config) -> + case lists:keysearch(diskless, 1, Config) of + {value, {diskless, true}} -> + true; + _Else -> + false + end. + + +start_transactions(Pids) -> + Fun = fun(Pid) -> + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + + +start_transactions(Pids, MaxRetries) -> + Fun = fun(Pid) -> + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids, MaxRetries) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +sync_trans_tid_serial(Nodes) -> + Fun = fun() -> mnesia:write_lock_table(schema) end, + rpc:multicall(Nodes, mnesia, transaction, [Fun]). + +select_nodes(N, Config, File, Line) -> + prepare_test_case([], N, Config, File, Line). + +prepare_test_case(Actions, N, Config, File, Line) -> + NodeList1 = lookup_config(nodes, Config), + NodeList2 = lookup_config(nodenames, Config), %% For testserver + NodeList3 = append_unique(NodeList1, NodeList2), + This = node(), + All = [This | lists:delete(This, NodeList3)], + Selected = pick_nodes(N, All, File, Line), + case diskless(Config) of + true -> + ok; + false -> + rpc:multicall(Selected, application, set_env,[mnesia, schema_location, opt_disc]) + end, + do_prepare(Actions, Selected, All, Config, File, Line). + +do_prepare([], Selected, _All, _Config, _File, _Line) -> + Selected; +do_prepare([{init_test_case, Appls} | Actions], Selected, All, Config, File, Line) -> + set_kill_timer(Config), + Started = init_nodes(Selected, File, Line), + All2 = append_unique(Started, All), + Alive = mnesia_lib:intersect(nodes() ++ [node()], All2), + kill_appls(Appls, Alive), + process_flag(trap_exit, true), + do_prepare(Actions, Started, All2, Config, File, Line); +do_prepare([delete_schema | Actions], Selected, All, Config, File, Line) -> + Alive = mnesia_lib:intersect(nodes() ++ [node()], All), + case diskless(Config) of + true -> + skip; + false -> + Del = fun(Node) -> + case mnesia:delete_schema([Node]) of + ok -> ok; + {error, {"All nodes not running",_}} -> + ok; + Else -> + ?log("Delete schema error ~p ~n", [Else]) + end + end, + lists:foreach(Del, Alive) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([create_schema | Actions], Selected, All, Config, File, Line) -> + case diskless(Config) of + true -> + skip; + _Else -> + case mnesia:create_schema(Selected) of + ok -> + ignore; + BadNodes -> + ?fatal("Cannot create Mnesia schema on ~p~n", [BadNodes]) + end + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{start_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + case start_appls(Appls, Selected, Config) of + [] -> ok; + Bad -> ?fatal("Cannot start appls ~p: ~p~n", [Appls, Bad]) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{reload_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + reload_appls(Appls, Selected), + do_prepare(Actions, Selected, All, Config, File, Line). + +set_kill_timer(Config) -> + case init:get_argument(mnesia_test_timeout) of + {ok, _ } -> ok; + _ -> + Time0 = + case lookup_config(tc_timeout, Config) of + [] -> timer:minutes(5); + ConfigTime when is_integer(ConfigTime) -> ConfigTime + end, + Mul = try + test_server:timetrap_scale_factor() + catch _:_ -> 1 end, + (catch test_server:timetrap(Mul*Time0 + 1000)), + spawn_link(?MODULE, kill_tc, [self(),Time0*Mul]) + end. + +kill_tc(Pid, Time) -> + receive + after Time -> + case process_info(Pid) of + undefined -> ok; + _ -> + ?error("Watchdog in test case timed out " + "in ~p min~n", [Time div (1000*60)]), + Files = mnesia_lib:dist_coredump(), + ?log("Cores dumped to:~n ~p~n", [Files]), + %% Genarate erlang crashdumps. + %% GenDump = fun(Node) -> + %% File = "CRASH_" ++ atom_to_list(Node) ++ ".dump", + %% rpc:call(Node, os, putenv, ["ERL_CRASH_DUMP", File]), + %% rpc:cast(Node, erlang, halt, ["RemoteTimeTrap"]) + %% end, + %% [GenDump(Node) || Node <- nodes()], + + %% erlang:halt("DebugTimeTrap"), + exit(Pid, kill) + end + end. + + +append_unique([], List) -> List; +append_unique([H|R], List) -> + case lists:member(H, List) of + true -> append_unique(R, List); + false -> [H | append_unique(R, List)] + end. + +pick_nodes(all, Nodes, File, Line) -> + pick_nodes(length(Nodes), Nodes, File, Line); +pick_nodes(N, [H | T], File, Line) when N > 0 -> + [H | pick_nodes(N - 1, T, File, Line)]; +pick_nodes(0, _Nodes, _File, _Line) -> + []; +pick_nodes(N, [], File, Line) -> + ?skip("Test case (~p(~p)) ignored: ~p nodes missing~n", + [File, Line, N]). + +init_nodes([Node | Nodes], File, Line) -> + case net_adm:ping(Node) of + pong -> + [Node | init_nodes(Nodes, File, Line)]; + pang -> + [Name, Host] = node_to_name_and_host(Node), + case slave_start_link(Host, Name) of + {ok, Node1} -> + Path = code:get_path(), + true = rpc:call(Node1, code, set_path, [Path]), + [Node1 | init_nodes(Nodes, File, Line)]; + Other -> + ?skip("Test case (~p(~p)) ignored: cannot start node ~p: ~p~n", + [File, Line, Node, Other]) + end + end; +init_nodes([], _File, _Line) -> + []. + +%% Returns [Name, Host] +node_to_name_and_host(Node) -> + string:tokens(atom_to_list(Node), [$@]). + +lookup_config(Key,Config) -> + case lists:keysearch(Key,1,Config) of + {value,{Key,Val}} -> + Val; + _ -> + [] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_appls(Appls, Nodes) -> + start_appls(Appls, Nodes, [], [schema]). + +start_appls(Appls, Nodes, Config) -> + start_appls(Appls, Nodes, Config, [schema]). + +start_appls([Appl | Appls], Nodes, Config, Tabs) -> + {Started, BadStarters} = + rpc:multicall(Nodes, ?MODULE, remote_start, [Appl, Config, Nodes]), + BadS = [{Node, Appl, Res} || {Node, Res} <- Started, Res /= ok], + BadN = [{BadNode, Appl, bad_start} || BadNode <- BadStarters], + Bad = BadS ++ BadN, + case Appl of + mnesia when Bad == [] -> + sync_tables(Nodes, Tabs); + _ -> + ignore + end, + Bad ++ start_appls(Appls, Nodes, Config, Tabs); +start_appls([], _Nodes, _Config, _Tabs) -> + []. + +remote_start(mnesia, Config, Nodes) -> + case diskless(Config) of + true -> + application_controller:set_env(mnesia, + extra_db_nodes, + Nodes -- [node()]), + application_controller:set_env(mnesia, + schema_location, + ram); + false -> + application_controller:set_env(mnesia, + schema_location, + opt_disc), + ignore + end, + {node(), mnesia:start()}; +remote_start(Appl, _Config, _Nodes) -> + Res = + case application:start(Appl) of + {error, {already_started, Appl}} -> + ok; + Other -> + Other + end, + {node(), Res}. + +%% Start Mnesia on all given nodes and wait for specified +%% tables to be accessible on each node. The atom all means +%% that we should wait for all tables to be loaded +%% +%% Returns a list of error tuples {BadNode, mnesia, Reason} +start_mnesia(Nodes) -> + start_appls([mnesia], Nodes). +start_mnesia(Nodes, Tabs) when is_list(Nodes) -> + start_appls([mnesia], Nodes, [], Tabs). + +%% Wait for the tables to be accessible from all nodes in the list +%% and that all nodes are aware of that the other nodes also ... +sync_tables(Nodes, Tabs) -> + Res = send_wait(Nodes, Tabs, []), + if + Res == [] -> + mnesia:transaction(fun() -> mnesia:write_lock_table(schema) end), + Res; + true -> + Res + end. + +send_wait([Node | Nodes], Tabs, Pids) -> + Pid = spawn_link(Node, ?MODULE, start_wait, [self(), Tabs]), + send_wait(Nodes, Tabs, [Pid | Pids]); +send_wait([], _Tabs, Pids) -> + rec_wait(Pids, []). + +rec_wait([Pid | Pids], BadRes) -> + receive + {'EXIT', Pid, R} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]); + {Pid, ok} -> + rec_wait(Pids, BadRes); + {Pid, {error, R}} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]) + end; +rec_wait([], BadRes) -> + BadRes. + +start_wait(Coord, Tabs) -> + process_flag(trap_exit, true), + Mon = whereis(mnesia_monitor), + case catch link(Mon) of + {'EXIT', _} -> + unlink(Coord), + Coord ! {self(), {error, {node_not_running, node()}}}; + _ -> + Res = start_wait_loop(Tabs), + unlink(Mon), + unlink(Coord), + Coord ! {self(), Res} + end. + +start_wait_loop(Tabs) -> + receive + {'EXIT', Pid, Reason} -> + {error, {start_wait, Pid, Reason}} + after 0 -> + case mnesia:wait_for_tables(Tabs, timer:seconds(30)) of + ok -> + verify_nodes(Tabs); + {timeout, BadTabs} -> + log("<>WARNING<> Wait for tables ~p: ~p~n", [node(), Tabs]), + start_wait_loop(BadTabs); + {error, Reason} -> + {error, {start_wait, Reason}} + end + end. + +verify_nodes(Tabs) -> + verify_nodes(Tabs, 0). + +verify_nodes([], _) -> + ok; + +verify_nodes([Tab| Tabs], N) -> + ?match(X when is_atom(X), mnesia_lib:val({Tab, where_to_read})), + Nodes = mnesia:table_info(Tab, where_to_write), + Copies = + mnesia:table_info(Tab, disc_copies) ++ + mnesia:table_info(Tab, disc_only_copies) ++ + mnesia:table_info(Tab, ram_copies), + Local = mnesia:table_info(Tab, local_content), + case Copies -- Nodes of + [] -> + verify_nodes(Tabs, 0); + _Else when Local == true, Nodes /= [] -> + verify_nodes(Tabs, 0); + Else -> + N2 = + if + N > 20 -> + log("<>WARNING<> ~w Waiting for table: ~p on ~p ~n", + [node(), Tab, Else]), + 0; + true -> N+1 + end, + timer:sleep(500), + verify_nodes([Tab| Tabs], N2) + end. + + +%% Nicely stop Mnesia on all given nodes +%% +%% Returns a list of error tuples {BadNode, Reason} +stop_mnesia(Nodes) when is_list(Nodes) -> + stop_appls([mnesia], Nodes). + +stop_appls([Appl | Appls], Nodes) when is_list(Nodes) -> + {Stopped, BadNodes} = rpc:multicall(Nodes, ?MODULE, remote_stop, [Appl]), + BadS =[{Node, Appl, Res} || {Node, Res} <- Stopped, Res /= stopped], + BadN =[{BadNode, Appl, bad_node} || BadNode <- BadNodes], + BadS ++ BadN ++ stop_appls(Appls, Nodes); +stop_appls([], _Nodes) -> + []. + +remote_stop(mnesia) -> + {node(), mnesia:stop()}; +remote_stop(Appl) -> + {node(), application:stop(Appl)}. + +remote_kill([Appl | Appls]) -> + catch Appl:lkill(), + application:stop(Appl), + remote_kill(Appls); +remote_kill([]) -> + ok. + +%% Abruptly kill Mnesia on all given nodes +%% Returns [] +kill_appls(Appls, Nodes) when is_list(Nodes) -> + verbose("<>WARNING<> Intentionally killing ~p: ~w...~n", + [Appls, Nodes], ?FILE, ?LINE), + rpc:multicall(Nodes, ?MODULE, remote_kill, [Appls]), + []. + +kill_mnesia(Nodes) when is_list(Nodes) -> + kill_appls([mnesia], Nodes). + +reload_appls([Appl | Appls], Selected) -> + kill_appls([Appl], Selected), + timer:sleep(1000), + Ok = {[ok || _N <- Selected], []}, + {Ok2temp, Empty} = rpc:multicall(Selected, application, unload, [Appl]), + Conv = fun({error,{not_loaded,mnesia}}) -> ok; (Else) -> Else end, + Ok2 = {lists:map(Conv, Ok2temp), Empty}, + + Ok3 = rpc:multicall(Selected, application, load, [Appl]), + if + Ok /= Ok2 -> + ?fatal("Cannot unload appl ~p: ~p~n", [Appl, Ok2]); + Ok /= Ok3 -> + ?fatal("Cannot load appl ~p: ~p~n", [Appl, Ok3]); + true -> + ok + end, + reload_appls(Appls, Selected); +reload_appls([], _Selected) -> + ok. + +shutdown() -> + log("<>WARNING<> Intentionally shutting down all nodes... ~p~n", + [nodes() ++ [node()]]), + rpc:multicall(nodes(), erlang, halt, []), + erlang:halt(). + +verify_mnesia(Ups, Downs, File, Line) when is_list(Ups), is_list(Downs) -> + BadUps = + [N || N <- Ups, rpc:call(N, mnesia, system_info, [is_running]) /= yes], + BadDowns = + [N || N <- Downs, rpc:call(N, mnesia, system_info, [is_running]) == yes], + if + BadUps == [] -> + ignore; + true -> + error("Mnesia is not running as expected: ~p~n", + [BadUps], File, Line) + end, + if + BadDowns == [] -> + ignore; + true -> + error("Mnesia is not stopped as expected: ~p~n", + [BadDowns], File, Line) + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +verify_replica_location(Tab, [], [], [], _) -> + ?match({'EXIT', _}, mnesia:table_info(Tab, ram_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_only_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_write)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_read)), + []; + +verify_replica_location(Tab, DiscOnly0, Ram0, Disc0, AliveNodes0) -> +%% sync_tables(AliveNodes0, [Tab]), + AliveNodes = lists:sort(AliveNodes0), + DiscOnly = lists:sort(DiscOnly0), + Ram = lists:sort(Ram0), + Disc = lists:sort(Disc0), + Write = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + Read = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + This = node(), + + timer:sleep(100), + + S1 = ?match(AliveNodes, lists:sort(mnesia:system_info(running_db_nodes))), + S2 = ?match(DiscOnly, lists:sort(mnesia:table_info(Tab, disc_only_copies))), + S3 = ?match(Ram, lists:sort(mnesia:table_info(Tab, ram_copies))), + S4 = ?match(Disc, lists:sort(mnesia:table_info(Tab, disc_copies))), + S5 = ?match(Write, lists:sort(mnesia:table_info(Tab, where_to_write))), + S6 = case lists:member(This, Read) of + true -> + ?match(This, mnesia:table_info(Tab, where_to_read)); + false -> + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)) + end, + lists:filter(fun({success,_}) -> false; (_) -> true end, [S1,S2,S3,S4,S5,S6]). + +ignore_dead(Nodes, AliveNodes) -> + Filter = fun(Node) -> lists:member(Node, AliveNodes) end, + lists:sort(lists:zf(Filter, Nodes)). + + +remote_activate_debug_fun(N, I, F, C, File, Line) -> + Pid = spawn_link(N, ?MODULE, do_remote_activate_debug_fun, [self(), I, F, C, File, Line]), + receive + {activated, Pid} -> ok; + {'EXIT', Pid, Reason} -> {error, Reason} + end. + +do_remote_activate_debug_fun(From, I, F, C, File, Line) -> + mnesia_lib:activate_debug_fun(I, F, C, File, Line), + From ! {activated, self()}, + timer:sleep(infinity). % Dies whenever the test process dies !! + + +sort(L) when is_list(L) -> + lists:sort(L); +sort({atomic, L}) when is_list(L) -> + {atomic, lists:sort(L)}; +sort({ok, L}) when is_list(L) -> + {ok, lists:sort(L)}; +sort(W) -> + W. diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl new file mode 100644 index 0000000000..85f12200d4 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.hrl @@ -0,0 +1,132 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-define(log(Format,Args),mnesia_test_lib:log(Format,Args,?FILE,?LINE)). +-define(warning(Format,Args),?log("<>WARNING<>~n " ++ Format,Args)). +-define(error(Format,Args), + mnesia_test_lib:error(Format,Args,?FILE,?LINE)). +-define(verbose(Format,Args),mnesia_test_lib:verbose(Format,Args,?FILE,?LINE)). + +-define(fatal(Format,Args), + ?error(Format, Args), + exit({test_case_fatal, Format, Args, ?FILE, ?LINE})). + +-define(skip(Format,Args), + ?warning(Format, Args), + exit({skipped, ?flat_format(Format, Args)})). + +-define(flat_format(Format,Args), + lists:flatten(io_lib:format(Format, Args))). + +-define(sort(What), mnesia_test_lib:sort(What)). + +-define(ignore(Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + AcTuAlReS + end()). + +-define(match(ExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + ExpectedRes -> + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + {success,AcTuAlReS}; + _ -> + ?error("Not Matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS} + end + end()). + +-define(match_inverse(NotExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + NotExpectedRes -> + ?error("Not matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS}; + _ -> + ?verbose("ok, ~n Result as expected: ~p~n",[AcTuAlReS]), + {success,AcTuAlReS} + end + end()). + +-define(match_receive(ExpectedMsg), + ?match(ExpectedMsg,mnesia_test_lib:pick_msg())). + +%% ExpectedMsgs must be completely bound +-define(match_multi_receive(ExpectedMsgs), + fun() -> + TmPeXpCtEdMsGs = lists:sort(ExpectedMsgs), + ?match(TmPeXpCtEdMsGs, + lists:sort(lists:map(fun(_) -> + mnesia_test_lib:pick_msg() + end, + TmPeXpCtEdMsGs))) + end()). + +-define(start_activities(Nodes), + mnesia_test_lib:start_activities(Nodes)). + +-define(start_transactions(Pids), + mnesia_test_lib:start_transactions(Pids)). + +-define(acquire_nodes(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +-define(activate_debug_fun(I, F, C), + mnesia_lib:activate_debug_fun(I, F, C, ?FILE, ?LINE)). + +-define(remote_activate_debug_fun(N, I, F, C), + ?match(ok, mnesia_test_lib:remote_activate_debug_fun(N, I, F, C, + ?FILE, ?LINE))). + +-define(deactivate_debug_fun(I), + mnesia_lib:deactivate_debug_fun(I, ?FILE, ?LINE)). + +-define(remote_deactivate_debug_fun(N, I), + rpc:call(N, mnesia_lib, deactivate_debug_fun, [I, ?FILE, ?LINE])). + +-define(is_debug_compiled, + case mnesia_lib:is_debug_compiled() of + false -> + ?skip("Mnesia is not debug compiled, test case ignored.~n", []); + _OhTeR -> + ok + end). + +-define(needs_disc(Config), + case mnesia_test_lib:diskless(Config) of + false -> + ok; + true -> + ?skip("Must have disc, test case ignored.~n", []) + end). + +-define(verify_mnesia(Ups, Downs), + mnesia_test_lib:verify_mnesia(Ups, Downs, ?FILE, ?LINE)). diff --git a/lib/mnesia/test/mnesia_tpcb.erl b/lib/mnesia/test/mnesia_tpcb.erl new file mode 100644 index 0000000000..903c53a21c --- /dev/null +++ b/lib/mnesia/test/mnesia_tpcb.erl @@ -0,0 +1,1268 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% MODULE +%% +%% mnesia_tpcb - TPC-B benchmarking of Mnesia +%% +%% DESCRIPTION +%% +%% The metrics used in the TPC-B benchmark are throughput as measured +%% in transactions per second (TPS). The benchmark uses a single, +%% simple update-intensive transaction to load the database system. +%% The single transaction type provides a simple, repeatable +%% unit of work, and is designed to exercise the basic components of +%% a database system. +%% +%% The definition of the TPC-B states lots of detailed rules and +%% conditions that must be fullfilled, e.g. how the ACID (atomicity, +%% consistency, isolation and durability) properties are verified, +%% how the random numbers must be distributed, minimum sizes of +%% the different types of records, minimum duration of the benchmark, +%% formulas to calculate prices (dollars per tps), disclosure issues +%% etc. Please, see http://www.tpc.org/ about the nitty gritty details. +%% +%% The TPC-B benchmark is stated in terms of a hypothetical bank. The +%% bank has one or more branches. Each branch has multiple tellers. The +%% bank has many customers, each with an account. The database represents +%% the cash position of each entity (branch, teller and account) and a +%% history of recent transactions run by the bank. The transaction +%% represents the work done when a customer makes a deposit or a +%% withdrawal against his account. The transaction is performed by a +%% teller at some branch. +%% +%% Each process that performs TPC-B transactions is called a driver. +%% Drivers generates teller_id, account_id and delta amount of +%% money randomly. An account, a teller and a branch are read, their +%% balances are adjusted and a history record is created. The driver +%% measures the time for 3 reads, 3 writes and 1 create. +%% +%% GETTING STARTED +%% +%% Generate tables and run with default configuration: +%% +%% mnesia_tpcb:start(). +%% +%% A little bit more advanced; +%% +%% spawn(mnesia_tpcb, start, [[[{n_drivers_per_node, 8}, {stop_after, infinity}]]), +%% mnesia_tpcb:stop(). +%% +%% Really advanced; +%% +%% mnesia_tpcb:init(([{n_branches, 8}, {replica_type, disc_only_copies}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 8}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 64}]). +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_tpcb). +-author('[email protected]'). + +-export([ + config/2, + count_balance/0, + driver_init/2, + init/1, + reporter_init/2, + run/1, + start/0, + start/1, + start/2, + stop/0, + real_trans/5, + verify_tabs/0, + reply_gen_branch/3, + frag_add_delta/7, + + conflict_test/1, + dist_test/1, + replica_test/1, + sticky_replica_test/1, + remote_test/1, + remote_frag2_test/1 + ]). + +-define(SECOND, 1000000). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, % Unique account id + branch_id = 0, % Branch where the account is held + balance = 0, % Account balance + filler = ?ACCOUNT_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + +-record(branch, + { + id = 0, % Unique branch id + balance = 0, % Total balance of whole branch + filler = ?BRANCH_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + +-record(teller, + { + id = 0, % Unique teller id + branch_id = 0, % Branch where the teller is located + balance = 0, % Teller balance + filler = ?TELLER_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +-record(history, + { + history_id = {0, 0}, % {DriverId, DriverLocalHistoryid} + time_stamp = now(), % Time point during active transaction + branch_id = 0, % Branch associated with teller + teller_id = 0, % Teller invlolved in transaction + account_id = 0, % Account updated by transaction + amount = 0, % Amount (delta) specified by transaction + filler = ?HISTORY_FILLER % Gap filler to ensure size >= 50 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + n_replicas = 1, % Ignored for non-fragmented tables + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_fragments = 0, + n_branches = 1, + n_tellers_per_branch = 10, % Must be 10 + n_accounts_per_branch = 100000, % Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(run_config, + { + driver_nodes = [node()], + n_drivers_per_node = 1, + use_running_mnesia = false, + stop_after = timer:minutes(15), % Minimum 15 min + report_interval = timer:minutes(1), + use_sticky_locks = false, + spawn_near_branch = false, + activity_type = transaction, + reuse_history_id = false + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(time, + { + n_trans = 0, + min_n = 0, + max_n = 0, + acc_time = 0, + max_time = 0 + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(driver_state, + { + driver_id, + driver_node, + seed, + n_local_branches, + local_branches, + tab_config, + run_config, + history_id, + time = #time{}, + acc_time = #time{}, + reuse_history_id + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(reporter_state, + { + driver_pids, + starter_pid, + n_iters = 0, + prev_tps = 0, + curr = #time{}, + acc = #time{}, + init_micros, + prev_micros, + run_config + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table not replicated + +config(frag_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table replicated to two nodes. + +config(frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {n_replicas, 2}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(sticky_replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [node()]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {use_sticky_locks, true}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, lots of branches + +config(dist_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 10 * length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, single branch + +config(conflict_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all other nodes. + +config(remote_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Remote}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to two other nodes. + +config(remote_frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Remote)}, + {n_fragments, length(Remote)}, + {n_replicas, 2}, + {replica_nodes, Remote}, + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start(What, ReplicaType) -> + spawn_link(?MODULE, start, [config(What, ReplicaType)]). + +replica_test(ReplicaType) -> + start(replica_test, ReplicaType). + +sticky_replica_test(ReplicaType) -> + start(sticky_replica_test, ReplicaType). + +dist_test(ReplicaType) -> + start(dist_test, ReplicaType). + +conflict_test(ReplicaType) -> + start(conflict_test, ReplicaType). + +remote_test(ReplicaType) -> + start(remote_test, ReplicaType). + +remote_frag2_test(ReplicaType) -> + start(remote_frag2_test, ReplicaType). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in either the record tab_config or run_config. Unknown keys are ignored. + +start() -> + start([]). +start(Args) -> + init(Args), + run(Args). + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +stop() -> + case whereis(mnesia_tpcb) of + undefined -> + {error, not_running}; + Pid -> + sync_stop(Pid) + end. + +sync_stop(Pid) -> + Pid ! {self(), stop}, + receive + {Pid, {stopped, Res}} -> Res + after timer:minutes(1) -> + exit(Pid, kill), + {error, brutal_kill} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Initialization + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record tab_config, unknown keys are ignored. + +init(Args) -> + TabConfig0 = list2rec(Args, record_info(fields, tab_config), #tab_config{}), + TabConfig = + if + TabConfig0#tab_config.n_fragments =:= 0 -> + TabConfig0#tab_config{n_replicas = length(TabConfig0#tab_config.replica_nodes)}; + true -> + TabConfig0 + end, + Tags = record_info(fields, tab_config), + Fun = fun(F, Pos) -> {{F, element(Pos, TabConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Table config: ~p ~n", [List]), + + DbNodes = TabConfig#tab_config.db_nodes, + stop(), + if + TabConfig#tab_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []), + case mnesia:delete_schema(DbNodes) of + ok -> + case mnesia:create_schema(DbNodes) of + ok -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~p: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to create schema on disc: ~p~n", + [Reason]), + exit({create_schema_failed, Reason}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to delete schema on disc: ~p~n", + [Reason]), + exit({delete_schema_failed, Reason}) + end + end, + gen_tabs(TabConfig). + +gen_tabs(TC) -> + create_tab(TC, branch, record_info(fields, branch), + undefined), + create_tab(TC, account, record_info(fields, account), + {branch, #account.branch_id}), + create_tab(TC, teller, record_info(fields, teller), + {branch, #teller.branch_id}), + create_tab(TC, history, record_info(fields, history), + {branch, #history.branch_id}), + + NB = TC#tab_config.n_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + io:format("TPC-B: Generating ~p branches a ~p bytes~n", + [NB, size(term_to_binary(default_branch(TC)))]), + io:format("TPC-B: Generating ~p * ~p tellers a ~p bytes~n", + [NB, NT, size(term_to_binary(default_teller(TC)))]), + io:format("TPC-B: Generating ~p * ~p accounts a ~p bytes~n", + [NB, NA, size(term_to_binary(default_account(TC)))]), + io:format("TPC-B: Generating 0 history records a ~p bytes~n", + [size(term_to_binary(default_history(TC)))]), + gen_branches(TC), + + case verify_tabs() of + ok -> + ignore; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables: ~w~n", + [Reason]), + exit({inconsistent_tables, Reason}) + end. + +create_tab(TC, Name, Attrs, _ForeignKey) when TC#tab_config.n_fragments =:= 0 -> + Nodes = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + Def = [{Type, Nodes}, {attributes, Attrs}], + create_tab(Name, Def); +create_tab(TC, Name, Attrs, ForeignKey) -> + NReplicas = TC#tab_config.n_replicas, + NodePool = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + NF = TC#tab_config.n_fragments, + Props = [{n_fragments, NF}, + {node_pool, NodePool}, + {n_copies(Type), NReplicas}, + {foreign_key, ForeignKey}], + Def = [{frag_properties, Props}, + {attributes, Attrs}], + create_tab(Name, Def). + +create_tab(Name, Def) -> + mnesia:delete_table(Name), + case mnesia:create_table(Name, Def) of + {atomic, ok} -> + ok; + {aborted, Reason} -> + io:format("TPC-B: <ERROR> failed to create table ~w ~w: ~p~n", + [Name, Def, Reason]), + exit({create_table_failed, Reason}) + end. + +n_copies(Type) -> + case Type of + ram_copies -> n_ram_copies; + disc_copies -> n_disc_copies; + disc_only_copies -> n_disc_only_copies + end. + +gen_branches(TC) -> + First = 0, + Last = First + TC#tab_config.n_branches - 1, + GenPids = gen_branches(TC, First, Last, []), + wait_for_gen(GenPids). + +wait_for_gen([]) -> + ok; +wait_for_gen(Pids) -> + receive + {branch_generated, Pid} -> wait_for_gen(lists:delete(Pid, Pids)); + Exit -> + exit({tpcb_failed, Exit}) + end. + +gen_branches(TC, BranchId, Last, UsedNs) when BranchId =< Last -> + UsedNs2 = get_branch_nodes(BranchId, UsedNs), + Node = hd(UsedNs2), + Pid = spawn_link(Node, ?MODULE, reply_gen_branch, + [self(), TC, BranchId]), + [Pid | gen_branches(TC, BranchId + 1, Last, UsedNs2)]; +gen_branches(_, _, _, _) -> + []. + +reply_gen_branch(ReplyTo, TC, BranchId) -> + gen_branch(TC, BranchId), + ReplyTo ! {branch_generated, self()}, + unlink(ReplyTo). + +%% Returns a new list of nodes with the best node as head +get_branch_nodes(BranchId, UsedNs) -> + WriteNs = table_info({branch, BranchId}, where_to_write), + WeightedNs = [{n_duplicates(N, UsedNs, 0), N} || N <- WriteNs], + [{_, LeastUsed} | _ ] = lists:sort(WeightedNs), + [LeastUsed | UsedNs]. + +n_duplicates(_N, [], Count) -> + Count; +n_duplicates(N, [N | Tail], Count) -> + n_duplicates(N, Tail, Count + 1); +n_duplicates(N, [_ | Tail], Count) -> + n_duplicates(N, Tail, Count). + +gen_branch(TC, BranchId) -> + A = default_account(TC), + NA = TC#tab_config.n_accounts_per_branch, + FirstA = BranchId * NA, + ArgsA = [FirstA, FirstA + NA - 1, BranchId, A], + ok = mnesia:activity(async_dirty, fun gen_accounts/4, ArgsA, mnesia_frag), + + T = default_teller(TC), + NT = TC#tab_config.n_tellers_per_branch, + FirstT = BranchId * NT, + ArgsT = [FirstT, FirstT + NT - 1, BranchId, T], + ok = mnesia:activity(async_dirty, fun gen_tellers/4, ArgsT, mnesia_frag), + + B = default_branch(TC), + FunB = fun() -> mnesia:write(branch, B#branch{id = BranchId}, write) end, + ok = mnesia:activity(sync_dirty, FunB, [], mnesia_frag). + +gen_tellers(Id, Last, BranchId, T) when Id =< Last -> + mnesia:write(teller, T#teller{id = Id, branch_id=BranchId}, write), + gen_tellers(Id + 1, Last, BranchId, T); +gen_tellers(_, _, _, _) -> + ok. + +gen_accounts(Id, Last, BranchId, A) when Id =< Last -> + mnesia:write(account, A#account{id = Id, branch_id=BranchId}, write), + gen_accounts(Id + 1, Last, BranchId, A); +gen_accounts(_, _, _, _) -> + ok. + +default_branch(TC) -> #branch{filler = TC#tab_config.branch_filler}. +default_teller(TC) -> #teller{filler = TC#tab_config.teller_filler}. +default_account(TC) -> #account{filler = TC#tab_config.account_filler}. +default_history(_TC) -> #history{}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Run the benchmark + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record run_config, unknown keys are ignored. +run(Args) -> + RunConfig = list2rec(Args, record_info(fields, run_config), #run_config{}), + Tags = record_info(fields, run_config), + Fun = fun(F, Pos) -> {{F, element(Pos, RunConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Run config: ~p ~n", [List]), + + Pid = spawn_link(?MODULE, reporter_init, [self(), RunConfig]), + receive + {Pid, {stopped, Res}} -> + Res; % Stopped by other process + Else -> + {tpcb_got, Else} + after RunConfig#run_config.stop_after -> + sync_stop(Pid) + end. + +reporter_init(Starter, RC) -> + register(mnesia_tpcb, self()), + process_flag(trap_exit, true), + DbNodes = mnesia:system_info(db_nodes), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~w: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end, + verify_tabs() + end, + + N = table_info(branch, size), + NT = table_info(teller, size) div N, + NA = table_info(account, size) div N, + + {Type, NF, RepNodes} = table_storage(branch), + TC = #tab_config{n_fragments = NF, + n_branches = N, + n_tellers_per_branch = NT, + n_accounts_per_branch = NA, + db_nodes = DbNodes, + replica_nodes = RepNodes, + replica_type = Type + }, + Drivers = start_drivers(RC, TC), + Now = now_to_micros(erlang:now()), + State = #reporter_state{driver_pids = Drivers, + run_config = RC, + starter_pid = Starter, + init_micros = Now, + prev_micros = Now + }, + case catch reporter_loop(State) of + {'EXIT', Reason} -> + io:format("TPC-B: Abnormal termination: ~p~n", [Reason]), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, {error, Reason}}}, % To be sure + exit(shutdown); + {ok, Stopper, State2} -> + Time = State2#reporter_state.acc, + Res = + case verify_tabs() of + ok -> + {ok, Time}; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables, ~p~n", + [{error, Reason}]), + {error, Reason} + end, + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, stop, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, Res}}, + if + Stopper =/= Starter -> + Stopper ! {self(), {stopped, Res}}; + true -> + ignore + end, + exit(shutdown) + end. + +table_info(Tab, Item) -> + Fun = fun() -> mnesia:table_info(Tab, Item) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +%% Returns {Storage, NFragments, ReplicaNodes} +table_storage(Tab) -> + case mnesia:table_info(branch, frag_properties) of + [] -> + NFO = 0, + NR = length(mnesia:table_info(Tab, ram_copies)), + ND = length(mnesia:table_info(Tab, disc_copies)), + NDO = length(mnesia:table_info(Tab, disc_only_copies)), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end; + Props -> + {value, NFO} = lists:keysearch(n_fragments, 1, Props), + NR = table_info(Tab, n_ram_copies), + ND = table_info(Tab, n_disc_copies), + NDO = table_info(Tab, n_disc_only_copies), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end + end. + +reporter_loop(State) -> + RC = State#reporter_state.run_config, + receive + {From, stop} -> + {ok, From, call_drivers(State, stop)}; + {'EXIT', Pid, Reason} when Pid =:= State#reporter_state.starter_pid -> + %% call_drivers(State, stop), + exit({starter_died, Pid, Reason}) + after RC#run_config.report_interval -> + Iters = State#reporter_state.n_iters, + State2 = State#reporter_state{n_iters = Iters + 1}, + case call_drivers(State2, report) of + State3 when State3#reporter_state.driver_pids =/= [] -> + State4 = State3#reporter_state{curr = #time{}}, + reporter_loop(State4); + _ -> + exit(drivers_died) + end + end. + +call_drivers(State, Msg) -> + Drivers = State#reporter_state.driver_pids, + lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Drivers), + State2 = show_report(calc_reports(Drivers, State)), + case Msg =:= stop of + true -> + Acc = State2#reporter_state.acc, + Init = State2#reporter_state.init_micros, + show_report(State2#reporter_state{n_iters = 0, + curr = Acc, + prev_micros = Init}); + false -> + ignore + end, + State2. + +calc_reports([], State) -> + State; +calc_reports([Pid|Drivers], State) -> + receive + {'EXIT', P, Reason} when P =:= State#reporter_state.starter_pid -> + exit({starter_died, P, Reason}); + {'EXIT', Pid, Reason} -> + exit({driver_died, Pid, Reason}); + {Pid, Time} when is_record(Time, time) -> + %% io:format("~w: ~w~n", [Pid, Time]), + A = add_time(State#reporter_state.acc, Time), + C = add_time(State#reporter_state.curr, Time), + State2 = State#reporter_state{acc = A, curr = C}, + calc_reports(Drivers, State2) + end. + +add_time(Acc, New) -> + Acc#time{n_trans = New#time.n_trans + Acc#time.n_trans, + min_n = lists:min([New#time.n_trans, Acc#time.min_n] -- [0]), + max_n = lists:max([New#time.n_trans, Acc#time.max_n]), + acc_time = New#time.acc_time + Acc#time.acc_time, + max_time = lists:max([New#time.max_time, Acc#time.max_time])}. + +-define(AVOID_DIV_ZERO(_What_), try (_What_) catch _:_ -> 0 end). + +show_report(State) -> + Now = now_to_micros(erlang:now()), + Iters = State#reporter_state.n_iters, + Time = State#reporter_state.curr, + Max = Time#time.max_time, + N = Time#time.n_trans, + Avg = ?AVOID_DIV_ZERO(Time#time.acc_time div N), + AliveN = length(State#reporter_state.driver_pids), + Tps = ?AVOID_DIV_ZERO((?SECOND * AliveN) div Avg), + PrevTps= State#reporter_state.prev_tps, + {DiffSign, DiffTps} = signed_diff(Iters, Tps, PrevTps), + Unfairness = ?AVOID_DIV_ZERO(Time#time.max_n / Time#time.min_n), + BruttoAvg = ?AVOID_DIV_ZERO((Now - State#reporter_state.prev_micros) div N), +%% io:format("n_iters=~p, n_trans=~p, n_drivers=~p, avg=~p, now=~p, prev=~p~n", +%% [Iters, N, AliveN, BruttoAvg, Now, State#reporter_state.prev_micros]), + BruttoTps = ?AVOID_DIV_ZERO(?SECOND div BruttoAvg), + case Iters > 0 of + true -> + io:format("TPC-B: ~p iter ~s~p diff ~p (~p) tps ~p avg micros ~p max micros ~p unfairness~n", + [Iters, DiffSign, DiffTps, Tps, BruttoTps, Avg, Max, Unfairness]); + false -> + io:format("TPC-B: ~p (~p) transactions per second, " + "duration of longest transaction was ~p milliseconds~n", + [Tps, BruttoTps, Max div 1000]) + end, + State#reporter_state{prev_tps = Tps, prev_micros = Now}. + +signed_diff(Iters, Curr, Prev) -> + case Iters > 1 of + true -> sign(Curr - Prev); + false -> sign(0) + end. + +sign(N) when N > 0 -> {"+", N}; +sign(N) -> {"", N}. + +now_to_micros({Mega, Secs, Micros}) -> + DT = calendar:now_to_datetime({Mega, Secs, 0}), + S = calendar:datetime_to_gregorian_seconds(DT), + (S * ?SECOND) + Micros. + +start_drivers(RC, TC) -> + LastHistoryId = table_info(history, size), + Reuse = RC#run_config.reuse_history_id, + DS = #driver_state{tab_config = TC, + run_config = RC, + n_local_branches = 0, + local_branches = [], + history_id = LastHistoryId, + reuse_history_id = Reuse}, + Nodes = RC#run_config.driver_nodes, + NB = TC#tab_config.n_branches, + First = 0, + AllBranches = lists:seq(First, First + NB - 1), + ND = RC#run_config.n_drivers_per_node, + Spawn = fun(Spec) -> + Node = Spec#driver_state.driver_node, + spawn_link(Node, ?MODULE, driver_init, [Spec, AllBranches]) + end, + Specs = [DS#driver_state{driver_id = Id, driver_node = N} + || N <- Nodes, + Id <- lists:seq(1, ND)], + Specs2 = lists:sort(lists:flatten(Specs)), + {Specs3, OrphanBranches} = alloc_local_branches(AllBranches, Specs2, []), + case length(OrphanBranches) of + N when N =< 10 -> + io:format("TPC-B: Orphan branches: ~p~n", [OrphanBranches]); + N -> + io:format("TPC-B: Orphan branches: ~p~n", [N]) + end, + [Spawn(Spec) || Spec <- Specs3]. + +alloc_local_branches([BranchId | Tail], Specs, OrphanBranches) -> + Nodes = table_info({branch, BranchId}, where_to_write), + LocalSpecs = [DS || DS <- Specs, + lists:member(DS#driver_state.driver_node, Nodes)], + case lists:keysort(#driver_state.n_local_branches, LocalSpecs) of + [] -> + alloc_local_branches(Tail, Specs, [BranchId | OrphanBranches]); + [DS | _] -> + LocalNB = DS#driver_state.n_local_branches + 1, + LocalBranches = [BranchId | DS#driver_state.local_branches], + DS2 = DS#driver_state{n_local_branches = LocalNB, + local_branches = LocalBranches}, + Specs2 = Specs -- [DS], + Specs3 = [DS2 | Specs2], + alloc_local_branches(Tail, Specs3, OrphanBranches) + end; +alloc_local_branches([], Specs, OrphanBranches) -> + {Specs, OrphanBranches}. + +driver_init(DS, AllBranches) -> + Seed = erlang:now(), + DS2 = + if + DS#driver_state.n_local_branches =:= 0 -> + DS#driver_state{seed = Seed, + n_local_branches = length(AllBranches), + local_branches = AllBranches}; + true -> + DS#driver_state{seed = Seed} + end, + io:format("TPC-B: Driver ~p started as ~p on node ~p with ~p local branches~n", + [DS2#driver_state.driver_id, self(), node(), DS2#driver_state.n_local_branches]), + driver_loop(DS2). + +driver_loop(DS) -> + receive + {From, report} -> + From ! {self(), DS#driver_state.time}, + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + DS2 = DS#driver_state{time=#time{}, acc_time = Acc}, % Reset timer + DS3 = calc_trans(DS2), + driver_loop(DS3); + {From, stop} -> + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + io:format("TPC-B: Driver ~p (~p) on node ~p stopped: ~w~n", + [DS#driver_state.driver_id, self(), node(self()), Acc]), + From ! {self(), DS#driver_state.time}, + unlink(From), + exit(stopped) + after 0 -> + DS2 = calc_trans(DS), + driver_loop(DS2) + end. + +calc_trans(DS) -> + {Micros, DS2} = time_trans(DS), + Time = DS2#driver_state.time, + Time2 = Time#time{n_trans = Time#time.n_trans + 1, + acc_time = Time#time.acc_time + Micros, + max_time = lists:max([Micros, Time#time.max_time]) + }, + case DS#driver_state.reuse_history_id of + false -> + HistoryId = DS#driver_state.history_id + 1, + DS2#driver_state{time=Time2, history_id = HistoryId}; + true -> + DS2#driver_state{time=Time2} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Generate teller_id, account_id and delta +%% Time the TPC-B transaction +time_trans(DS) -> + OldSeed = get(random_seed), % Avoid interference with Mnesia + put(random_seed, DS#driver_state.seed), + Random = random:uniform(), + NewSeed = get(random_seed), + case OldSeed of + undefined -> erase(random_seed); + _ -> put(random_seed, OldSeed) + end, + + TC = DS#driver_state.tab_config, + RC = DS#driver_state.run_config, + {Branchid, Args} = random_to_args(Random, DS), + {Fun, Mod} = trans_type(TC, RC), + {Time, Res} = timer:tc(?MODULE, real_trans, [RC, Branchid, Fun, Args, Mod]), + + case Res of + AccountBal when is_integer(AccountBal) -> + {Time, DS#driver_state{seed = NewSeed}}; + Other -> + exit({crash, Other, Args, Random, DS}) + end. + +random_to_args(Random, DS) -> + DriverId = DS#driver_state.driver_id, + TC = DS#driver_state.tab_config, + HistoryId = DS#driver_state.history_id, + Delta = trunc(Random * 1999998) - 999999, % -999999 <= Delta <= +999999 + + Branches = DS#driver_state.local_branches, + NB = DS#driver_state.n_local_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + Tmp = trunc(Random * NB * NT), + BranchPos = (Tmp div NT) + 1, + BranchId = + case TC#tab_config.n_fragments of + 0 -> BranchPos - 1; + _ -> lists:nth(BranchPos, Branches) + end, + RelativeTellerId = Tmp div NT, + TellerId = (BranchId * NT) + RelativeTellerId, + {AccountBranchId, AccountId} = + if + Random >= 0.85, NB > 1 -> + %% Pick from a remote account + TmpAccountId= trunc(Random * (NB - 1) * NA), + TmpAccountBranchId = TmpAccountId div NA, + if + TmpAccountBranchId =:= BranchId -> + {TmpAccountBranchId + 1, TmpAccountId + NA}; + true -> + {TmpAccountBranchId, TmpAccountId} + end; + true -> + %% Pick from a local account + RelativeAccountId = trunc(Random * NA), + TmpAccountId = (BranchId * NA) + RelativeAccountId, + {BranchId, TmpAccountId} + end, + + {BranchId, [DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta]}. + +real_trans(RC, BranchId, Fun, Args, Mod) -> + Type = RC#run_config.activity_type, + case RC#run_config.spawn_near_branch of + false -> + mnesia:activity(Type, Fun, Args, Mod); + true -> + Node = table_info({branch, BranchId}, where_to_read), + case rpc:call(Node, mnesia, activity, [Type, Fun, Args, Mod]) of + {badrpc, Reason} -> exit(Reason); + Other -> Other + end + end. + +trans_type(TC, RC) -> + if + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= false -> + {fun add_delta/7, mnesia}; + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= true -> + {fun sticky_add_delta/7, mnesia}; + TC#tab_config.n_fragments > 0, + RC#run_config.use_sticky_locks =:= false -> + {fun frag_add_delta/7, mnesia_frag} + end. + +%% +%% Runs the TPC-B defined transaction and returns NewAccountBalance +%% + +add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, write), + + %% Return account balance + NewA#account.balance. + +sticky_add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab orinary read lock when the record is read + %% Grab sticky write lock when the record is written + %% This transaction would benefit of an early stick_write lock at read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, read), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, sticky_write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, read), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, sticky_write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, read), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, sticky_write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, sticky_write), + + %% Return account balance + NewA#account.balance. + +frag_add_delta(DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta) -> + %% Access fragmented table + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(NewB), + + %% Add delta to teller balance + [T] = mnesia:read({teller, BranchId}, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(NewT), + + %% Add delta to account balance + %%io:format("frag_add_delta(~p): ~p\n", [node(), {account, BranchId, AccountId}]), + [A] = mnesia:read({account, AccountBranchId}, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(NewA), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(History), + + %% Return account balance + NewA#account.balance. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Verify table consistency + +verify_tabs() -> + Nodes = mnesia:system_info(running_db_nodes), + case lists:member(node(), Nodes) of + true -> + Tabs = [branch, teller, account, history], + io:format("TPC-B: Verifying tables: ~w~n", [Tabs]), + rpc:multicall(Nodes, mnesia, wait_for_tables, [Tabs, infinity]), + + Fun = fun() -> + mnesia:write_lock_table(branch), + mnesia:write_lock_table(teller), + mnesia:write_lock_table(account), + mnesia:write_lock_table(history), + {Res, BadNodes} = + rpc:multicall(Nodes, ?MODULE, count_balance, []), + check_balance(Res, BadNodes) + end, + case mnesia:transaction(Fun) of + {atomic, Res} -> Res; + {aborted, Reason} -> {error, Reason} + end; + false -> + {error, "Must be initiated from a running db_node"} + end. + +%% Returns a list of {Table, Node, Balance} tuples +%% Assumes that no updates are performed + +-record(summary, {table, node, balance, size}). + +count_balance() -> + [count_balance(branch, #branch.balance), + count_balance(teller, #teller.balance), + count_balance(account, #account.balance)]. + +count_balance(Tab, BalPos) -> + Frags = table_info(Tab, frag_names), + count_balance(Tab, Frags, 0, 0, BalPos). + +count_balance(Tab, [Frag | Frags], Bal, Size, BalPos) -> + First = mnesia:dirty_first(Frag), + {Bal2, Size2} = count_frag_balance(Frag, First, Bal, Size, BalPos), + count_balance(Tab, Frags, Bal2, Size2, BalPos); +count_balance(Tab, [], Bal, Size, _BalPos) -> + #summary{table = Tab, node = node(), balance = Bal, size = Size}. + +count_frag_balance(_Frag, '$end_of_table', Bal, Size, _BalPos) -> + {Bal, Size}; +count_frag_balance(Frag, Key, Bal, Size, BalPos) -> + [Record] = mnesia:dirty_read({Frag, Key}), + Bal2 = Bal + element(BalPos, Record), + Next = mnesia:dirty_next(Frag, Key), + count_frag_balance(Frag, Next, Bal2, Size + 1, BalPos). + +check_balance([], []) -> + mnesia:abort({"No balance"}); +check_balance(Summaries, []) -> + [One | Rest] = lists:flatten(Summaries), + Balance = One#summary.balance, + %% Size = One#summary.size, + case [S || S <- Rest, S#summary.balance =/= Balance] of + [] -> + ok; + BadSummaries -> + mnesia:abort({"Bad balance", One, BadSummaries}) + end; +check_balance(_, BadNodes) -> + mnesia:abort({"Bad nodes", BadNodes}). diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl new file mode 100644 index 0000000000..c67382e694 --- /dev/null +++ b/lib/mnesia/test/mnesia_trans_access_test.erl @@ -0,0 +1,1254 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_trans_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), mnesia_recovery_test:receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil access of records in the scope of transactions", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + write, read, wread, delete, delete_object, + match_object, select, select14, all_keys, + transaction, nested_activities, + index_tabs, index_lifecycle + ]. + +%% Write records + +write(suite) -> []; +write(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = write, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write({Tab, 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:write({foo, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:write({Tab, 1, 2})), + ?verify_mnesia(Nodes, []). + +%% Read records + +read(suite) -> []; +read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = read, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:read(OneRec) end)), + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec, TwoRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Read records and set write lock + +wread(suite) -> []; +wread(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = wread, + Schema = [{name, Tab}, {type, set}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:wread(OneRec) end)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [TwoRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:wread({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete record + +delete(suite) -> []; +delete(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:delete({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete matching record + +delete_object(suite) -> []; +delete_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete_object, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab}) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete_object(OneRec)), + + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['_']}, 21}) end)), + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['$5']}, 21}) end)), + + ?verify_mnesia(Nodes, []). + +%% Read matching records + +match_object(suite) -> []; +match_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({foo, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({[], '$1', 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:match_object(OnePat)), + ?verify_mnesia(Nodes, []). + +%% select +select(suite) -> []; +select(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, {match, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, [{'_', [], '$1'}]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat)), + ?verify_mnesia(Nodes, []). + + +%% more select +select14(suite) -> []; +select14(Config) when is_list(Config) -> + [Node1,Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = select14_ets, + Tab2 = select14_dets, + Tab3 = select14_remote, + Tab4 = select14_remote_dets, + Schemas = [[{name, Tab1}, {attributes, [k, v]}, {ram_copies, [Node1]}], + [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + [{name, Tab3}, {attributes, [k, v]}, {ram_copies, [Node2]}], + [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}]], + [?match({atomic, ok}, mnesia:create_table(Schema)) || Schema <- Schemas], + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Recs,Cont},Fun) -> + Sel = mnesia:select(Cont), + Recs ++ Fun(Sel, Fun) + end, + Loop = fun(Table,Pattern) -> + Sel = mnesia:select(Table, Pattern, 1, read), + Res = LoopHelp(Sel,LoopHelp), + case mnesia:table_info(Table, type) of + ordered_set -> Res; + _ -> lists:sort(Res) + end + end, + Test = + fun(Tab) -> + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + All = [OneRec,TwoRec], + AllPat = [{'_', [], ['$_']}], + + ?match({atomic, []}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, All}, Trans(fun() -> Loop(Tab, AllPat) end)), + + {atomic,{_, Cont}} = Trans(fun() -> mnesia:select(Tab, OnePat, 1, read) end), + ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select(Cont) end)), + + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, {match, '$1', 2},1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, [{'_', [], '$1'}],1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(sune) end)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat,1,read)), + ?match({aborted, {badarg,sune}}, + Trans(fun() -> mnesia:select(sune) end)) + end, + Test(Tab1), + Test(Tab2), + Test(Tab3), + Test(Tab4), + ?verify_mnesia(Nodes, []). + + +%% Pick all keys from table + +all_keys(suite) ->[]; +all_keys(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = all_keys, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + Write = fun() -> mnesia:write({Tab, 14, 4}) end, + AllKeys = fun() -> mnesia:all_keys(Tab) end, + + ?match({atomic, []}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys(foo) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys([]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:all_keys(Tab)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Use and misuse transactions + +transaction(suite) -> []; +transaction(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end)), + ?match({aborted, _}, mnesia:transaction(no_fun)), + ?match({aborted, _}, mnesia:transaction(?MODULE, no_fun, [foo])), + + {success, [A, B, C, D, E, F, G, H]} = + ?start_activities(lists:duplicate(8, Node1)), + ?start_transactions([A, B, C, D, E, F, G, H]), + + A ! fun() -> mnesia:abort(abort_bad_trans) end, + ?match_receive({A, {aborted, abort_bad_trans}}), + + B ! fun() -> erlang:error(exit_here) end, + ?match_receive({B, {aborted, _}}), + + C ! fun() -> throw(throw_bad_trans) end, + ?match_receive({C, {aborted, {throw, throw_bad_trans}}}), + + D ! fun() -> exit(exit_bad_trans) end, + ?match_receive({D, {aborted, exit_bad_trans}}), + + E ! fun() -> exit(normal) end, + ?match_receive({E, {aborted, normal}}), + + F ! fun() -> exit(abnormal) end, + ?match_receive({F, {aborted, abnormal}}), + + G ! fun() -> exit(G, abnormal) end, + ?match_receive({'EXIT', G, abnormal}), + + H ! fun() -> exit(H, kill) end, + ?match_receive({'EXIT', H, killed}), + + ?match({atomic, ali_baba}, + mnesia:transaction(fun() -> ali_baba end, infinity)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 1)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 0)), + ?match({aborted, Reason8} when element(1, Reason8) == badarg, mnesia:transaction(fun() -> ali_baba end, -1)), + ?match({aborted, Reason1} when element(1, Reason1) == badarg, mnesia:transaction(fun() -> ali_baba end, foo)), + Fun = fun() -> + ?match(true, mnesia:is_transaction()), + ?match({atomic, ok}, + mnesia:transaction(fun() -> ?match(true, mnesia:is_transaction()),ok end)), ok end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_activities(suite) -> + [ + basic_nested, + nested_transactions, + mix_of_nested_activities + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% ensure that nested transactions behave correctly +%% We create a particular table that is used by this test only +-record(ntab, {a, b}). +basic_nested(doc) -> ["Test the basic functionality of nested transactions"]; +basic_nested(suite) -> []; +basic_nested(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + Args = [{ram_copies, Nodes}, + {attributes, record_info(fields, ntab)}], + ?match({atomic, ok}, mnesia:create_table(ntab, Args)), + do_nested(top), + case mnesia_test_lib:diskless(Config) of + false -> + lists:foreach(fun(N) -> + ?match({atomic, ok}, + mnesia:change_table_copy_type(ntab, N, disc_only_copies)) + end, Nodes), + do_nested(top); + true -> + skip + end, + ?verify_mnesia(Nodes, []). + +do_nested(How) -> + F1 = fun() -> + mnesia:write(#ntab{a= 1}), + mnesia:write(#ntab{a= 2}) + end, + F2 = fun() -> + mnesia:read({ntab, 1}) + end, + ?match({atomic, ok}, mnesia:transaction(F1)), + ?match({atomic, _}, mnesia:transaction(F2)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f2() end) + end)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f3() end) + end)), + ?match({atomic, {atomic, [#ntab{a = 5}]}}, + mnesia:transaction(fun() -> mnesia:write(#ntab{a = 5}), + mnesia:transaction(fun() -> n_f4() end) + end)), + Cyclic = fun() -> mnesia:abort({cyclic,a,a,a,a,a}) end, %% Ugly + NodeNotR = fun() -> mnesia:abort({node_not_running, testNode}) end, + + TestAbort = fun(Fun) -> + case get(restart_counter) of + undefined -> + put(restart_counter, 1), + Fun(); + _ -> + erase(restart_counter), + ok + end + end, + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [Cyclic])end)), + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [NodeNotR])end)), + + %% Now try the restart thingie + case How of + top -> + Pids = [spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}])], + ?match({info, _, _}, mnesia_tm:get_info(2000)), + lists:foreach(fun(P) -> receive + {P, ok} -> ok + end + end, Pids), + ?match([], [Tab || Tab <- ets:all(), mnesia_trans_store == ets:info(Tab, name)]); + + {spawned, Pid} -> + ?match({info, _, _}, mnesia_tm:get_info(2000)), + Pid ! {self(), ok}, + exit(normal) + end. + + +n_f1() -> + mnesia:read({ntab, 1}), + mnesia:write(#ntab{a = 3}). + +n_f2() -> + mnesia:write(#ntab{a = 4}), + erlang:error(exit_here). + +n_f3() -> + mnesia:write(#ntab{a = 4}), + throw(funky). + +n_f4() -> + mnesia:read({ntab, 5}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_transactions(doc) -> + ["Verify that nested_transactions are handled as expected"]; +nested_transactions(suite) -> + [nested_trans_both_ok, + nested_trans_child_dies, + nested_trans_parent_dies, + nested_trans_both_dies]. + +nested_trans_both_ok(suite) -> []; +nested_trans_both_ok(Config) when is_list(Config) -> + nested_transactions(Config, ok, ok). + +nested_trans_child_dies(suite) -> []; +nested_trans_child_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, ok). + +nested_trans_parent_dies(suite) -> []; +nested_trans_parent_dies(Config) when is_list(Config) -> + nested_transactions(Config, ok, abort). + +nested_trans_both_dies(suite) -> []; +nested_trans_both_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, abort). + +nested_transactions(Config, Child, Father) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = nested_trans, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> + [{name, Tab}, {ram_copies, Nodes}]; + false -> + [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match(ok, mnesia:dirty_write({Tab, father, not_updated})), + ?match(ok, mnesia:dirty_write({Tab, child, not_updated})), + + ChildOk = fun() -> mnesia:write({Tab, child, updated}) end, + ChildAbort = fun() -> + mnesia:write({Tab, child, updated}), + erlang:error(exit_here) + end, + + Child_Fun = % Depending of test case + case Child of + ok -> ChildOk; + abort -> ChildAbort + end, + + FatherOk = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}) + end, + + FatherAbort = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}), + erlang:error(exit_here) + end, + + {FatherRes, ChildRes} = % Depending of test case + case Father of + ok -> ?match({atomic, ok}, mnesia:transaction(FatherOk)), + case Child of + ok -> {[{Tab, father, updated}], [{Tab, child, updated}]}; + _ -> {[{Tab, father, updated}], [{Tab, child, not_updated}]} + end; + abort -> ?match({aborted, _}, mnesia:transaction(FatherAbort)), + {[{Tab, father, not_updated}], [{Tab, child, not_updated}]} + end, + + %% Syncronize things!! + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({Tab, sync, sync}) end)), + + ?match(ChildRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, child}])), + + ?match(FatherRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, father}])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mix_of_nested_activities(doc) -> + ["Verify that dirty operations in a transaction are handled like ", + "normal transactions"]; +mix_of_nested_activities(suite) -> []; +mix_of_nested_activities(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = tab, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{type,bag}|Def])), + Activities = [transaction, sync_transaction, + ets, async_dirty, sync_dirty], + %% Make a test for all 3000 combinations + Tests = [[A,B,C,D,E] || + A <- Activities, + B <- Activities, + C <- Activities, + D <- Activities, + E <- Activities], + Foreach = + fun(Test,No) -> + Result = lists:reverse(Test), + ?match({No,Result},{No,catch apply_op({Tab,No},Test)}), + No+1 + end, + lists:foldl(Foreach, 0, Tests), + ?verify_mnesia(Nodes, []). + +apply_op(Oid,[Type]) -> + check_res(Type,mnesia:Type(fun() -> [Type|read_op(Oid)] end)); +apply_op(Oid = {Tab,Key},[Type|Next]) -> + check_res(Type,mnesia:Type(fun() -> + Prev = read_op(Oid), + mnesia:write({Tab,Key,[Type|Prev]}), + apply_op(Oid,Next) + end)). + +check_res(transaction, {atomic,Res}) -> + Res; +check_res(sync_transaction, {atomic,Res}) -> + Res; +check_res(async_dirty, Res) when is_list(Res) -> + Res; +check_res(sync_dirty, Res) when is_list(Res) -> + Res; +check_res(ets, Res) when is_list(Res) -> + Res; +check_res(Type,Res) -> + ?match(bug,{Type,Res}). + +read_op(Oid) -> + case lists:reverse(mnesia:read(Oid)) of + [] -> []; + [{_,_,Ops}|_] -> + Ops + end. + +index_tabs(suite) -> + [ + index_match_object, + index_read, + index_update, + index_write + ]. + +%% Read matching records by using an index + +index_match_object(suite) -> []; +index_match_object(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_match_object, + Schema = [{name, Tab}, {attributes, [k, v, e]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 2, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_match_object({Tab, '$1', 2}, ValPos) end)), + OneRec = {Tab, {1, 1}, 2, {1, 1}}, + OnePat = {Tab, '$1', 2, '_'}, + BadPat = {Tab, '$1', '$2', '_'}, %% See ref guide + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + Imatch = fun(Patt, Pos) -> + mnesia:transaction(fun() -> lists:sort(mnesia:index_match_object(Patt, Pos)) end) + end, + ?match({atomic, [OneRec]}, Imatch(OnePat, ValPos)), + ?match({aborted, _}, Imatch(OnePat, BadValPos)), + ?match({aborted, _}, Imatch({foo, '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch({[], '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch(BadPat, ValPos)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_match_object(OnePat, ValPos)), + + Another = {Tab, {3,1}, 2, {4,4}}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Another) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, {4, 4}, 3, {4, 4}}) end)), + + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1, '$1'}, 2, '_'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {'$2', '$1'}, 2, {'_', '$1'}}, ValPos)), + ?match({atomic, [OneRec, Another]}, Imatch({Tab, '_', 2, '_'}, ValPos)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 4, 5, {7, 4}}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 7, 5, {7, 5}}) end)), + + ?match({atomic, [{Tab, 4, 5, {7, 4}}]}, Imatch({Tab, '$1', 5, {'_', '$1'}}, ValPos)), + + ?match({atomic, [OneRec]}, rpc:call(Node2, mnesia, transaction, + [fun() -> + lists:sort(mnesia:index_match_object({Tab, {1,1}, 2, + {1,1}}, ValPos)) + end])), + ?verify_mnesia(Nodes, []). + +%% Read records by using an index + +index_read(suite) -> []; +index_read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_read, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + OneRec = {Tab, 1, 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, BadValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(foo, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read([], 2, ValPos) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_read(Tab, 2, ValPos)), + ?verify_mnesia(Nodes, []). + +index_update(suite) -> [index_update_set, index_update_bag]; +index_update(doc) -> ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. +index_update_set(suite) -> []; +index_update_set(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, {attributes, [k, v1, v2, v3]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2], lists:sort(R2)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R4} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R5} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R6)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1], lists:sort(R11)), + ?match({atomic, [Rec1]},mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, []},mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?verify_mnesia(Nodes, []). + +index_update_bag(suite) -> []; +index_update_bag(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, + {type, bag}, + {attributes, [k, v1, v2, v3]}, + {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R2)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R3)), + + {atomic, R4} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R5} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + + %% OTP-6587 Needs some whitebox testing to see that the index table is cleaned correctly + + [IPos] = mnesia_lib:val({Tab,index}), + ITab = mnesia_lib:val({index_test,{index, IPos}}), + io:format("~n Index ~p @ ~p => ~p ~n~n",[IPos,ITab, ets:tab2list(ITab)]), + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R60} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R60)), + + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec3) end)), + {atomic, R61} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R61)), + {atomic, R62} = mnesia:transaction(fun() -> mnesia:index_read(Tab,12, ValPos) end), + ?match([], lists:sort(R62)), + ?match([{2,1},{2,2}], ets:tab2list(ITab)), + + %% reset for rest of testcase + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + %% OTP-6587 + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R9)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + {atomic, R12} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R12)), + {atomic, R13} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + {atomic, R14} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R15} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R16} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R17} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + {atomic, R18} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R19} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + + +index_write(suite) -> []; +index_write(doc) -> ["See ticket OTP-8072"]; +index_write(Config)when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + mnesia:create_table(a, [{index, [val]}]), + mnesia:create_table(counter, []), + + CreateIfNonExist = + fun(Index) -> + case mnesia:index_read(a, Index, 3) of + [] -> + Id = mnesia:dirty_update_counter(counter, id, 1), + New = {a, Id, Index}, + mnesia:write(New), + New; + [Found] -> + Found + end + end, + + Trans = fun(A) -> + mnesia:transaction(CreateIfNonExist, [A]) + %% This works better most of the time + %% And it is allowed to fail since it's dirty + %% mnesia:async_dirty(CreateIfNonExist, [A]) + end, + + Self = self(), + Update = fun() -> + Res = lists:map(Trans, lists:seq(1,10)), + Self ! {self(), Res} + end, + + Pids = [spawn(Update) || _ <- lists:seq(1,5)], + + Gather = fun(Pid, Acc) -> receive {Pid, Res} -> [Res|Acc] end end, + Results = lists:foldl(Gather, [], Pids), + Expected = hd(Results), + Check = fun(Res) -> ?match(Expected, Res) end, + lists:foreach(Check, Results), + ?verify_mnesia(Nodes, []). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop indecies + +index_lifecycle(suite) -> + [ + add_table_index_ram, + add_table_index_disc, + add_table_index_disc_only, + create_live_table_index_ram, + create_live_table_index_disc, + create_live_table_index_disc_only, + del_table_index_ram, + del_table_index_disc, + del_table_index_disc_only, + idx_schema_changes + ]. + +add_table_index_ram(suite) -> []; +add_table_index_ram(Config) when is_list(Config) -> + add_table_index(Config, ram_copies). + +add_table_index_disc(suite) -> []; +add_table_index_disc(Config) when is_list(Config) -> + add_table_index(Config, disc_copies). + +add_table_index_disc_only(suite) -> []; +add_table_index_disc_only(Config) when is_list(Config) -> + add_table_index(Config, disc_only_copies). + +%% Add table index + +add_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = add_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({aborted, Reason41 } when element(1, Reason41) == bad_type, + mnesia:add_table_index(Tab, BadValPos)), + ?match({aborted,Reason42 } when element(1, Reason42) == bad_type, + mnesia:add_table_index(Tab, 2)), + ?match({aborted, Reason43 } when element(1, Reason43) == bad_type, + mnesia:add_table_index(Tab, 1)), + ?match({aborted, Reason44 } when element(1, Reason44) == bad_type, + mnesia:add_table_index(Tab, 0)), + ?match({aborted, Reason45 } when element(1, Reason45) == bad_type, + mnesia:add_table_index(Tab, -1)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted, Reason46 } when element(1, Reason46) == already_exists, + mnesia:add_table_index(Tab, ValPos)), + + NestedFun = fun() -> + ?match({aborted, nested_transaction}, + mnesia:add_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +create_live_table_index_ram(suite) -> []; +create_live_table_index_ram(Config) when is_list(Config) -> + create_live_table_index(Config, ram_copies). + +create_live_table_index_disc(suite) -> []; +create_live_table_index_disc(Config) when is_list(Config) -> + create_live_table_index(Config, disc_copies). + +create_live_table_index_disc_only(suite) -> []; +create_live_table_index_disc_only(Config) when is_list(Config) -> + create_live_table_index(Config, disc_only_copies). + +create_live_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = create_live_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + mnesia:dirty_write({Tab, 1, 2}), + + Fun = fun() -> + ?match(ok, mnesia:write({Tab, 2, 2})), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, [{Tab, 1, 2},{Tab, 2, 2}]}, + mnesia:transaction(fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos)) + end)), + ?verify_mnesia(Nodes, []). + +%% Drop table index + +del_table_index_ram(suite) ->[]; +del_table_index_ram(Config) when is_list(Config) -> + del_table_index(Config, ram_copies). + +del_table_index_disc(suite) ->[]; +del_table_index_disc(Config) when is_list(Config) -> + del_table_index(Config, disc_copies). + +del_table_index_disc_only(suite) ->[]; +del_table_index_disc_only(Config) when is_list(Config) -> + del_table_index(Config, disc_only_copies). + +del_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = del_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:del_table_index(Tab, BadValPos)), + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + + ?match({aborted,Reason1} when element(1, Reason1) == no_exists, + mnesia:del_table_index(Tab, ValPos)), + NestedFun = + fun() -> + ?match({aborted, nested_transaction}, + mnesia:del_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +idx_schema_changes(suite) -> [idx_schema_changes_ram, + idx_schema_changes_disc, + idx_schema_changes_disc_only]; +idx_schema_changes(doc) -> + ["Tests that index tables are handled correctly when schema changes.", + "For example when a replica is deleted or inserted", + "TICKET OTP-2XXX (ELVIRA)"]. + +idx_schema_changes_ram(suite) -> []; +idx_schema_changes_ram(Config) when is_list(Config) -> + idx_schema_changes(Config, ram_copies). +idx_schema_changes_disc(suite) -> []; +idx_schema_changes_disc(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_copies). +idx_schema_changes_disc_only(suite) -> []; +idx_schema_changes_disc_only(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_only_copies). + +idx_schema_changes(Config, Storage) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_schema_changes, + Idx = 3, + Schema = [{name, Tab}, {index, [Idx]}, {attributes, [k, v]}, {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + {Storage1, Storage2} = + case Storage of + disc_only_copies -> + {ram_copies, disc_copies}; + disc_copies -> + {disc_only_copies, ram_copies}; + ram_copies -> + {disc_copies, disc_only_copies} + end, + + Write = fun(N) -> + mnesia:write({Tab, N, N+50}) + end, + + [mnesia:sync_transaction(Write, [N]) || N <- lists:seq(1, 10)], + ?match([{Tab, 1, 51}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 51, Idx])), + ?match([{Tab, 1, 51}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 51, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N1, Storage1)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [17]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [18]])), + + ?match([{Tab, 17, 67}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 67, Idx])), + ?match([{Tab, 18, 68}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 68, Idx])), + + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [11]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [12]])), + + ?match([{Tab, 11, 61}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 61, Idx])), + ?match([{Tab, 12, 62}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 62, Idx])), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [19]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [20]])), + + ?match([{Tab, 19, 69}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 69, Idx])), + ?match([{Tab, 20, 70}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 70, Idx])), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N2, Storage)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [13]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [14]])), + + ?match([{Tab, 13, 63}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 63, Idx])), + ?match([{Tab, 14, 64}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 64, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N2, Storage2)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [15]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [16]])), + + ?match([{Tab, 15, 65}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 65, Idx])), + ?match([{Tab, 16, 66}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 66, Idx])), + + ?verify_mnesia(Nodes, []). diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt new file mode 100755 index 0000000000..25243f1149 --- /dev/null +++ b/lib/mnesia/test/mt @@ -0,0 +1,60 @@ +#! /bin/sh -f +# ``The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved via the world wide web at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +# +# Author: Hakan Mattsson <[email protected]> +# Purpose: Simplified execution of the test suite +# +# Usage: mt <args to erlang startup script> + +#top=".." +top="$ERL_TOP/lib/mnesia" +h=`hostname` +p="-pa $top/examples -pa $top/ebin -pa $top/test -mnesia_test_verbose true" +log=test_log$$ +latest=test_log_latest +args=${1+"$@"} +erlcmd="erl -sname a $p $args -mnesia_test_timeout" +erlcmd1="erl -sname a1 $p $args" +erlcmd2="erl -sname a2 $p $args" + +xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 & +xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 & + +rm "$latest" 2>/dev/null +ln -s "$log" "$latest" +touch "$log" + +echo "$erlcmd1" +echo "" +echo "$erlcmd2" +echo "" +echo "$erlcmd" +echo "" +echo "Give the following command in order to see the outcome from node a@$h"":" +echo "" +echo " less test_log$$" + +ostype=`uname -s` +if [ "$ostype" = "SunOS" ] ; then + /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd & +else + xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" & +fi +tail -f "$log" | egrep 'Eval|<>ERROR|NYI' + diff --git a/lib/mnesia/test/mt.erl b/lib/mnesia/test/mt.erl new file mode 100644 index 0000000000..f69c4a11fd --- /dev/null +++ b/lib/mnesia/test/mt.erl @@ -0,0 +1,262 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Nice shortcuts intended for testing of Mnesia +%%% +%%% See the mnesia_SUITE module about the structure of +%%% the test suite. +%%% +%%% See the mnesia_test_lib module about the test case execution. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mt). +-author('[email protected]'). +-export([ + t/0, t/1, t/2, t/3, % Run test cases + loop/1, loop/2, loop/3, % loop test cases + doc/0, doc/1, % Generate test case doc + struct/0, struct/1, % View test suite struct + shutdown/0, ping/0, start_nodes/0, % Node admin + read_config/0, write_config/1 % Config admin + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Aliases for the (sub) test suites +alias(all) -> mnesia_SUITE; +alias(atomicity) -> mnesia_atomicity_test; +alias(backup) -> mnesia_evil_backup; +alias(config) -> mnesia_config_test; +alias(consistency) -> mnesia_consistency_test; +alias(dirty) -> mnesia_dirty_access_test; +alias(durability) -> mnesia_durability_test; +alias(evil) -> mnesia_evil_coverage_test; +alias(qlc) -> mnesia_qlc_test; +alias(examples) -> mnesia_examples_test; +alias(frag) -> mnesia_frag_test; +alias(heavy) -> {mnesia_SUITE, heavy}; +alias(install) -> mnesia_install_test; +alias(isolation) -> mnesia_isolation_test; +alias(light) -> {mnesia_SUITE, light}; +alias(measure) -> mnesia_measure_test; +alias(medium) -> {mnesia_SUITE, medium}; +alias(nice) -> mnesia_nice_coverage_test; +alias(recover) -> mnesia_recover_test; +alias(recovery) -> mnesia_recovery_test; +alias(registry) -> mnesia_registry_test; +alias(suite) -> mnesia_SUITE; +alias(trans) -> mnesia_trans_access_test; +alias(Other) -> Other. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Resolves the name of test suites and test cases +%% according to the alias definitions. Single atoms +%% are assumed to be the name of a test suite. +resolve(Suite0) when is_atom(Suite0) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, all}; + {Suite, Case} -> + {Suite, Case} + end; +resolve({Suite0, Case}) when is_atom(Suite0), is_atom(Case) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, Case}; + {Suite, Case2} -> + {Suite, Case2} + end; +resolve(List) when is_list(List) -> + [resolve(Case) || Case <- List]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Run one or more test cases + +%% Run the default test case with default config +t() -> + t(read_test_case()). + +%% Resolve the test case name and run the test case +%% The test case is noted as default test case +%% and the outcome of the tests are written to +%% to a file. +t(silly) -> + mnesia_install_test:silly(); +t(diskless) -> + %% Run the default test case with default config, + %% but diskless + t(read_test_case(), diskless); +t(Case) -> + %% Use the default config + t(Case, read_config()). + +t(Case, Config) when Config == diskless -> + %% Run the test case with default config, but diskless + Config2 = [{diskless, true} | read_config()], + t(Case, Config2); +t(Mod, Fun) when is_atom(Mod), is_atom(Fun) -> + %% Run the test case with default config + t({Mod, Fun}, read_config()); +t(RawCase, Config) when is_list(Config) -> + %% Resolve the test case name and run the test case + Case = resolve(RawCase), + write_test_case(Case), + Res = mnesia_test_lib:test(Case, Config), + append_test_case_info(Case, Res). + +t(Mod, Fun, Config) when Config == diskless -> + t({Mod, Fun}, diskless). + +config_fname() -> + "mnesia_test_case_config". + +%% Read default config file +read_config() -> + Fname = config_fname(), + mnesia_test_lib:log("Consulting file ~s...~n", [Fname]), + case file:consult(Fname) of + {ok, Config} -> + mnesia_test_lib:log("Read config ~w~n", [Config]), + Config; + _Error -> + Config = mnesia_test_lib:default_config(), + mnesia_test_lib:log("<>WARNING<> Using default config: ~w~n", [Config]), + Config + end. + +%% Write new default config file +write_config(Config) when is_list(Config) -> + Fname = config_fname(), + {ok, Fd} = file:open(Fname, write), + write_list(Fd, Config), + file:close(Fd). + +write_list(Fd, [H | T]) -> + ok = io:format(Fd, "~p.~n",[H]), + write_list(Fd, T); +write_list(_, []) -> + ok. + +test_case_fname() -> + "mnesia_test_case_info". + +%% Read name of test case +read_test_case() -> + Fname = test_case_fname(), + case file:open(Fname, [read]) of + {ok, Fd} -> + Res = io:read(Fd, []), + file:close(Fd), + case Res of + {ok, TestCase} -> + mnesia_test_lib:log("Using test case ~w from file ~s~n", + [TestCase, Fname]), + TestCase; + {error, _} -> + default_test_case(Fname) + end; + {error, _} -> + default_test_case(Fname) + end. + +default_test_case(Fname) -> + TestCase = all, + mnesia_test_lib:log("<>WARNING<> Cannot read file ~s, " + "using default test case: ~w~n", + [Fname, TestCase]), + TestCase. + +write_test_case(TestCase) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, write), + ok = io:format(Fd, "~p.~n",[TestCase]), + file:close(Fd). + +append_test_case_info(TestCase, TestCaseInfo) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, [read, write]), + ok = io:format(Fd, "~p.~n",[TestCase]), + ok = io:format(Fd, "~p.~n",[TestCaseInfo]), + file:close(Fd), + TestCaseInfo. + +%% Generate HTML pages from the test case structure +doc() -> + doc(suite). + +doc(Case) -> + mnesia_test_lib:doc(resolve(Case)). + +%% Display out the test case structure +struct() -> + struct(suite). + +struct(Case) -> + mnesia_test_lib:struct([resolve(Case)]). + +%% Shutdown all nodes with erlang:halt/0 +shutdown() -> + mnesia_test_lib:shutdown(). + +%% Ping all nodes in config spec +ping() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + [{N, net_adm:ping(N)} || N <- Nodes]. + +%% Slave start all nodes in config spec +start_nodes() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + mnesia_test_lib:init_nodes(Nodes, ?FILE, ?LINE), + ping(). + +%% loop one testcase /suite until it fails + +loop(Case) -> + loop_1(Case,-1,read_config()). + +loop(M,F) when is_atom(F) -> + loop_1({M,F},-1,read_config()); +loop(Case,N) when is_integer(N) -> + loop_1(Case, N,read_config()). + +loop(M,F,N) when is_integer(N) -> + loop_1({M,F},N,read_config()). + +loop_1(Case,N,Config) when N /= 0 -> + io:format("Loop test ~p ~n", [abs(N)]), + case ok_result(Res = t(Case,Config)) of + true -> + loop_1(Case,N-1,Config); + error -> + Res + end; +loop_1(_,_,_) -> + ok. + +ok_result([{_T,{ok,_,_}}|R]) -> + ok_result(R); +ok_result([{_T,{TC,List}}|R]) when is_tuple(TC), is_list(List) -> + ok_result(List) andalso ok_result(R); +ok_result([]) -> true; +ok_result(_) -> error. |