|
1 | | -using Test, MySQL, DBInterface, Tables, Dates, DecFP |
| 1 | +using Test, MySQL, DBInterface, Tables, Dates, DecFP, Harbor, Sockets |
2 | 2 |
|
3 | | -conn = DBInterface.connect(MySQL.Connection, "127.0.0.1", "root", ""; port=3306) |
| 3 | +const MYSQL_IMAGE_REF = get(ENV, "MYSQL_IMAGE", "mysql:8") |
| 4 | +const MYSQL_TEST_USER = "root" |
| 5 | +const MYSQL_TEST_PASSWORD = "" |
| 6 | + |
| 7 | +struct MySQLTestConfig |
| 8 | + host::String |
| 9 | + port::Int |
| 10 | + user::String |
| 11 | + password::String |
| 12 | +end |
| 13 | + |
| 14 | +const TEST_CONFIG = Ref{Union{Nothing, MySQLTestConfig}}(nothing) |
| 15 | +const TEST_OPTION_FILE = Ref{Union{Nothing, String}}(nothing) |
| 16 | + |
| 17 | +function parse_image_ref(ref::String) |
| 18 | + slash = findlast('/', ref) |
| 19 | + colon = findlast(':', ref) |
| 20 | + if colon !== nothing && (slash === nothing || colon > slash) |
| 21 | + return String(ref[begin:prevind(ref, colon)]), String(ref[nextind(ref, colon):end]) |
| 22 | + end |
| 23 | + return ref, "latest" |
| 24 | +end |
| 25 | + |
| 26 | +function docker_available() |
| 27 | + Sys.which("docker") === nothing && return false |
| 28 | + try |
| 29 | + run(pipeline(`docker info`, stdout=devnull, stderr=devnull)) |
| 30 | + return true |
| 31 | + catch |
| 32 | + return false |
| 33 | + end |
| 34 | +end |
| 35 | + |
| 36 | +function pick_port() |
| 37 | + server = Sockets.listen(Sockets.IPv4(0), 0) |
| 38 | + _, port = Sockets.getsockname(server) |
| 39 | + port = Int(port) |
| 40 | + close(server) |
| 41 | + return port |
| 42 | +end |
| 43 | + |
| 44 | +test_config() = something(TEST_CONFIG[]) |
| 45 | +test_host() = test_config().host |
| 46 | +test_port() = test_config().port |
| 47 | +test_user() = test_config().user |
| 48 | +test_password() = test_config().password |
| 49 | +test_option_file() = something(TEST_OPTION_FILE[]) |
| 50 | + |
| 51 | +connect_mysql(; kw...) = DBInterface.connect(MySQL.Connection, test_host(), test_user(), test_password(); port=test_port(), kw...) |
| 52 | + |
| 53 | +function wait_for_connection(cfg::MySQLTestConfig; timeout::Float64=90.0) |
| 54 | + start_time = time() |
| 55 | + last_err = nothing |
| 56 | + while time() - start_time < timeout |
| 57 | + try |
| 58 | + return DBInterface.connect(MySQL.Connection, cfg.host, cfg.user, cfg.password; port=cfg.port, connect_timeout=2) |
| 59 | + catch err |
| 60 | + last_err = err |
| 61 | + sleep(0.5) |
| 62 | + end |
| 63 | + end |
| 64 | + last_err === nothing && error("MySQL did not become ready") |
| 65 | + error("MySQL did not become ready: $(sprint(showerror, last_err))") |
| 66 | +end |
| 67 | + |
| 68 | +function write_option_file(dir::AbstractString, cfg::MySQLTestConfig) |
| 69 | + path = joinpath(dir, "my.ini") |
| 70 | + open(path, "w") do io |
| 71 | + print(io, """ |
| 72 | +[client] |
| 73 | +host=$(cfg.host) |
| 74 | +user=$(cfg.user) |
| 75 | +port=$(cfg.port) |
| 76 | +connect_timeout=30 |
| 77 | +report-data-truncation=true |
| 78 | +password = $(repr(cfg.password)) |
| 79 | +""") |
| 80 | + end |
| 81 | + return path |
| 82 | +end |
| 83 | + |
| 84 | +function with_mysql(f::Function) |
| 85 | + image, tag = parse_image_ref(MYSQL_IMAGE_REF) |
| 86 | + host_port = pick_port() |
| 87 | + env = Dict("MYSQL_ALLOW_EMPTY_PASSWORD" => "yes") |
| 88 | + return Harbor.with_container( |
| 89 | + image; |
| 90 | + tag=tag, |
| 91 | + ports=Dict(3306 => host_port), |
| 92 | + environment=env, |
| 93 | + # Harbor's port wait is enough here because we also poll for a real client connection below. |
| 94 | + wait_strategy=(port=3306,), |
| 95 | + wait_timeout=120.0, |
| 96 | + ) do _ |
| 97 | + cfg = MySQLTestConfig("127.0.0.1", host_port, MYSQL_TEST_USER, MYSQL_TEST_PASSWORD) |
| 98 | + conn = wait_for_connection(cfg) |
| 99 | + DBInterface.close!(conn) |
| 100 | + return f(cfg) |
| 101 | + end |
| 102 | +end |
| 103 | + |
| 104 | +@testset "MySQL" begin |
| 105 | + |
| 106 | +let mysql = MySQL.API.init() |
| 107 | + MySQL.setoptions!(mysql) |
| 108 | + @test MySQL.API.getoption(mysql, MySQL.API.MYSQL_OPT_SSL_VERIFY_SERVER_CERT) == false |
| 109 | + MySQL.setoptions!(mysql; ssl_verify_server_cert=true) |
| 110 | + @test MySQL.API.getoption(mysql, MySQL.API.MYSQL_OPT_SSL_VERIFY_SERVER_CERT) == true |
| 111 | +end |
| 112 | + |
| 113 | +if !docker_available() |
| 114 | + @info "Docker not available; skipping MySQL integration tests." |
| 115 | + @test true |
| 116 | +else |
| 117 | + with_mysql() do cfg |
| 118 | + TEST_CONFIG[] = cfg |
| 119 | + mktempdir() do dir |
| 120 | + TEST_OPTION_FILE[] = write_option_file(dir, cfg) |
| 121 | + |
| 122 | +conn = connect_mysql() |
4 | 123 | DBInterface.close!(conn) |
5 | 124 |
|
6 | 125 | # https://github.com/JuliaDatabases/MySQL.jl/issues/170 |
7 | | -conn = DBInterface.connect(MySQL.Connection, "mysql://127.0.0.1", "root"; port=3306) |
| 126 | +conn = DBInterface.connect(MySQL.Connection, string("mysql://", test_host()), test_user(); port=test_port()) |
8 | 127 | DBInterface.close!(conn) |
9 | 128 |
|
10 | 129 | # AbstractString as a connection parameter or an option |
11 | | -conn = DBInterface.connect(MySQL.Connection, SubString("127.0.0.1"), SubString("root"), SubString(""); port=3306, charset_name=SubString("utf8mb4")) |
| 130 | +conn = DBInterface.connect(MySQL.Connection, SubString(test_host()), SubString(test_user()), SubString(test_password()); port=test_port(), charset_name=SubString("utf8mb4")) |
12 | 131 | DBInterface.close!(conn) |
13 | 132 |
|
14 | 133 | # load host/user + options from file |
15 | | -conn = DBInterface.connect(MySQL.Connection, "", ""; option_file=joinpath(dirname(pathof(MySQL)), "../test/", "my.ini")) |
| 134 | +conn = DBInterface.connect(MySQL.Connection, "", ""; port=0, option_file=test_option_file()) |
16 | 135 | @test isopen(conn) |
17 | 136 |
|
18 | 137 | DBInterface.execute(conn, "DROP DATABASE if exists mysqltest") |
@@ -372,15 +491,15 @@ ret = columntable(res) |
372 | 491 | @test_throws ArgumentError MySQL.load(ct, conn, "test194") |
373 | 492 |
|
374 | 493 | @testset "transactions" begin |
375 | | - conn = DBInterface.connect(MySQL.Connection, "127.0.0.1", "root", ""; port=3306) |
| 494 | + conn = connect_mysql() |
376 | 495 | try |
377 | 496 | DBInterface.execute(conn, "DROP DATABASE if exists mysqltest") |
378 | 497 | DBInterface.execute(conn, "CREATE DATABASE mysqltest") |
379 | 498 | DBInterface.execute(conn, "use mysqltest") |
380 | 499 | DBInterface.execute(conn, "DROP TABLE IF EXISTS TransactionTest") |
381 | 500 | DBInterface.execute(conn, "CREATE TABLE TransactionTest (a int)") |
382 | 501 |
|
383 | | - conn2 = DBInterface.connect(MySQL.Connection, "127.0.0.1", "root", ""; port=3306) |
| 502 | + conn2 = connect_mysql() |
384 | 503 | DBInterface.execute(conn2, "use mysqltest") |
385 | 504 |
|
386 | 505 | try |
@@ -414,4 +533,8 @@ ret = columntable(res) |
414 | 533 | finally |
415 | 534 | DBInterface.close!(conn) |
416 | 535 | end |
| 536 | +end |
| 537 | + end |
| 538 | + end |
| 539 | +end |
417 | 540 | end |
0 commit comments