# coding: utf-8 require 'spec_helper' require 'open3' require 'mysql2' require 'flydata/command/sync' require 'flydata/parser/mysql/dump_parser' module Flydata module Parser module Mysql context "Test Dump Generators" do let(:stdin) do s = double(:stdin) allow(s).to receive(:close_write) s end let(:stdout) do s = double(:stdout) allow(s).to receive(:set_encoding) allow(s).to receive(:gets).and_return("first line") allow(s).to receive(:each_line).and_yield("another line") s end let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_#{Time.now.to_i}") } let(:default_conf) do { 'host' => 'localhost', 'port' => 3306, 'username' => 'admin', 'password' => 'pass', 'database' => 'dev', 'tables' => ['users','groups'], } end describe MysqlDumpGeneratorNoMasterData do let(:stderr) { double(:stderr) } let(:wait_thr) { double(:wait_thr) } let(:default_dump_generator) { MysqlDumpGeneratorNoMasterData.new(default_conf) } let(:binlog_pos) { { binfile: 'mysql-bin.000451', pos: 89872 } } let(:mysql_client) do m = double(:mysql_client) allow(m).to receive(:query).with(/TABLES/) allow(m).to receive(:query).with("SHOW VARIABLES LIKE 'version'"). and_return([{'Value' => '5.1.40-0ubuntu0.12.04.1-log'}]) allow(m).to receive(:query).with("SHOW MASTER STATUS;"). and_return([{'File' => 'mysql-bin.000451', 'Position' => 89872}]) allow(m).to receive(:close) m end describe '#dump' do before do expect(Mysql2::Client).to receive(:new).and_return(mysql_client) expect(Open3).to receive(:popen3).and_yield(stdin, stdout, stderr, wait_thr) end context "when mysqldump exits with status 0" do it do expect(wait_thr).to receive(:value).and_return(0) expect(stderr).to receive(:each_line).and_yield("") expect(default_dump_generator.dump(file_path)).to eq(binlog_pos) end end context "when mysqldump exits with status 1" do it do expect(wait_thr).to receive(:value).and_return(1) expect(stderr).to receive(:each_line).and_yield("") expect{ default_dump_generator.dump(file_path) }.to raise_error end end context "when mysqldump exits with status 0, but there are error in stderr" do it do expect(wait_thr).to receive(:value).and_return(0) expect(stderr).to receive(:each_line).and_yield("mysqldump error") expect{ default_dump_generator.dump(file_path) }.to raise_error end end after :each do File.delete(file_path) if File.exists?(file_path) end end end end describe FdMysqlClient do let(:db_opts) do { host: 'localhost', port: 3306, username: 'admin', password: 'pass', database: 'dev' } end describe '#query' do module DummyMysqlClient def initialize(opts = {}) end def query(sql, opts = {}) raise Mysql2::Error.new("Timeout waiting for a response from the last query. (waited 600 seconds)") end end it 'raises appropriate error when query times out' do described_class.instance_eval { include DummyMysqlClient } sql = "FLUSH LOCAL TABLES" test_client = described_class.new(db_opts) expect{test_client.query(sql)}.to raise_error(RuntimeError, /query timed out when running/) expect(test_client.last_query).to eq(sql) end end end describe MysqlDumpParser do let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_parse_#{Time.now.to_i}") } let(:dump_io) { File.open(file_path, 'r', encoding: "utf-8") } let(:default_parser) { MysqlDumpParser.new(binlog_pos: default_binlog_pos) } def generate_dump_file(content) File.open(file_path, 'w') {|f| f.write(content)} end after do File.delete(file_path) if File.exists?(file_path) end describe '#parse' do DUMP_HEADER = <--5.6.13-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Position to start replication or point-in-time recovery from -- -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000267', MASTER_LOG_POS=120; EOT def index_after(content, string) content.index(string) + string.bytesize + 1 end let(:default_parser) { MysqlDumpParser.new(binlog_pos: default_binlog_pos) } let(:default_binlog_pos) { {binfile: 'mysql-bin.000267', pos: 120 } } let(:dump_pos_after_binlog_pos) { index_after(DUMP_HEADER, '5.6.13-log') } let(:create_table_block) { double('create_table_block') } let(:insert_record_block) { double('insert_record_block') } let(:check_point_block) { double('check_point_block') } before do generate_dump_file('') end context 'when dump contains only binlog pos' do before { generate_dump_file(<