isolated_server 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2d7d8b108bb1695c895f2cca19133ba564e5f82
4
+ data.tar.gz: 7a84a2bfcaff968497b8905be18d419af9fd73e6
5
+ SHA512:
6
+ metadata.gz: 0612f94c0a675cea739aa38f072392351b1be05d83ab9615f7789cefff73b7f54f98e265406b346982c3c497a4bb0b7e113d25f1c9971235be2abf9700af2f88
7
+ data.tar.gz: 75e455b4e6bf2143975f9d3dd5227b932ec7c5154459d248c645a44e74b1d854e897af6a3a30560550f5ed23d7e4d6fd8788766b5df1aacf9db86909270067e6
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ gemfiles/*.gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ gemfile:
3
+ - gemfiles/mongo_1.7.gemfile
4
+ - gemfiles/mongo_latest.gemfile
5
+ rvm:
6
+ - 2.1.1
7
+ - 2.1.2
8
+
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise "mongo-1.7" do
2
+ gem "mongo", "~> 1.7.0"
3
+ end
4
+
5
+ appraise "mongo-latest" do
6
+ gem "mongo"
7
+ end
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "bump"
4
+ gem "mysql2", "~> 0.3"
5
+ gem "mongo", "~> 1.10"
6
+ # Specify your gem's dependencies in isolated_server.gemspec
7
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ben Osheroff
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ [![Build Status](https://travis-ci.org/gabetax/isolated_server.svg?branch=master)](https://travis-ci.org/gabetax/isolated_server)
2
+
3
+ # Mysql Isolated Servers -- a gem for testing mysql stuff
4
+
5
+ This gem provides functionality to quickly bring up and tear down mysql instances for the
6
+ purposes of testing code against more advanced mysql topologies -- replication, vertical
7
+ partitions, etc.
8
+
9
+ I developed this as part of my testing strategy for implementing http://github.com/osheroff/ar_mysql_flexmaster, but it's
10
+ been useful in developement of a couple of other projects too (http://github.com/osheroff/mmtop).
11
+
12
+ ## Usage
13
+
14
+ ```
15
+ $mysql_master = IsolatedServer::Mysql.new(allow_output: false)
16
+ $mysql_master.boot!
17
+
18
+ puts "mysql master booted on port #{$mysql_master.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_master.port} mysql"
19
+
20
+ $mysql_slave = IsolatedServer::Mysql.new
21
+ $mysql_slave.boot!
22
+
23
+ puts "mysql slave booted on port #{$mysql_slave.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_slave.port} mysql"
24
+
25
+ $mysql_slave_2 = IsolatedServer::Mysql.new
26
+ $mysql_slave_2.boot!
27
+
28
+ puts "mysql chained slave booted on port #{$mysql_slave_2.port} -- access with mysql -uroot -h127.0.0.1 --port=#{$mysql_slave_2.port} mysql"
29
+
30
+ $mysql_slave.make_slave_of($mysql_master)
31
+ $mysql_slave_2.make_slave_of($mysql_slave)
32
+
33
+ $mysql_slave.set_rw(false)
34
+ sleep if __FILE__ == $0
35
+ ```
36
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "bump/tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
3
+ require 'rubygems'
4
+ require 'isolated_server'
5
+ require 'getoptlong'
6
+
7
+ opts = GetoptLong.new(
8
+ [ '--log-bin', GetoptLong::REQUIRED_ARGUMENT ],
9
+ [ '--pid', GetoptLong::REQUIRED_ARGUMENT ]
10
+ )
11
+
12
+ options = {}
13
+ opts.each do |opt, arg|
14
+ case opt
15
+ when '--log-bin'
16
+ options[:log_bin] = arg
17
+ when '--pid'
18
+ options[:pid] = arg.to_i
19
+ end
20
+ end
21
+
22
+ options[:params] = ARGV.join(' ')
23
+
24
+ isolated_server = IsolatedServer::Mysql.new(options)
25
+ isolated_server.boot!
26
+
27
+ puts "dir: " + isolated_server.base
28
+ puts "port: " + isolated_server.port.to_s
29
+ STDOUT.flush
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "bump"
6
+ gem "mysql2", "~> 0.3"
7
+ gem "mongo", "~> 1.10"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "bump"
6
+ gem "mysql2", "~> 0.3"
7
+ gem "mongo", "~> 1.10"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'isolated_server/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "isolated_server"
8
+ spec.version = IsolatedServer::VERSION
9
+ spec.authors = ["Ben Osheroff", "Gabe Martin-Dempesy"]
10
+ spec.email = ["ben@zendesk.com", "gabe@zendesk.com"]
11
+ spec.description = %q{A small library that allows you to easily spin up new local mysql servers for testing purposes.}
12
+ spec.summary = %q{A small library that allows you to easily spin up new local mysql servers for testing purposes.}
13
+ spec.homepage = "http://github.com/gabetax/isolated_server"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "pry"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "appraisal"
25
+ end
@@ -0,0 +1,117 @@
1
+ require 'socket'
2
+
3
+ module IsolatedServer
4
+ class Base
5
+ attr_reader :pid, :base, :port
6
+ attr_accessor :params
7
+
8
+ def initialize(options)
9
+ @base = options[:base] || Dir.mktmpdir("isolated", "/tmp")
10
+ @params = options[:params]
11
+ @port = options[:port]
12
+ @allow_output = options[:allow_output]
13
+ @parent_pid = options[:pid]
14
+ end
15
+
16
+ def locate_executable(*candidates)
17
+ output = `which #{candidates.shelljoin}`
18
+ raise "I couldn't find any of these: #{candidates.join(',')} in $PATH" if output.chomp.empty?
19
+ output.split("\n").first
20
+ end
21
+
22
+ def down!
23
+ Process.kill("HUP", @pid)
24
+ Process.wait
25
+ @cx = nil
26
+ end
27
+
28
+ def kill!
29
+ return unless @pid
30
+ Process.kill("TERM", @pid)
31
+ end
32
+
33
+ def cleanup!
34
+ system("rm -Rf #{base.shellescape}")
35
+ end
36
+
37
+ include Socket::Constants
38
+ def grab_free_port
39
+ while true
40
+ candidate=9000 + rand(50_000)
41
+
42
+ begin
43
+ socket = Socket.new(AF_INET, SOCK_STREAM, 0)
44
+ socket.bind(Socket.pack_sockaddr_in(candidate, '127.0.0.1'))
45
+ socket.close
46
+ return candidate
47
+ rescue Exception
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.exec_wait(cmd, options = {})
53
+ allow_output = options[:allow_output] # default false
54
+ parent_pid = options[:parent_pid] || $$
55
+
56
+ fork do
57
+ exec_pid = fork do
58
+ if !allow_output
59
+ devnull = File.open("/dev/null", "w")
60
+ STDOUT.reopen(devnull)
61
+ STDERR.reopen(devnull)
62
+ end
63
+
64
+ exec(cmd)
65
+ end
66
+
67
+ # begin waiting for the parent (or mysql) to die; at_exit is hard to control when interacting with test/unit
68
+ # we can also be killed by our parent with down! and up!
69
+ #
70
+ ["TERM", "INT"].each do |sig|
71
+ trap(sig) do
72
+ if block_given?
73
+ yield(exec_pid)
74
+ else
75
+ Process.kill("KILL", exec_pid)
76
+ end
77
+
78
+ exit!
79
+ end
80
+ end
81
+
82
+ # HUP == down, but don't cleanup.
83
+ trap("HUP") do
84
+ Process.kill("KILL", exec_pid)
85
+ exit!
86
+ end
87
+
88
+ while true
89
+ begin
90
+ Process.kill(0, parent_pid)
91
+ Process.kill(0, exec_pid)
92
+ rescue Exception
93
+ if block_given?
94
+ yield(exec_pid)
95
+ else
96
+ Process.kill("KILL", exec_pid)
97
+ end
98
+
99
+ exit!
100
+ end
101
+
102
+ sleep 1
103
+ end
104
+ end
105
+ end
106
+
107
+ def exec_server(cmd)
108
+ cmd.strip!
109
+ cmd.gsub!(/\\\n/, ' ')
110
+
111
+ @pid = self.class.exec_wait(cmd, allow_output: @allow_output, parent_pid: @parent_pid) do |child_pid|
112
+ Process.kill("KILL", child_pid)
113
+ cleanup!
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,61 @@
1
+ require 'tmpdir'
2
+
3
+ module IsolatedServer
4
+ class Mongodb < Base
5
+
6
+ attr_reader :dbpath, :port, :repl_set
7
+
8
+ def initialize(options = {})
9
+ super options
10
+ @dbpath = FileUtils.mkdir("#{@base}/data").first
11
+ end
12
+
13
+ def boot!
14
+ @port ||= grab_free_port
15
+
16
+ up!
17
+ end
18
+
19
+ def up!
20
+ mongod = locate_executable("mongod")
21
+
22
+ exec_server([
23
+ mongod,
24
+ '--dbpath', @dbpath,
25
+ '--port', @port,
26
+ *@params
27
+ ].shelljoin)
28
+
29
+ until up?
30
+ sleep(0.1)
31
+ end
32
+ end
33
+
34
+ def up?
35
+ begin
36
+ connection.ping
37
+ true
38
+ rescue Mongo::ConnectionFailure
39
+ false
40
+ end
41
+ end
42
+
43
+ def connection
44
+ @connection ||= connection_klass.new('localhost', @port)
45
+ end
46
+
47
+ def connection_klass
48
+ if Kernel.const_defined?("Mongo::MongoClient")
49
+ # 1.8.0+
50
+ Mongo::MongoClient
51
+ else
52
+ # < 1.8.0
53
+ Mongo::Connection
54
+ end
55
+ end
56
+
57
+ def console
58
+ system(['mongo', '--port', @port].shelljoin)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ require "java"
2
+ java_import "java.sql.DriverManager"
3
+ java_import "org.apache.commons.lang.StringEscapeUtils"
4
+
5
+ module IsolatedServer
6
+ class Mysql < Base
7
+ class WrappedJDBCConnection
8
+ def initialize(port)
9
+ @cx ||= DriverManager.get_connection("jdbc:mysql://127.0.0.1:#{port}/mysql", "root", "")
10
+ end
11
+
12
+ def query(sql)
13
+ stmt = @cx.create_statement
14
+ if sql !~ /^select/i && sql !~ /^show/i
15
+ return stmt.execute(sql)
16
+ end
17
+
18
+ rs = stmt.execute_query(sql)
19
+
20
+ rows = []
21
+ while (rs.next)
22
+ meta_data = rs.get_meta_data
23
+ num_cols = meta_data.get_column_count
24
+
25
+ row = {}
26
+ 1.upto(num_cols) do |col|
27
+ col_name = meta_data.get_column_label(col)
28
+ col_value = rs.get_object(col) # of meta_data.get_column_type(col)
29
+
30
+ row[col_name] = col_value
31
+ end
32
+
33
+ rows << row
34
+ end
35
+ rows
36
+ ensure
37
+ stmt.close if stmt
38
+ rs.close if rs
39
+ end
40
+
41
+ def escape(str)
42
+ StringEscapeUtils.escapeSql(str)
43
+ end
44
+ end
45
+
46
+ module DBConnection
47
+ def connection
48
+ @cx ||= WrappedJDBCConnection.new(@port)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ module IsolatedServer
2
+ class Mysql < Base
3
+ module DBConnection
4
+ def connection
5
+ require 'mysql2'
6
+ @cx ||= Mysql2::Client.new(:host => "127.0.0.1", :port => @port, :username => "root", :password => "", :database => "mysql")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,185 @@
1
+ require 'isolated_server'
2
+
3
+ if RUBY_PLATFORM == "java"
4
+ require 'isolated_server/mysql/jdbc_connection'
5
+ else
6
+ require 'isolated_server/mysql/mysql2_connection'
7
+ end
8
+
9
+ require 'tmpdir'
10
+
11
+ module IsolatedServer
12
+ class Mysql < Base
13
+
14
+ include DBConnection
15
+
16
+ attr_reader :server_id, :mysql_data_dir, :initial_binlog_file, :initial_binlog_pos
17
+
18
+ def initialize(options = {})
19
+ super options
20
+ @mysql_data_dir = "#{@base}/mysqld"
21
+ @mysql_socket = "#{@mysql_data_dir}/mysqld.sock"
22
+ @load_data_path = options[:data_path]
23
+ @log_bin = options[:log_bin] || "--log-bin"
24
+ @server_id = rand(2**31)
25
+ end
26
+
27
+ # For JRuby
28
+ # @todo Extract and genericize more of this into `Base`
29
+ def self.thread_boot(*params)
30
+ bin = [File.dirname(__FILE__) + "/../bin/boot_isolated_mysql_server"]
31
+ mysql_dir, mysql_port = nil, nil
32
+ restore_env = {}
33
+
34
+ if `which ruby` =~ (/rvm/)
35
+ bin = ["rvm", "1.8.7", "do", "ruby"] + bin
36
+ end
37
+
38
+ params = ["--pid", $$.to_s] + params
39
+
40
+ Thread.abort_on_exception = true
41
+ Thread.new do
42
+ ENV.keys.grep(/GEM|BUNDLE|RUBYOPT/).each do |k|
43
+ restore_env[k] = ENV.delete(k)
44
+ end
45
+ IO.popen(bin + params, "r") do |pipe|
46
+ mysql_dir = pipe.readline.split(' ').last
47
+ mysql_port = pipe.readline.split(' ').last.to_i
48
+ sleep
49
+ end
50
+ end
51
+
52
+ while mysql_port.nil?
53
+ sleep 1
54
+ end
55
+ new(:port => mysql_port, :base => mysql_dir)
56
+ end
57
+
58
+ def boot!
59
+ @port ||= grab_free_port
60
+
61
+ setup_data_dir
62
+ setup_binlog
63
+ setup_tmp_dir
64
+
65
+ up!
66
+
67
+ record_initial_master_position
68
+
69
+ setup_time_zone
70
+ setup_server_id
71
+ end
72
+
73
+ def up!
74
+ exec_server([
75
+ locate_executable("mysqld"),
76
+ '--no-defaults',
77
+ '--default-storage-engine=innodb',
78
+ "--datadir=#{@mysql_data_dir}",
79
+ "--pid-file=#{@base}/mysqld.pid",
80
+ "--port=#{@port}",
81
+ "--socket=#{@mysql_data_dir}/mysql.sock",
82
+ @log_bin,
83
+ '--log-slave-updates',
84
+ *@params
85
+ ].shelljoin)
86
+
87
+ sleep(0.1) until up?
88
+ end
89
+
90
+ def up?
91
+ system("mysql -h127.0.0.1 --port=#{@port.to_s.shellescape} --database=mysql -u root -e 'select 1' >/dev/null 2>&1")
92
+ end
93
+
94
+ def console
95
+ system("mysql -uroot --port #{@port.to_s.shellescape} mysql --host 127.0.0.1")
96
+ end
97
+
98
+ def reconnect!
99
+ @cx = nil
100
+ connection
101
+ end
102
+
103
+ def make_slave_of(master)
104
+ binlog_file = master.initial_binlog_file || (@log_bin.split('/').last + ".000001")
105
+ binlog_pos = master.initial_binlog_pos || 4
106
+
107
+ connection.query(<<-EOL
108
+ CHANGE MASTER TO MASTER_HOST='127.0.0.1',
109
+ MASTER_PORT=#{master.port},
110
+ MASTER_USER='root', MASTER_PASSWORD='',
111
+ MASTER_LOG_FILE='#{binlog_file}',
112
+ MASTER_LOG_POS=#{binlog_pos}
113
+ EOL
114
+ )
115
+ connection.query("SLAVE START")
116
+ connection.query("SET GLOBAL READ_ONLY=1")
117
+ end
118
+
119
+ def set_rw(rw)
120
+ ro = rw ? 0 : 1
121
+ connection.query("SET GLOBAL READ_ONLY=#{ro}")
122
+ end
123
+
124
+ private
125
+
126
+ def setup_data_dir
127
+ system("rm -Rf #{@mysql_data_dir.shellescape}")
128
+ system("mkdir #{@mysql_data_dir.shellescape}")
129
+ if @load_data_path
130
+ system("cp -a #{@load_data_path.shellescape}/* #{@mysql_data_dir.shellescape}")
131
+ system("rm -f #{@mysql_data_dir.shellescape}/relay-log.info")
132
+ else
133
+ mysql_install_db = locate_executable("mysql_install_db")
134
+
135
+ idb_path = File.dirname(mysql_install_db)
136
+ system("(cd #{idb_path.shellescape}/..; mysql_install_db --datadir=#{@mysql_data_dir.shellescape} --user=`whoami`) >/dev/null 2>&1")
137
+ system("cp #{File.expand_path(File.dirname(__FILE__)).shellescape}/mysql/tables/user.* #{@mysql_data_dir.shellescape}/mysql")
138
+ end
139
+ end
140
+
141
+ def setup_binlog
142
+ if !@log_bin
143
+ @log_bin = "--log-bin"
144
+ else
145
+ if @log_bin[0] != '/'
146
+ binlog_dir = "#{@mysql_data_dir}/#{@log_bin}"
147
+ else
148
+ binlog_dir = @log_bin
149
+ end
150
+
151
+ system("mkdir -p #{binlog_dir.shellescape}")
152
+ @log_bin = "--log-bin=#{binlog_dir}"
153
+ end
154
+ end
155
+
156
+ # http://dev.mysql.com/doc/refman/5.0/en/temporary-files.html
157
+ def setup_tmp_dir
158
+ system("mkdir -p #{base.shellescape}/tmp")
159
+ system("chmod 0777 #{base.shellescape}/tmp")
160
+ ENV["TMPDIR"] = "#{base.shellescape}/tmp"
161
+ end
162
+
163
+ # http://dev.mysql.com/doc/refman/5.5/en/mysql-tzinfo-to-sql.html
164
+ def setup_time_zone
165
+ tzinfo_to_sql = locate_executable("mysql_tzinfo_to_sql5", "mysql_tzinfo_to_sql")
166
+ raise "could not find mysql_tzinfo_to_sql" unless tzinfo_to_sql
167
+ system("#{tzinfo_to_sql.shellescape} /usr/share/zoneinfo 2>/dev/null | mysql -h127.0.0.1 --database=mysql --port=#{@port.to_s.shellescape} -u root mysql")
168
+
169
+ begin
170
+ connection.query("SET GLOBAL time_zone='UTC'")
171
+ rescue Mysql2::Error
172
+ connection.query("SET GLOBAL time_zone='UTC'")
173
+ end
174
+ end
175
+
176
+ def setup_server_id
177
+ connection.query("SET GLOBAL server_id=#{@server_id}")
178
+ end
179
+
180
+ def record_initial_master_position
181
+ master_binlog_info = connection.query("show master status").first
182
+ @initial_binlog_file, @initial_binlog_pos = master_binlog_info.values_at('File', 'Position')
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,3 @@
1
+ module IsolatedServer
2
+ VERSION = "0.4.2"
3
+ end
@@ -0,0 +1,19 @@
1
+ require 'isolated_server/base'
2
+ require 'isolated_server/version'
3
+ require 'shellwords'
4
+
5
+ # Load support for databases if their corresponding gem is available
6
+ begin
7
+ require 'mongo'
8
+ require 'isolated_server/mongodb'
9
+ rescue LoadError
10
+ end
11
+
12
+ begin
13
+ require 'mysql2'
14
+ require 'isolated_server/mysql'
15
+ rescue LoadError
16
+ end
17
+
18
+ module IsolatedServer
19
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe IsolatedServer::Mongodb do
4
+ subject { IsolatedServer::Mongodb.new }
5
+ let(:collection) { subject.connection['test_db']['test_col'] }
6
+ describe "#boot!" do
7
+ it "starts up a new server" do
8
+ subject.boot!
9
+ expect(subject.pid).to be_a_running_process
10
+
11
+ id = nil
12
+ expect { id = collection.insert(name: 'Bob') }.
13
+ to change { collection.count }.
14
+ from(0).
15
+ to(1)
16
+
17
+ result = collection.find_one(id)
18
+ expect(result['name']).to eq('Bob')
19
+
20
+ subject.down!
21
+ expect(subject.pid).not_to be_a_running_process
22
+ expect(subject).not_to be_up
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe IsolatedServer::Mysql do
4
+ subject { IsolatedServer::Mysql.new }
5
+ describe "#boot!" do
6
+ it "starts up a new server" do
7
+ subject.boot!
8
+ expect(subject.pid).to be_a_running_process
9
+ expect(subject).to be_up
10
+
11
+ # Make sure we are connected, queryable, and reasonably confident that global variables match our expectations
12
+ expect(query_mysql_global_variable(subject, 'port').to_i).to eq(subject.port)
13
+ expect(query_mysql_global_variable(subject, 'server_id').to_i).to eq(subject.server_id)
14
+ expect(query_mysql_global_variable(subject, 'datadir').chomp('/')).to eq(subject.mysql_data_dir)
15
+ expect(query_mysql_global_variable(subject, 'time_zone')).to eq('UTC')
16
+
17
+ expect { subject.set_rw(false) }.
18
+ to change { query_mysql_global_variable(subject, 'read_only') }.
19
+ from('OFF').
20
+ to('ON')
21
+
22
+ subject.down!
23
+ expect(subject.pid).not_to be_a_running_process
24
+ expect(subject).not_to be_up
25
+ end
26
+ end
27
+
28
+ describe "#make_slave_of" do
29
+ let(:master) { IsolatedServer::Mysql.new }
30
+ let(:slave) { IsolatedServer::Mysql.new }
31
+ let(:db_name) { 'integration_test' }
32
+ let(:person_name) { 'Bob' }
33
+
34
+ it "sets establishes the relationship" do
35
+ master.boot!
36
+ slave.boot!
37
+
38
+ slave.make_slave_of(master)
39
+ sleep 0.1
40
+ expect(slave_status(slave)['Slave_IO_State']).to eq("Waiting for master to send event")
41
+
42
+ master.connection.query("CREATE DATABASE #{db_name}")
43
+ master.connection.select_db(db_name)
44
+ master.connection.query(create_table_sql)
45
+ master.connection.query("INSERT INTO people SET name = '#{person_name}'")
46
+ sleep 0.1
47
+
48
+ slave.connection.select_db(db_name)
49
+ slave_result = slave.connection.query('SELECT name FROM people').first
50
+ expect(slave_result['name']).to eq person_name
51
+
52
+ master.down!
53
+ slave.down!
54
+
55
+ end
56
+ end
57
+
58
+ def query_mysql_global_variable(isolated_database, key)
59
+ isolated_database.
60
+ connection.
61
+ query("SHOW GLOBAL VARIABLES LIKE '#{key}'").
62
+ first["Value"]
63
+ end
64
+
65
+ def slave_status(isolated_database)
66
+ isolated_database.connection.query('SHOW SLAVE STATUS').first
67
+ end
68
+
69
+ def create_table_sql
70
+ <<-eos
71
+ CREATE TABLE `people` (
72
+ `id` int(11) NOT NULL AUTO_INCREMENT,
73
+ `name` varchar(255) DEFAULT NULL,
74
+ PRIMARY KEY (`id`)
75
+ ) ENGINE=InnoDB;
76
+ eos
77
+ end
78
+
79
+ end
@@ -0,0 +1,83 @@
1
+ require 'isolated_server'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
6
+ # file to always be loaded, without a need to explicitly require it in any files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, make a
12
+ # separate helper file that requires this one and then use it only in the specs
13
+ # that actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].sort.each { |f| require f }
21
+
22
+ RSpec.configure do |config|
23
+ # The settings below are suggested to provide a good initial experience
24
+ # with RSpec, but feel free to customize to your heart's content.
25
+ =begin
26
+ # These two settings work together to allow you to limit a spec run
27
+ # to individual examples or groups you care about by tagging them with
28
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
29
+ # get run.
30
+ config.filter_run :focus
31
+ config.run_all_when_everything_filtered = true
32
+
33
+ # Many RSpec users commonly either run the entire suite or an individual
34
+ # file, and it's useful to allow more verbose output when running an
35
+ # individual spec file.
36
+ if config.files_to_run.one?
37
+ # Use the documentation formatter for detailed output,
38
+ # unless a formatter has already been configured
39
+ # (e.g. via a command-line flag).
40
+ config.default_formatter = 'doc'
41
+ end
42
+
43
+ # Print the 10 slowest examples and example groups at the
44
+ # end of the spec run, to help surface which specs are running
45
+ # particularly slow.
46
+ config.profile_examples = 10
47
+
48
+ # Run specs in random order to surface order dependencies. If you find an
49
+ # order dependency and want to debug it, you can fix the order by providing
50
+ # the seed, which is printed after each run.
51
+ # --seed 1234
52
+ config.order = :random
53
+
54
+ # Seed global randomization in this process using the `--seed` CLI option.
55
+ # Setting this allows you to use `--seed` to deterministically reproduce
56
+ # test failures related to randomization by passing the same `--seed` value
57
+ # as the one that triggered the failure.
58
+ Kernel.srand config.seed
59
+
60
+ # rspec-expectations config goes here. You can use an alternate
61
+ # assertion/expectation library such as wrong or the stdlib/minitest
62
+ # assertions if you prefer.
63
+ config.expect_with :rspec do |expectations|
64
+ # Enable only the newer, non-monkey-patching expect syntax.
65
+ # For more details, see:
66
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
67
+ expectations.syntax = :expect
68
+ end
69
+
70
+ # rspec-mocks config goes here. You can use an alternate test double
71
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
72
+ config.mock_with :rspec do |mocks|
73
+ # Enable only the newer, non-monkey-patching expect syntax.
74
+ # For more details, see:
75
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
76
+ mocks.syntax = :expect
77
+
78
+ # Prevents you from mocking or stubbing a method that does not exist on
79
+ # a real object. This is generally recommended.
80
+ mocks.verify_partial_doubles = true
81
+ end
82
+ =end
83
+ end
@@ -0,0 +1,12 @@
1
+ require 'rspec/expectations'
2
+
3
+ RSpec::Matchers.define :be_a_running_process do
4
+ match do |pid|
5
+ begin
6
+ Process.kill 0, pid
7
+ true
8
+ rescue Errno::EPERM, Errno::ESRCH
9
+ false
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: isolated_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
5
+ platform: ruby
6
+ authors:
7
+ - Ben Osheroff
8
+ - Gabe Martin-Dempesy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-12-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.5'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.5'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: pry
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: appraisal
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: A small library that allows you to easily spin up new local mysql servers
85
+ for testing purposes.
86
+ email:
87
+ - ben@zendesk.com
88
+ - gabe@zendesk.com
89
+ executables:
90
+ - boot_isolated_mysql_server
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - ".travis.yml"
97
+ - Appraisals
98
+ - Gemfile
99
+ - LICENSE.txt
100
+ - README.md
101
+ - Rakefile
102
+ - bin/boot_isolated_mysql_server
103
+ - gemfiles/mongo_1.7.gemfile
104
+ - gemfiles/mongo_latest.gemfile
105
+ - isolated_server.gemspec
106
+ - lib/isolated_server.rb
107
+ - lib/isolated_server/base.rb
108
+ - lib/isolated_server/mongodb.rb
109
+ - lib/isolated_server/mysql.rb
110
+ - lib/isolated_server/mysql/jdbc_connection.rb
111
+ - lib/isolated_server/mysql/mysql2_connection.rb
112
+ - lib/isolated_server/mysql/tables/user.MYD
113
+ - lib/isolated_server/mysql/tables/user.MYI
114
+ - lib/isolated_server/mysql/tables/user.frm
115
+ - lib/isolated_server/version.rb
116
+ - spec/lib/isolated_server/mongodb_spec.rb
117
+ - spec/lib/isolated_server/mysql_spec.rb
118
+ - spec/spec_helper.rb
119
+ - spec/support/process_matcher.rb
120
+ homepage: http://github.com/gabetax/isolated_server
121
+ licenses: []
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.2.2
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: A small library that allows you to easily spin up new local mysql servers
143
+ for testing purposes.
144
+ test_files:
145
+ - spec/lib/isolated_server/mongodb_spec.rb
146
+ - spec/lib/isolated_server/mysql_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/process_matcher.rb