isolated_server 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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