Skip to content

Commit d48c04a

Browse files
authored
Ruby integration tests (#147)
* Ruby integration tests * forgot a file * refactor * refactoring * more refactoring * remove config helper * try multiple databases * fix * more databases * Use pg stats * ports * speed * Fix tests * preload library * comment
1 parent 2628dec commit d48c04a

File tree

13 files changed

+639
-165
lines changed

13 files changed

+639
-165
lines changed

.circleci/config.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,34 @@ jobs:
1515
RUSTFLAGS: "-C instrument-coverage"
1616
LLVM_PROFILE_FILE: "pgcat-%m.profraw"
1717
- image: postgres:14
18-
# auth:
19-
# username: mydockerhub-user
20-
# password: $DOCKERHUB_PASSWORD
18+
command: ["postgres", "-p", "5432", "-c", "shared_preload_libraries=pg_stat_statements"]
2119
environment:
2220
POSTGRES_USER: postgres
2321
POSTGRES_DB: postgres
2422
POSTGRES_PASSWORD: postgres
2523
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
24+
- image: postgres:14
25+
command: ["postgres", "-p", "7432", "-c", "shared_preload_libraries=pg_stat_statements"]
26+
environment:
27+
POSTGRES_USER: postgres
28+
POSTGRES_DB: postgres
29+
POSTGRES_PASSWORD: postgres
30+
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
31+
- image: postgres:14
32+
command: ["postgres", "-p", "8432", "-c", "shared_preload_libraries=pg_stat_statements"]
33+
environment:
34+
POSTGRES_USER: postgres
35+
POSTGRES_DB: postgres
36+
POSTGRES_PASSWORD: postgres
37+
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
38+
- image: postgres:14
39+
command: ["postgres", "-p", "9432", "-c", "shared_preload_libraries=pg_stat_statements"]
40+
environment:
41+
POSTGRES_USER: postgres
42+
POSTGRES_DB: postgres
43+
POSTGRES_PASSWORD: postgres
44+
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
45+
2646
# Add steps to the job
2747
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
2848
steps:

.circleci/run_tests.sh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ function start_pgcat() {
1616

1717
# Setup the database with shards and user
1818
PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_routing_setup.sql
19+
PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 7432 -U postgres -f tests/sharding/query_routing_setup.sql
20+
PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 8432 -U postgres -f tests/sharding/query_routing_setup.sql
21+
PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 9432 -U postgres -f tests/sharding/query_routing_setup.sql
1922

2023
PGPASSWORD=sharding_user pgbench -h 127.0.0.1 -U sharding_user shard0 -i
2124
PGPASSWORD=sharding_user pgbench -h 127.0.0.1 -U sharding_user shard1 -i
@@ -26,7 +29,7 @@ wget -O toxiproxy-2.4.0.deb https://github.com/Shopify/toxiproxy/releases/downlo
2629
sudo dpkg -i toxiproxy-2.4.0.deb
2730

2831
# Start Toxiproxy
29-
toxiproxy-server &
32+
LOG_LEVEL=error toxiproxy-server &
3033
sleep 1
3134

3235
# Create a database at port 5433, forward it to Postgres
@@ -87,7 +90,8 @@ kill -SIGHUP $(pgrep pgcat) # Reload config again
8790
cd tests/ruby
8891
sudo gem install bundler
8992
bundle install
90-
ruby tests.rb
93+
bundle exec ruby tests.rb
94+
bundle exec rspec *_spec.rb
9195
cd ../..
9296

9397
#
@@ -105,9 +109,9 @@ psql -U admin_user -e -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW STATS' > /dev/n
105109
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'RELOAD' > /dev/null
106110
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW CONFIG' > /dev/null
107111
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW DATABASES' > /dev/null
108-
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW LISTS' > /dev/null
109-
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW POOLS' > /dev/null
110-
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW VERSION' > /dev/null
112+
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgcat -c 'SHOW LISTS' > /dev/null
113+
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgcat -c 'SHOW POOLS' > /dev/null
114+
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgcat -c 'SHOW VERSION' > /dev/null
111115
psql -U admin_user -h 127.0.0.1 -p 6432 -d pgbouncer -c "SET client_encoding TO 'utf8'" > /dev/null # will ignore
112116
(! psql -U admin_user -e -h 127.0.0.1 -p 6432 -d random_db -c 'SHOW STATS' > /dev/null)
113117
export PGPASSWORD=sharding_user

tests/ruby/Gemfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
source "https://rubygems.org"
22

33
gem "pg"
4-
gem "activerecord"
4+
gem "toml"
5+
gem "rspec"
56
gem "rubocop"
6-
gem "toml", "~> 0.3.0"
7+
gem "toxiproxy"
8+
gem "activerecord"

tests/ruby/Gemfile.lock

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ GEM
1313
tzinfo (~> 2.0)
1414
ast (2.4.2)
1515
concurrent-ruby (1.1.10)
16+
diff-lcs (1.5.0)
1617
i18n (1.11.0)
1718
concurrent-ruby (~> 1.0)
1819
minitest (5.16.2)
@@ -24,6 +25,19 @@ GEM
2425
rainbow (3.1.1)
2526
regexp_parser (2.3.1)
2627
rexml (3.2.5)
28+
rspec (3.11.0)
29+
rspec-core (~> 3.11.0)
30+
rspec-expectations (~> 3.11.0)
31+
rspec-mocks (~> 3.11.0)
32+
rspec-core (3.11.0)
33+
rspec-support (~> 3.11.0)
34+
rspec-expectations (3.11.0)
35+
diff-lcs (>= 1.2.0, < 2.0)
36+
rspec-support (~> 3.11.0)
37+
rspec-mocks (3.11.1)
38+
diff-lcs (>= 1.2.0, < 2.0)
39+
rspec-support (~> 3.11.0)
40+
rspec-support (3.11.0)
2741
rubocop (1.29.0)
2842
parallel (~> 1.10)
2943
parser (>= 3.1.0.0)
@@ -38,19 +52,23 @@ GEM
3852
ruby-progressbar (1.11.0)
3953
toml (0.3.0)
4054
parslet (>= 1.8.0, < 3.0.0)
55+
toxiproxy (2.0.1)
4156
tzinfo (2.0.4)
4257
concurrent-ruby (~> 1.0)
4358
unicode-display_width (2.1.0)
4459

4560
PLATFORMS
61+
aarch64-linux
4662
arm64-darwin-21
4763
x86_64-linux
4864

4965
DEPENDENCIES
5066
activerecord
5167
pg
68+
rspec
5269
rubocop
53-
toml (~> 0.3.0)
70+
toml
71+
toxiproxy
5472

5573
BUNDLED WITH
56-
2.3.7
74+
2.3.21

tests/ruby/helpers/pg_instance.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
require 'pg'
2+
require 'toxiproxy'
3+
4+
class PgInstance
5+
attr_reader :port
6+
attr_reader :username
7+
attr_reader :password
8+
attr_reader :database_name
9+
10+
def initialize(port, username, password, database_name)
11+
@original_port = port
12+
@toxiproxy_port = 10000 + port.to_i
13+
@port = @toxiproxy_port
14+
15+
@username = username
16+
@password = password
17+
@database_name = database_name
18+
@toxiproxy_name = "database_#{@original_port}"
19+
Toxiproxy.populate([{
20+
name: @toxiproxy_name,
21+
listen: "0.0.0.0:#{@toxiproxy_port}",
22+
upstream: "localhost:#{@original_port}",
23+
}])
24+
25+
# Toxiproxy server will outlive our PgInstance objects
26+
# so we want to destroy our proxies before exiting
27+
# Ruby finalizer is ideal for doing this
28+
ObjectSpace.define_finalizer(@toxiproxy_name, proc { Toxiproxy[@toxiproxy_name].destroy })
29+
end
30+
31+
def with_connection
32+
conn = PG.connect("postgres://#{@username}:#{@password}@localhost:#{port}/#{database_name}")
33+
yield conn
34+
ensure
35+
conn&.close
36+
end
37+
38+
def reset
39+
reset_toxics
40+
reset_stats
41+
end
42+
43+
def toxiproxy
44+
Toxiproxy[@toxiproxy_name]
45+
end
46+
47+
def take_down
48+
if block_given?
49+
Toxiproxy[@toxiproxy_name].toxic(:limit_data, bytes: 5).apply { yield }
50+
else
51+
Toxiproxy[@toxiproxy_name].toxic(:limit_data, bytes: 5).toxics.each(&:save)
52+
end
53+
end
54+
55+
def add_latency(latency)
56+
if block_given?
57+
Toxiproxy[@toxiproxy_name].toxic(:latency, latency: latency).apply { yield }
58+
else
59+
Toxiproxy[@toxiproxy_name].toxic(:latency, latency: latency).toxics.each(&:save)
60+
end
61+
end
62+
63+
def delete_proxy
64+
Toxiproxy[@toxiproxy_name].delete
65+
end
66+
67+
def reset_toxics
68+
Toxiproxy[@toxiproxy_name].toxics.each(&:destroy)
69+
end
70+
71+
def reset_stats
72+
with_connection { |c| c.async_exec("SELECT pg_stat_statements_reset()") }
73+
end
74+
75+
def count_query(query)
76+
with_connection { |c| c.async_exec("SELECT SUM(calls) FROM pg_stat_statements WHERE query = '#{query}'")[0]["sum"].to_i }
77+
end
78+
79+
def count_select_1_plus_2
80+
with_connection { |c| c.async_exec("SELECT SUM(calls) FROM pg_stat_statements WHERE query = 'SELECT $1 + $2'")[0]["sum"].to_i }
81+
end
82+
end

tests/ruby/helpers/pgcat_helper.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
require 'json'
2+
require 'ostruct'
3+
require_relative 'pgcat_process'
4+
require_relative 'pg_instance'
5+
6+
module Helpers
7+
module Pgcat
8+
def self.three_shard_setup(pool_name, pool_size)
9+
user = {
10+
"password" => "sharding_user",
11+
"pool_size" => pool_size,
12+
"statement_timeout" => 0,
13+
"username" => "sharding_user"
14+
}
15+
16+
pgcat = PgcatProcess.new("info")
17+
primary0 = PgInstance.new(5432, user["username"], user["password"], "shard0")
18+
primary1 = PgInstance.new(7432, user["username"], user["password"], "shard1")
19+
primary2 = PgInstance.new(8432, user["username"], user["password"], "shard2")
20+
21+
pgcat_cfg = pgcat.current_config
22+
pgcat_cfg["pools"] = {
23+
"#{pool_name}" => {
24+
"default_role" => "any",
25+
"pool_mode" => "transaction",
26+
"primary_reads_enabled" => false,
27+
"query_parser_enabled" => false,
28+
"sharding_function" => "pg_bigint_hash",
29+
"shards" => {
30+
"0" => { "database" => "shard0", "servers" => [["localhost", primary0.port.to_s, "primary"]] },
31+
"1" => { "database" => "shard1", "servers" => [["localhost", primary1.port.to_s, "primary"]] },
32+
"2" => { "database" => "shard2", "servers" => [["localhost", primary2.port.to_s, "primary"]] },
33+
},
34+
"users" => { "0" => user }
35+
}
36+
}
37+
pgcat.update_config(pgcat_cfg)
38+
39+
pgcat.start
40+
pgcat.wait_until_ready
41+
42+
OpenStruct.new.tap do |struct|
43+
struct.pgcat = pgcat
44+
struct.shards = [primary0, primary1, primary2]
45+
struct.all_databases = [primary0, primary1, primary2]
46+
end
47+
end
48+
49+
def self.single_shard_setup(pool_name, pool_size)
50+
user = {
51+
"password" => "sharding_user",
52+
"pool_size" => pool_size,
53+
"statement_timeout" => 0,
54+
"username" => "sharding_user"
55+
}
56+
57+
pgcat = PgcatProcess.new("info")
58+
pgcat_cfg = pgcat.current_config
59+
60+
primary = PgInstance.new(5432, user["username"], user["password"], "shard0")
61+
replica0 = PgInstance.new(7432, user["username"], user["password"], "shard0")
62+
replica1 = PgInstance.new(8432, user["username"], user["password"], "shard0")
63+
replica2 = PgInstance.new(9432, user["username"], user["password"], "shard0")
64+
65+
# Main proxy configs
66+
pgcat_cfg["pools"] = {
67+
"#{pool_name}" => {
68+
"default_role" => "any",
69+
"pool_mode" => "transaction",
70+
"primary_reads_enabled" => false,
71+
"query_parser_enabled" => false,
72+
"sharding_function" => "pg_bigint_hash",
73+
"shards" => {
74+
"0" => {
75+
"database" => "shard0",
76+
"servers" => [
77+
["localhost", primary.port.to_s, "primary"],
78+
["localhost", replica0.port.to_s, "replica"],
79+
["localhost", replica1.port.to_s, "replica"],
80+
["localhost", replica2.port.to_s, "replica"]
81+
]
82+
},
83+
},
84+
"users" => { "0" => user }
85+
}
86+
}
87+
pgcat_cfg["general"]["port"] = pgcat.port
88+
pgcat.update_config(pgcat_cfg)
89+
pgcat.start
90+
pgcat.wait_until_ready
91+
92+
OpenStruct.new.tap do |struct|
93+
struct.pgcat = pgcat
94+
struct.primary = primary
95+
struct.replicas = [replica0, replica1, replica2]
96+
struct.all_databases = [primary, replica0, replica1, replica2]
97+
end
98+
end
99+
end
100+
end

0 commit comments

Comments
 (0)