Skip to content

Commit

Permalink
Added some trivial integration tests
Browse files Browse the repository at this point in the history
Ported some very basic tests as well as created two interrupt/resume
tests.
  • Loading branch information
shuhaowu committed Nov 20, 2018
1 parent d645602 commit 2ef8987
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 0 deletions.
84 changes: 84 additions & 0 deletions test/integration/cases/trivial_integration_tests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require "json"
require "ghostferry_integration"

class TrivialIntegrationTests < GhostferryIntegration::TestCase
def ghostferry_main_path
"go/minimal.go"
end

def test_copy_data_without_any_writes_to_source
@dbs.seed_simple_database_with_single_table
@ghostferry.run
assert_test_table_is_identical
end

def test_copy_data_with_writes_to_source
use_datawriter

@dbs.seed_simple_database_with_single_table

@ghostferry.run
assert_test_table_is_identical
end

def test_interrupt_resume_with_writes_to_source
@dbs.seed_simple_database_with_single_table

dumped_state = nil
with_isolated_setup_and_teardown do
use_datawriter

batches_written = 0
@ghostferry.on_status(Ghostferry::Status::AFTER_ROW_COPY) do
batches_written += 1
if batches_written >= 2
@ghostferry.send_signal("TERM")
end
end

dumped_state = @ghostferry.run_expecting_interrupt
assert_basic_fields_exist_in_dumped_state(dumped_state)
end

# We want to write some data to the source database while Ghostferry is down
# to verify that it is copied over.
5.times do
@datawriter.insert_data(@dbs.source)
@datawriter.update_data(@dbs.source)
@datawriter.delete_data(@dbs.source)
end

with_isolated_setup_and_teardown do
use_datawriter
@ghostferry.run(dumped_state)

assert_test_table_is_identical
end
end

def test_interrupt_resume_when_table_has_completed
@dbs.seed_simple_database_with_single_table
dumped_state = nil

results = @dbs.source.query("SELECT COUNT(*) as cnt FROM #{GhostferryIntegration::DbManager::DEFAULT_FULL_TABLE_NAME}")
rows = results.first["cnt"]

with_isolated_setup_and_teardown do
use_datawriter

@ghostferry.on_status(Ghostferry::Status::ROW_COPY_COMPLETED) do
@ghostferry.send_signal("TERM")
end

dumped_state = @ghostferry.run_expecting_interrupt
assert_basic_fields_exist_in_dumped_state(dumped_state)
end

with_isolated_setup_and_teardown do
use_datawriter
@ghostferry.run(dumped_state)

assert_test_table_is_identical
end
end
end
139 changes: 139 additions & 0 deletions test/integration/ruby/ghostferry_integration/test_case.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
require "logger"
require "minitest"
require "minitest/hooks/test"

module GhostferryIntegration
class TestCase < Minitest::Test
include Minitest::Hooks
include GhostferryIntegration

##############
# Test Hooks #
##############

def before_all
@logger = Logger.new(STDOUT)
if ENV["DEBUG"] == "1"
@logger.level = Logger::DEBUG
else
@logger.level = Logger::INFO
end
@ghostferry = Ghostferry.new(ghostferry_main_path, logger: @logger)
end

def after_all
@ghostferry.remove_binary
end

def before_setup
@dbs = DbManager.new(logger: @logger)
@dbs.reset_data

setup_ghostferry_datawriter
end

def after_teardown
teardown_ghostferry_datawriter
end

######################
# Test Setup Helpers #
######################

# If multiple Ghostferry runs are needed within a single test, such as in
# the case of interrupt/resume testing, we will need to wrap each
# @ghostferry.run within a block for this method.
#
# This method doesn't destroy the database state like before_setup and
# after_teardown does.
def with_isolated_setup_and_teardown
setup_ghostferry_datawriter
yield
teardown_ghostferry_datawriter
end

# This setup the datawriter to start when Ghostferry start and stop when
# cutover is about to take place.
#
# The on_write block is called everytime the datawriter writes a row with
# the argument op, id.
#
# op: "INSERT"/"UPDATE"/"DELETE"
# id: the primary id of the row inserted
def use_datawriter(&on_write)
start_datawriter_with_ghostferry(&on_write)
stop_datawriter_during_cutover
end

#####################
# Assertion Helpers #
#####################

def assert_test_table_is_identical
source, target = @dbs.source_and_target_table_metrics

assert source[DbManager::DEFAULT_FULL_TABLE_NAME][:row_count] > 0
assert target[DbManager::DEFAULT_FULL_TABLE_NAME][:row_count] > 0

assert_equal(
source[DbManager::DEFAULT_FULL_TABLE_NAME][:checksum],
target[DbManager::DEFAULT_FULL_TABLE_NAME][:checksum],
)

assert_equal(
source[DbManager::DEFAULT_FULL_TABLE_NAME][:sample_row],
target[DbManager::DEFAULT_FULL_TABLE_NAME][:sample_row],
)
end

# Use this method to assert the validity of the structure of the dumped
# state.
#
# To actually assert the validity of the data within the dumped state, you
# have to do it manually.
def assert_basic_fields_exist_in_dumped_state(dumped_state)
refute dumped_state.nil?
refute dumped_state["GhostferryVersion"].nil?
refute dumped_state["LastKnownTableSchemaCache"].nil?
refute dumped_state["LastSuccessfulPrimaryKeys"].nil?
refute dumped_state["CompletedTables"].nil?
refute dumped_state["LastWrittenBinlogPosition"].nil?
end

protected
def ghostferry_main_path
raise NotImplementedError
end

private

def start_datawriter_with_ghostferry(&on_write)
@ghostferry.on_status(Ghostferry::Status::READY) do
@datawriter.start(&on_write)
end
end

def stop_datawriter_during_cutover
@ghostferry.on_status(Ghostferry::Status::ROW_COPY_COMPLETED) do
# At the start of the cutover phase, we have to set the database to
# read-only. This is done by stopping the datawriter.
@datawriter.stop
@datawriter.join
end
end

def setup_ghostferry_datawriter
@ghostferry.reset_state
@datawriter = DataWriter.new(@dbs.source_db_config, logger: @logger)
end

# This should be a no op if ghostferry and datawriter have already been
# stopped.
def teardown_ghostferry_datawriter
@datawriter.stop
@datawriter.join

@ghostferry.stop_and_cleanup
end
end
end
6 changes: 6 additions & 0 deletions test/integration/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "minitest/autorun"

ruby_path = File.join(File.absolute_path(File.dirname(__FILE__)), "ruby")
$LOAD_PATH.unshift(ruby_path) unless $LOAD_PATH.include?(ruby_path)

require_relative "cases/trivial_integration_tests"

0 comments on commit 2ef8987

Please sign in to comment.