From c0314bfe2ce517e8dceee58525de49bf2f08af3b Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Mon, 24 May 2021 15:01:03 -0400 Subject: [PATCH 01/13] Try to split out tests and test dependencies into separate projects --- dub.sdl | 61 ++---------------- integration-tests/dub.sdl | 21 ++++++ integration-tests/source/app.d | 3 + .../source}/mysql/test/common.d | 12 +++- .../source}/mysql/test/integration.d | 0 .../source}/mysql/test/regression.d | 0 testconn/.dub.sdl.swp | Bin 0 -> 12288 bytes testconn/dub.sdl | 7 ++ testconn/source/.app.d.swp | Bin 0 -> 12288 bytes {source => testconn/source}/app.d | 0 10 files changed, 45 insertions(+), 59 deletions(-) create mode 100644 integration-tests/dub.sdl create mode 100644 integration-tests/source/app.d rename {source => integration-tests/source}/mysql/test/common.d (90%) rename {source => integration-tests/source}/mysql/test/integration.d (100%) rename {source => integration-tests/source}/mysql/test/regression.d (100%) create mode 100644 testconn/.dub.sdl.swp create mode 100644 testconn/dub.sdl create mode 100644 testconn/source/.app.d.swp rename {source => testconn/source}/app.d (100%) diff --git a/dub.sdl b/dub.sdl index ad7ae6ae..7190feaf 100644 --- a/dub.sdl +++ b/dub.sdl @@ -1,65 +1,12 @@ name "mysql-native" description "A native MySQL driver implementation based on Steve Teale's original" license "BSL-1.0" -copyright "Copyright (c) 2011-2019 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, and Nick Sabalausky" -authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" +authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" dependency "vibe-core" version="~>1.16.0" optional=true toolchainRequirements frontend=">=2.068" -sourcePaths "source/" -importPaths "source/" - -configuration "application" { - targetType "executable" - versions "VibeCustomMain" -} - -configuration "library" { - targetType "library" - excludedSourceFiles "source/app.d" -} - -// Do not use this. Use "run_tests" insetad. -configuration "unittest" { - excludedSourceFiles "source/app.d" - preBuildCommands \ - "echo \"ERROR: Don't use 'dub test' to test mysql-native. Use 'run_tests' instead.\"" \ - "echo Bailing..." \ - "mkdir" // Generate error to halt build -} - -// Run with: dub test -c unittest-vibe -configuration "unittest-vibe" { - targetType "executable" - targetPath "bin/" - targetName "mysqln-tests-vibe" - excludedSourceFiles "source/app.d" - - dependency "vibe-core" version="~>1.16.0" optional=false - - // mainSourceFile "source/mysql/package.d" - debugVersions "MYSQLN_TESTS" -} - -// Run with: dub run -c unittest-vibe-ut -- -t -configuration "unittest-vibe-ut" { - targetType "executable" - targetPath "bin/" - targetName "mysqln-tests-vibe" - excludedSourceFiles "source/app.d" - sourceFiles "bin/ut.d" - importPaths "bin/" - buildOptions "unittests" - - dependency "vibe-core" version="~>1.16.0" optional=false - - dependency "unit-threaded" version="~>1.0.15" - - debugVersions "MYSQLN_TESTS" - versions "MYSQLN_TESTS_NO_MAIN" - versions "unitUnthreaded" - - preBuildCommands "dub run unit-threaded -c gen_ut_main -- -f bin/ut.d" -} +subPackage "./integration-tests" +subPackage "./testconn" diff --git a/integration-tests/dub.sdl b/integration-tests/dub.sdl new file mode 100644 index 00000000..7073036d --- /dev/null +++ b/integration-tests/dub.sdl @@ -0,0 +1,21 @@ +name "integration-tests" +description "Test harness for integration tests" +license "BSL-1.0" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, and Nick Sabalausky" +authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" + +dependency "mysql-native" path="../" +dependency "unit-threaded" version="~>0.7.45" +dependency "vibe-core" version="~>1.0" + +configuration "unittest" { + targetType "executable" + debugVersions "MYSQLN_TESTS" + versions "MYSQLN_TESTS_NO_MAIN" + versions "unitUnthreaded" + sourceFiles "bin/ut.d" + importPaths "bin/" + buildOptions "unittests" + + preBuildCommands "dub run unit-threaded -c gen_ut_main -- -f bin/ut.d" +} diff --git a/integration-tests/source/app.d b/integration-tests/source/app.d new file mode 100644 index 00000000..6e8ec472 --- /dev/null +++ b/integration-tests/source/app.d @@ -0,0 +1,3 @@ +import mysql.test.common; +import mysql.test.integration; +import mysql.test.regression; diff --git a/source/mysql/test/common.d b/integration-tests/source/mysql/test/common.d similarity index 90% rename from source/mysql/test/common.d rename to integration-tests/source/mysql/test/common.d index b4f330ee..c81e6dbf 100644 --- a/source/mysql/test/common.d +++ b/integration-tests/source/mysql/test/common.d @@ -42,12 +42,18 @@ version(DoCoreTests) private @property string testConnectionStrFile() { import std.file, std.path; + + return "testConnectionStr.txt"; - static string cached; + /*static string cached; if(!cached) + { + import std.stdio; cached = buildPath(thisExePath.dirName.dirName, "testConnectionStr.txt"); + writeln("the file is ", cached); + } - return cached; + return cached;*/ } @property string testConnectionStr() @@ -84,6 +90,8 @@ version(DoCoreTests) Connection createCn(string cnStr = testConnectionStr) { + import std.stdio; + writeln("testing with connection string ", testConnectionStr); return new Connection(cnStr); } diff --git a/source/mysql/test/integration.d b/integration-tests/source/mysql/test/integration.d similarity index 100% rename from source/mysql/test/integration.d rename to integration-tests/source/mysql/test/integration.d diff --git a/source/mysql/test/regression.d b/integration-tests/source/mysql/test/regression.d similarity index 100% rename from source/mysql/test/regression.d rename to integration-tests/source/mysql/test/regression.d diff --git a/testconn/.dub.sdl.swp b/testconn/.dub.sdl.swp new file mode 100644 index 0000000000000000000000000000000000000000..fa315badd4a19094e69a5f74014bf5acc102fa3a GIT binary patch literal 12288 zcmeI2y^j+|7>D0W6J5gPP>_-)QeGCMaOl=w6d{Ue4n=T?FX0PFG?(%2*t^_%*6i#$ za&bzebch}hbdge|2O&X60R{X6wAaueMM;^0{Pt|%&bMAWWukYaN2~dIcjlQHTb8F) zyS#dik5?Lmw4dne&EJ1`vo%Mjmxyet``YFe<*AvsGuAz)yp=fKa4#ltC5k;2jdiPD zj-#xrZFLyFK-_O)l8i@fhAO^&M7!U(uKn#chG4TH|koM;2Crsso(R4l>J?F-r zqpUo{fEW-1Vn7Ut0Wly3#DEwO17bi7h=Kp00p$~Y^9j*)kpKVx_V@qmMWV;xH}EC6 zjrv=ob_&mR@HzMl{JlW*5Hv zZ$3sl@D%(CegQpj1$+(mgGI0awmu?y0W4St-vS1I%@h3z{s7Ox18@)A1$V&Dpao8W zFMuR5AO^&M7!U(uKn&34DA9Z`(JNUP`6uIUR~g^3gZqh`WQH>nrf#}9xk*VCdQYs~ z#M>tZEv_jp-8IgjH0-aURaaCtrt|&#xueZ`macbIXxMex zZF4I$)hw;}4$*vHCpL@?)+1ks;>g9NyR{7GJG-1|F(=j+R1_5L+FsM@vHgOQy;88K z@1@S(uY44X`bx^W-* z^P#uFP1RD7%IwAo-n5~1jfiG2QZu~`4X>$|ARA9uVvwpX{_ z+3bvcNdX}qka(*qfrNMkA;>fD5dt0%FNjyD)GBQys;W<|+6Nx`o!#~M;#>?-rAkP9 zx{vIgo%#Lw&dhK2blW?%U8~b`+0z6+PZ09an>T-R_|J#Pg$yB5@d=mu#^N7S_w(dx zomm?~?5wr|k#&W|+__=7ZQ+hbT;?LfE0PYyi01~5%8hQwYwoJYlr!Nu#ihzgF%B%A zW4@oY=jn{np4gLiF2tEb3z{lW2@O$8bm%wR|0~v4} zJOI9b3^u@DzAwl0^SA!Y=URO zKOZLKYj6|12^c7Yqu_535%MK?AG{7a;1qZWeESR718;#>!EeD;a2^~1|AF5B0=L0u zpa-g81FV4O00nwpj)CMSQQ-ftz-wd)zadMzfunfe_0p-!YFVaEQq^;1CXd^%fW8Pg zvxaHiNb}Sh2OZXPxy<0+Y-E`!)?<>>m1Vg?hfLCCNzvr;WtoLZyLWOnH0yG2poVw1 z&_aQ(olm9KQ!*6>z{X0<#?b;D+1b#lOo#WatmH1YRCyz9_jC&)o(2q`d0;qFQ3N}S zwJOG*t>d%fpmoks$2c-=DRdnV5B=`-4qex|VmfI=gs`SpX-f}{ok7ts$k1~nZ++}4 z$9FjmxWrc~IT-uC2vnG`(@$H>m-P5?YEFU@^Q$f!$ZUPf=+wIHiiuBiPxv{LQZdCx z+`A8pW~bPx)GLj2wm$!tsC_b}N+GpWWKnQM!+i;CmM)d zN&8{x3>XSSKow>$YG(aDiZHhqd8szH8s%=I+36OmRaA*ec`j~4cmt>{<*o?qg#kB> za@S}!HoL8&XlC1`bbR7`^7t+- zMdo2Gtv#74(5d3U&jcfYG8XB4ia@Ri`K zkGqloILGvsytz5UbA09%SW{|iFZtQa~wTz&qk2d;*ZQuc9PdQ9&7zGOR#IdB*l79jUw&w?HQr4CJM?D{zOBr7z-#5Beh2rTor((LXg5 zQWe72ul2XQ*0ONs%&F(|I4^>NR!V=#`@42{G2KdeyBA*Dy;OcG>xemCtmvgHVI&zs z;^~pf&H_i|r!C=xVX>Xak)`(73pI Date: Mon, 24 May 2021 21:39:40 -0400 Subject: [PATCH 02/13] Integration tests now runnable --- .github/workflows/integration-testing.yml | 8 +- integration-tests/source/app.d | 1 + integration-tests/source/mysql/maintests.d | 1346 +++++++++++++++++ integration-tests/source/mysql/test/common.d | 24 +- .../source/mysql/test/integration.d | 16 +- source/mysql/commands.d | 385 ----- source/mysql/connection.d | 395 ----- source/mysql/exceptions.d | 38 - source/mysql/package.d | 4 +- source/mysql/pool.d | 171 +-- source/mysql/prepared.d | 367 +---- source/mysql/result.d | 25 - 12 files changed, 1395 insertions(+), 1385 deletions(-) create mode 100644 integration-tests/source/mysql/maintests.d diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index 1cce8a16..c4bddf25 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -120,12 +120,12 @@ jobs: ## Turns out the unittest-vibe-ut tried to connect to an actualy MySQL on 172.18.0.1 so it's not ## actually a unit test at all. It's an integration test and should be pulled out from the main ## codebase into a separate sub module - - name: Run unittest-vibe-ut + - name: Run unittest-vibe env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt - dub run -c unittest-vibe-ut -- -t + dub run ":integration-tests" - name: Build The Example Project working-directory: ./examples/homePage @@ -190,7 +190,7 @@ jobs: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt - dub run -c unittest-vibe-ut -- -t + dub run ":integration-tests" - name: Build The Example Project working-directory: ./examples/homePage @@ -201,4 +201,4 @@ jobs: env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | - ./example "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" \ No newline at end of file + ./example "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" diff --git a/integration-tests/source/app.d b/integration-tests/source/app.d index 6e8ec472..92a3b3e1 100644 --- a/integration-tests/source/app.d +++ b/integration-tests/source/app.d @@ -1,3 +1,4 @@ import mysql.test.common; import mysql.test.integration; import mysql.test.regression; +import mysql.maintests; diff --git a/integration-tests/source/mysql/maintests.d b/integration-tests/source/mysql/maintests.d new file mode 100644 index 00000000..e8e62ab4 --- /dev/null +++ b/integration-tests/source/mysql/maintests.d @@ -0,0 +1,1346 @@ +module mysql.maintests; +import mysql.test.common; +import mysql; +import mysql.protocol.constants; + +import std.exception; +import std.variant; +import std.typecons; +import std.array; +import std.algorithm; + +// mysql.commands +@("columnSpecial") +debug(MYSQLN_TESTS) +unittest +{ + import std.array; + import std.range; + import mysql.test.common; + mixin(scopedCn); + + // Setup + cn.exec("DROP TABLE IF EXISTS `columnSpecial`"); + cn.exec("CREATE TABLE `columnSpecial` ( + `data` LONGBLOB + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below + auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + auto data = alph.cycle.take(totalSize).array; + cn.exec("INSERT INTO `columnSpecial` VALUES (\""~(cast(string)data)~"\")"); + + // Common stuff + int chunkSize; + immutable selectSQL = "SELECT `data` FROM `columnSpecial`"; + ubyte[] received; + bool lastValueOfFinished; + void receiver(const(ubyte)[] chunk, bool finished) + { + assert(lastValueOfFinished == false); + + if(finished) + assert(chunk.length == chunkSize); + else + assert(chunk.length < chunkSize); // Not always true in general, but true in this unittest + + received ~= chunk; + lastValueOfFinished = finished; + } + + // Sanity check + auto value = cn.queryValue(selectSQL); + assert(!value.isNull); + assert(value.get == data); + + // Use ColumnSpecialization with sql string, + // and totalSize as a multiple of chunkSize + { + chunkSize = 100; + assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); + auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); + + received = null; + lastValueOfFinished = false; + value = cn.queryValue(selectSQL, [columnSpecial]); + assert(!value.isNull); + assert(value.get == data); + //TODO: ColumnSpecialization is not yet implemented + //assert(lastValueOfFinished == true); + //assert(received == data); + } + + // Use ColumnSpecialization with sql string, + // and totalSize as a non-multiple of chunkSize + { + chunkSize = 64; + assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize); + auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); + + received = null; + lastValueOfFinished = false; + value = cn.queryValue(selectSQL, [columnSpecial]); + assert(!value.isNull); + assert(value.get == data); + //TODO: ColumnSpecialization is not yet implemented + //assert(lastValueOfFinished == true); + //assert(received == data); + } + + // Use ColumnSpecialization with prepared statement, + // and totalSize as a multiple of chunkSize + { + chunkSize = 100; + assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); + auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); + + received = null; + lastValueOfFinished = false; + auto prepared = cn.prepare(selectSQL); + prepared.columnSpecials = [columnSpecial]; + value = cn.queryValue(prepared); + assert(!value.isNull); + assert(value.get == data); + //TODO: ColumnSpecialization is not yet implemented + //assert(lastValueOfFinished == true); + //assert(received == data); + } +} + +// Test what happens when queryRowTuple receives no rows +@("queryRowTuple_noRows") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.test.common : scopedCn, createCn; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `queryRowTuple_noRows`"); + cn.exec("CREATE TABLE `queryRowTuple_noRows` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable selectSQL = "SELECT * FROM `queryRowTuple_noRows`"; + int queryTupleResult; + assertThrown!MYX(cn.queryRowTuple(selectSQL, queryTupleResult)); +} + +@("execOverloads") +debug(MYSQLN_TESTS) +unittest +{ + import std.array; + import mysql.connection; + import mysql.test.common; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `execOverloads`"); + cn.exec("CREATE TABLE `execOverloads` ( + `i` INTEGER, + `s` VARCHAR(50) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)"; + + // Do the inserts, using exec + + // exec: const(char[]) sql + assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1); + assert(cn.exec(prepareSQL, 2, "bb") == 1); + assert(cn.exec(prepareSQL, [Variant(3), Variant("cc")]) == 1); + + // exec: prepared sql + auto prepared = cn.prepare(prepareSQL); + prepared.setArgs(4, "dd"); + assert(cn.exec(prepared) == 1); + + assert(cn.exec(prepared, 5, "ee") == 1); + assert(prepared.getArg(0) == 5); + assert(prepared.getArg(1) == "ee"); + + assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1); + assert(prepared.getArg(0) == 6); + assert(prepared.getArg(1) == "ff"); + + // exec: bcPrepared sql + auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); + bcPrepared.setArgs(7, "gg"); + assert(cn.exec(bcPrepared) == 1); + assert(bcPrepared.getArg(0) == 7); + assert(bcPrepared.getArg(1) == "gg"); + + // Check results + auto rows = cn.query("SELECT * FROM `execOverloads`").array(); + assert(rows.length == 7); + + assert(rows[0].length == 2); + assert(rows[1].length == 2); + assert(rows[2].length == 2); + assert(rows[3].length == 2); + assert(rows[4].length == 2); + assert(rows[5].length == 2); + assert(rows[6].length == 2); + + assert(rows[0][0] == 1); + assert(rows[0][1] == "aa"); + assert(rows[1][0] == 2); + assert(rows[1][1] == "bb"); + assert(rows[2][0] == 3); + assert(rows[2][1] == "cc"); + assert(rows[3][0] == 4); + assert(rows[3][1] == "dd"); + assert(rows[4][0] == 5); + assert(rows[4][1] == "ee"); + assert(rows[5][0] == 6); + assert(rows[5][1] == "ff"); + assert(rows[6][0] == 7); + assert(rows[6][1] == "gg"); +} + +@("queryOverloads") +debug(MYSQLN_TESTS) +unittest +{ + import std.array; + import mysql.connection; + import mysql.test.common; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `queryOverloads`"); + cn.exec("CREATE TABLE `queryOverloads` ( + `i` INTEGER, + `s` VARCHAR(50) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")"); + + immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?"; + + // Test query + { + Row[] rows; + + // String sql + rows = cn.query("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 1); + assert(rows[0][1] == "aa"); + + rows = cn.query(prepareSQL, 2, "bb").array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 2); + assert(rows[0][1] == "bb"); + + rows = cn.query(prepareSQL, [Variant(3), Variant("cc")]).array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 3); + assert(rows[0][1] == "cc"); + + // Prepared sql + auto prepared = cn.prepare(prepareSQL); + prepared.setArgs(1, "aa"); + rows = cn.query(prepared).array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 1); + assert(rows[0][1] == "aa"); + + rows = cn.query(prepared, 2, "bb").array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 2); + assert(rows[0][1] == "bb"); + + rows = cn.query(prepared, [Variant(3), Variant("cc")]).array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 3); + assert(rows[0][1] == "cc"); + + // BCPrepared sql + auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); + bcPrepared.setArgs(1, "aa"); + rows = cn.query(bcPrepared).array; + assert(rows.length == 1); + assert(rows[0].length == 2); + assert(rows[0][0] == 1); + assert(rows[0][1] == "aa"); + } + + // Test queryRow + { + Nullable!Row row; + + // String sql + row = cn.queryRow("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 1); + assert(row[1] == "aa"); + + row = cn.queryRow(prepareSQL, 2, "bb"); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 2); + assert(row[1] == "bb"); + + row = cn.queryRow(prepareSQL, [Variant(3), Variant("cc")]); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 3); + assert(row[1] == "cc"); + + // Prepared sql + auto prepared = cn.prepare(prepareSQL); + prepared.setArgs(1, "aa"); + row = cn.queryRow(prepared); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 1); + assert(row[1] == "aa"); + + row = cn.queryRow(prepared, 2, "bb"); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 2); + assert(row[1] == "bb"); + + row = cn.queryRow(prepared, [Variant(3), Variant("cc")]); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 3); + assert(row[1] == "cc"); + + // BCPrepared sql + auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); + bcPrepared.setArgs(1, "aa"); + row = cn.queryRow(bcPrepared); + assert(!row.isNull); + assert(row.length == 2); + assert(row[0] == 1); + assert(row[1] == "aa"); + } + + // Test queryRowTuple + { + int i; + string s; + + // String sql + cn.queryRowTuple("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"", i, s); + assert(i == 1); + assert(s == "aa"); + + // Prepared sql + auto prepared = cn.prepare(prepareSQL); + prepared.setArgs(2, "bb"); + cn.queryRowTuple(prepared, i, s); + assert(i == 2); + assert(s == "bb"); + + // BCPrepared sql + auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); + bcPrepared.setArgs(3, "cc"); + cn.queryRowTuple(bcPrepared, i, s); + assert(i == 3); + assert(s == "cc"); + } + + // Test queryValue + { + Nullable!Variant value; + + // String sql + value = cn.queryValue("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 1); + + value = cn.queryValue(prepareSQL, 2, "bb"); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 2); + + value = cn.queryValue(prepareSQL, [Variant(3), Variant("cc")]); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 3); + + // Prepared sql + auto prepared = cn.prepare(prepareSQL); + prepared.setArgs(1, "aa"); + value = cn.queryValue(prepared); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 1); + + value = cn.queryValue(prepared, 2, "bb"); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 2); + + value = cn.queryValue(prepared, [Variant(3), Variant("cc")]); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 3); + + // BCPrepared sql + auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); + bcPrepared.setArgs(1, "aa"); + value = cn.queryValue(bcPrepared); + assert(!value.isNull); + assert(value.get.type != typeid(typeof(null))); + assert(value.get == 1); + } +} + +// mysql.connection +@("prepareFunction") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.test.common; + mixin(scopedCn); + + exec(cn, `DROP FUNCTION IF EXISTS hello`); + exec(cn, ` + CREATE FUNCTION hello (s CHAR(20)) + RETURNS CHAR(50) DETERMINISTIC + RETURN CONCAT('Hello ',s,'!') + `); + + auto preparedHello = prepareFunction(cn, "hello", 1); + preparedHello.setArgs("World"); + auto rs = cn.query(preparedHello).array; + assert(rs.length == 1); + assert(rs[0][0] == "Hello World!"); +} + +@("prepareProcedure") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.test.common; + import mysql.test.integration; + mixin(scopedCn); + initBaseTestTables(cn); + + exec(cn, `DROP PROCEDURE IF EXISTS insert2`); + exec(cn, ` + CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50)) + BEGIN + INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2); + END + `); + + auto preparedInsert2 = prepareProcedure(cn, "insert2", 2); + preparedInsert2.setArgs(2001, "inserted string 1"); + cn.exec(preparedInsert2); + + auto rs = query(cn, "SELECT stringcol FROM basetest WHERE intcol=2001").array; + assert(rs.length == 1); + assert(rs[0][0] == "inserted string 1"); +} + +// This also serves as a regression test for #167: +// ResultRange doesn't get invalidated upon reconnect +@("reconnect") +debug(MYSQLN_TESTS) +unittest +{ + import std.variant; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `reconnect`"); + cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)"); + + enum sql = "SELECT a FROM `reconnect`"; + + // Sanity check + auto rows = cn.query(sql).array; + assert(rows[0][0] == 1); + assert(rows[1][0] == 2); + assert(rows[2][0] == 3); + + // Ensure reconnect keeps the same connection when it's supposed to + auto range = cn.query(sql); + assert(range.front[0] == 1); + cn.reconnect(); + assert(!cn.closed); // Is open? + assert(range.isValid); // Still valid? + range.popFront(); + assert(range.front[0] == 2); + + // Ensure reconnect reconnects when it's supposed to + range = cn.query(sql); + assert(range.front[0] == 1); + cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities + cn.reconnect(~cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Try manually reconnecting + range = cn.query(sql); + assert(range.front[0] == 1); + cn.connect(cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Try manually closing and connecting + range = cn.query(sql); + assert(range.front[0] == 1); + cn.close(); + assert(cn.closed); // Is closed? + assert(!range.isValid); // Was invalidated? + cn.connect(cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Auto-reconnect upon a command + cn.close(); + assert(cn.closed); + range = cn.query(sql); + assert(!cn.closed); + assert(range.front[0] == 1); +} + +@("releaseAll") +debug(MYSQLN_TESTS) +unittest +{ + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `releaseAll`"); + cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`"); + auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)"); + assert(cn.isRegistered(preparedSelect)); + assert(cn.isRegistered(preparedInsert)); + + cn.releaseAll(); + assert(!cn.isRegistered(preparedSelect)); + assert(!cn.isRegistered(preparedInsert)); + cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)"); + assert(!cn.isRegistered(preparedSelect)); + assert(!cn.isRegistered(preparedInsert)); + + cn.exec(preparedInsert); + cn.query(preparedSelect).array; + assert(cn.isRegistered(preparedSelect)); + assert(cn.isRegistered(preparedInsert)); +} + +// Test register, release, isRegistered, and auto-register for prepared statements +@("autoRegistration") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.connection; + import mysql.test.common; + + Prepared preparedInsert; + Prepared preparedSelect; + immutable insertSQL = "INSERT INTO `autoRegistration` VALUES (1), (2)"; + immutable selectSQL = "SELECT `val` FROM `autoRegistration`"; + int queryTupleResult; + + { + mixin(scopedCn); + + // Setup + cn.exec("DROP TABLE IF EXISTS `autoRegistration`"); + cn.exec("CREATE TABLE `autoRegistration` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + // Initial register + preparedInsert = cn.prepare(insertSQL); + preparedSelect = cn.prepare(selectSQL); + + // Test basic register, release, isRegistered + assert(cn.isRegistered(preparedInsert)); + assert(cn.isRegistered(preparedSelect)); + cn.release(preparedInsert); + cn.release(preparedSelect); + assert(!cn.isRegistered(preparedInsert)); + assert(!cn.isRegistered(preparedSelect)); + + // Test manual re-register + cn.register(preparedInsert); + cn.register(preparedSelect); + assert(cn.isRegistered(preparedInsert)); + assert(cn.isRegistered(preparedSelect)); + + // Test double register + cn.register(preparedInsert); + cn.register(preparedSelect); + assert(cn.isRegistered(preparedInsert)); + assert(cn.isRegistered(preparedSelect)); + + // Test double release + cn.release(preparedInsert); + cn.release(preparedSelect); + assert(!cn.isRegistered(preparedInsert)); + assert(!cn.isRegistered(preparedSelect)); + cn.release(preparedInsert); + cn.release(preparedSelect); + assert(!cn.isRegistered(preparedInsert)); + assert(!cn.isRegistered(preparedSelect)); + } + + // Note that at this point, both prepared statements still exist, + // but are no longer registered on any connection. In fact, there + // are no open connections anymore. + + // Test auto-register: exec + { + mixin(scopedCn); + + assert(!cn.isRegistered(preparedInsert)); + cn.exec(preparedInsert); + assert(cn.isRegistered(preparedInsert)); + } + + // Test auto-register: query + { + mixin(scopedCn); + + assert(!cn.isRegistered(preparedSelect)); + cn.query(preparedSelect).each(); + assert(cn.isRegistered(preparedSelect)); + } + + // Test auto-register: queryRow + { + mixin(scopedCn); + + assert(!cn.isRegistered(preparedSelect)); + cn.queryRow(preparedSelect); + assert(cn.isRegistered(preparedSelect)); + } + + // Test auto-register: queryRowTuple + { + mixin(scopedCn); + + assert(!cn.isRegistered(preparedSelect)); + cn.queryRowTuple(preparedSelect, queryTupleResult); + assert(cn.isRegistered(preparedSelect)); + } + + // Test auto-register: queryValue + { + mixin(scopedCn); + + assert(!cn.isRegistered(preparedSelect)); + cn.queryValue(preparedSelect); + assert(cn.isRegistered(preparedSelect)); + } +} + +// An attempt to reproduce issue #81: Using mysql-native driver with no default database +// I'm unable to actually reproduce the error, though. +@("issue81") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.escape; + import std.conv; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `issue81`"); + cn.exec("CREATE TABLE `issue81` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `issue81` (a) VALUES (1)"); + + auto cn2 = new Connection(text("host=", cn._host, ";port=", cn._port, ";user=", cn._user, ";pwd=", cn._pwd)); + scope(exit) cn2.close(); + + cn2.query("SELECT * FROM `"~mysqlEscape(cn._db).text~"`.`issue81`"); +} + +// Regression test for Issue #154: +// autoPurge can throw an exception if the socket was closed without purging +// +// This simulates a disconnect by closing the socket underneath the Connection +// object itself. +@("dropConnection") +debug(MYSQLN_TESTS) +unittest +{ + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `dropConnection`"); + cn.exec("CREATE TABLE `dropConnection` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `dropConnection` VALUES (1), (2), (3)"); + import mysql.prepared; + { + auto prep = cn.prepare("SELECT * FROM `dropConnection`"); + cn.query(prep); + } + // close the socket forcibly + cn._socket.close(); + // this should still work (it should reconnect). + cn.exec("DROP TABLE `dropConnection`"); +} + +/+ +Test Prepared's ability to be safely refcount-released during a GC cycle +(ie, `Connection.release` must not allocate GC memory). + +Currently disabled because it's not guaranteed to always work +(and apparently, cannot be made to work?) +For relevant discussion, see issue #159: +https://github.com/mysql-d/mysql-native/issues/159 ++/ +version(none) +debug(MYSQLN_TESTS) +{ + /// Proof-of-concept ref-counted Prepared wrapper, just for testing, + /// not really intended for actual use. + private struct RCPreparedPayload + { + Prepared prepared; + Connection conn; // Connection to be released from + + alias prepared this; + + @disable this(this); // not copyable + ~this() + { + // There are a couple calls to this dtor where `conn` happens to be null. + if(conn is null) + return; + + assert(conn.isRegistered(prepared)); + conn.release(prepared); + } + } + ///ditto + alias RCPrepared = RefCounted!(RCPreparedPayload, RefCountedAutoInitialize.no); + ///ditto + private RCPrepared rcPrepare(Connection conn, const(char[]) sql) + { + import std.algorithm.mutation : move; + + auto prepared = conn.prepare(sql); + auto payload = RCPreparedPayload(prepared, conn); + return refCounted(move(payload)); + } + + @("rcPrepared") + unittest + { + import core.memory; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `rcPrepared`"); + cn.exec("CREATE TABLE `rcPrepared` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `rcPrepared` VALUES (1), (2), (3)"); + + // Define this in outer scope to guarantee data is left pending when + // RCPrepared's payload is collected. This will guarantee + // that Connection will need to queue the release. + ResultRange rows; + + void bar() + { + class Foo { RCPrepared p; } + auto foo = new Foo(); + + auto rcStmt = cn.rcPrepare("SELECT * FROM `rcPrepared`"); + foo.p = rcStmt; + rows = cn.query(rcStmt); + + /+ + At this point, there are two references to the prepared statement: + One in a `Foo` object (currently bound to `foo`), and one on the stack. + + Returning from this function will destroy the one on the stack, + and deterministically reduce the refcount to 1. + + So, right here we set `foo` to null to *keep* the Foo object's + reference to the prepared statement, but set adrift the Foo object + itself, ready to be destroyed (along with the only remaining + prepared statement reference it contains) by the next GC cycle. + + Thus, `RCPreparedPayload.~this` and `Connection.release(Prepared)` + will be executed during a GC cycle...and had better not perform + any allocations, or else...boom! + +/ + foo = null; + } + + bar(); + assert(cn.hasPending); // Ensure Connection is forced to queue the release. + GC.collect(); // `Connection.release(Prepared)` better not be allocating, or boom! + } +} + +// mysql.exceptions +@("wrongFunctionException") +debug(MYSQLN_TESTS) +unittest +{ + import std.exception; + import mysql.commands; + import mysql.connection; + import mysql.prepared; + import mysql.test.common : scopedCn, createCn; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `wrongFunctionException`"); + cn.exec("CREATE TABLE `wrongFunctionException` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable insertSQL = "INSERT INTO `wrongFunctionException` VALUES (1), (2)"; + immutable selectSQL = "SELECT * FROM `wrongFunctionException`"; + Prepared preparedInsert; + Prepared preparedSelect; + int queryTupleResult; + assertNotThrown!MYXWrongFunction(cn.exec(insertSQL)); + assertNotThrown!MYXWrongFunction(cn.query(selectSQL).each()); + assertNotThrown!MYXWrongFunction(cn.queryRowTuple(selectSQL, queryTupleResult)); + assertNotThrown!MYXWrongFunction(preparedInsert = cn.prepare(insertSQL)); + assertNotThrown!MYXWrongFunction(preparedSelect = cn.prepare(selectSQL)); + assertNotThrown!MYXWrongFunction(cn.exec(preparedInsert)); + assertNotThrown!MYXWrongFunction(cn.query(preparedSelect).each()); + assertNotThrown!MYXWrongFunction(cn.queryRowTuple(preparedSelect, queryTupleResult)); + + assertThrown!MYXResultRecieved(cn.exec(selectSQL)); + assertThrown!MYXNoResultRecieved(cn.query(insertSQL).each()); + assertThrown!MYXNoResultRecieved(cn.queryRowTuple(insertSQL, queryTupleResult)); + assertThrown!MYXResultRecieved(cn.exec(preparedSelect)); + assertThrown!MYXNoResultRecieved(cn.query(preparedInsert).each()); + assertThrown!MYXNoResultRecieved(cn.queryRowTuple(preparedInsert, queryTupleResult)); +} + +// mysql.pool +@("onNewConnection") +debug(MYSQLN_TESTS) +unittest +{ + auto count = 0; + void callback(Connection conn) + { + count++; + } + + // Test getting/setting + auto poolA = new MySQLPool(testConnectionStr, &callback); + auto poolB = new MySQLPool(testConnectionStr); + auto poolNoCallback = new MySQLPool(testConnectionStr); + + assert(poolA.onNewConnection == &callback); + assert(poolB.onNewConnection is null); + assert(poolNoCallback.onNewConnection is null); + + poolB.onNewConnection = &callback; + assert(poolB.onNewConnection == &callback); + assert(count == 0); + + // Ensure callback is called + { + auto connA = poolA.lockConnection(); + assert(!connA.closed); + assert(count == 1); + + auto connB = poolB.lockConnection(); + assert(!connB.closed); + assert(count == 2); + } + + // Ensure works with no callback + { + auto oldCount = count; + auto poolC = new MySQLPool(testConnectionStr); + auto connC = poolC.lockConnection(); + assert(!connC.closed); + assert(count == oldCount); + } +} + +@("registration") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.commands; + auto pool = new MySQLPool(testConnectionStr); + + // Setup + auto cn = pool.lockConnection(); + cn.exec("DROP TABLE IF EXISTS `poolRegistration`"); + cn.exec("CREATE TABLE `poolRegistration` ( + `data` LONGBLOB + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + immutable sql = "SELECT * from `poolRegistration`"; + auto cn2 = pool.lockConnection(); + pool.applyAuto(cn2); + assert(cn !is cn2); + + // Tests: + // Initial + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(!cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + // Register on connection #1 + auto prepared = cn.prepare(sql); + { + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } + + // autoRegister + pool.autoRegister(prepared); + { + assert(!pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(!pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(cn3.isRegistered(sql)); + } + + // autoRelease + pool.autoRelease(prepared); + { + assert(!pool.isAutoCleared(sql)); + assert(!pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } + + // clearAuto + pool.clearAuto(prepared); + { + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } +} + +@("closedConnection") // "cct" +debug(MYSQLN_TESTS) +{ + import mysql.pool; + MySQLPool cctPool; + int cctCount=0; + + void cctStart() + { + import std.array; + import mysql.commands; + + cctPool = new MySQLPool(testConnectionStr); + cctPool.onNewConnection = (Connection conn) { cctCount++; }; + assert(cctCount == 0); + + auto cn = cctPool.lockConnection(); + assert(!cn.closed); + cn.close(); + assert(cn.closed); + assert(cctCount == 1); + } + + unittest + { + cctStart(); + assert(cctCount == 1); + + auto cn = cctPool.lockConnection(); + assert(cctCount == 1); + assert(!cn.closed); + } +} + +// mysql.prepared +@("paramSpecial") +debug(MYSQLN_TESTS) +unittest +{ + import std.array; + import std.range; + import mysql.connection; + import mysql.test.common; + mixin(scopedCn); + + // Setup + cn.exec("DROP TABLE IF EXISTS `paramSpecial`"); + cn.exec("CREATE TABLE `paramSpecial` ( + `data` LONGBLOB + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below + auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + auto data = alph.cycle.take(totalSize).array; + + int chunkSize; + const(ubyte)[] dataToSend; + bool finished; + uint sender(ubyte[] chunk) + { + assert(!finished); + assert(chunk.length == chunkSize); + + if(dataToSend.length < chunkSize) + { + auto actualSize = cast(uint) dataToSend.length; + chunk[0..actualSize] = dataToSend[]; + finished = true; + dataToSend.length = 0; + return actualSize; + } + else + { + chunk[] = dataToSend[0..chunkSize]; + dataToSend = dataToSend[chunkSize..$]; + return chunkSize; + } + } + + immutable selectSQL = "SELECT `data` FROM `paramSpecial`"; + + // Sanity check + cn.exec("INSERT INTO `paramSpecial` VALUES (\""~(cast(string)data)~"\")"); + auto value = cn.queryValue(selectSQL); + assert(!value.isNull); + assert(value.get == data); + + { + // Clear table + cn.exec("DELETE FROM `paramSpecial`"); + value = cn.queryValue(selectSQL); // Ensure deleted + assert(value.isNull); + + // Test: totalSize as a multiple of chunkSize + chunkSize = 100; + assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); + auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender); + + finished = false; + dataToSend = data; + auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)"); + prepared.setArg(0, cast(ubyte[])[], paramSpecial); + assert(cn.exec(prepared) == 1); + value = cn.queryValue(selectSQL); + assert(!value.isNull); + assert(value.get == data); + } + + { + // Clear table + cn.exec("DELETE FROM `paramSpecial`"); + value = cn.queryValue(selectSQL); // Ensure deleted + assert(value.isNull); + + // Test: totalSize as a non-multiple of chunkSize + chunkSize = 64; + assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize); + auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender); + + finished = false; + dataToSend = data; + auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)"); + prepared.setArg(0, cast(ubyte[])[], paramSpecial); + assert(cn.exec(prepared) == 1); + value = cn.queryValue(selectSQL); + assert(!value.isNull); + assert(value.get == data); + } +} + +@("setArg-typeMods") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.test.common; + mixin(scopedCn); + + // Setup + cn.exec("DROP TABLE IF EXISTS `setArg-typeMods`"); + cn.exec("CREATE TABLE `setArg-typeMods` ( + `i` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto insertSQL = "INSERT INTO `setArg-typeMods` VALUES (?)"; + + // Sanity check + { + int i = 111; + assert(cn.exec(insertSQL, i) == 1); + auto value = cn.queryValue("SELECT `i` FROM `setArg-typeMods`"); + assert(!value.isNull); + assert(value.get == i); + } + + // Test const(int) + { + const(int) i = 112; + assert(cn.exec(insertSQL, i) == 1); + } + + // Test immutable(int) + { + immutable(int) i = 113; + assert(cn.exec(insertSQL, i) == 1); + } + + // Note: Variant doesn't seem to support + // `shared(T)` or `shared(const(T)`. Only `shared(immutable(T))`. + + // Test shared immutable(int) + { + shared immutable(int) i = 113; + assert(cn.exec(insertSQL, i) == 1); + } +} + +@("setNullArg") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.connection; + import mysql.test.common; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `setNullArg`"); + cn.exec("CREATE TABLE `setNullArg` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)"; + immutable selectSQL = "SELECT * FROM `setNullArg`"; + auto preparedInsert = cn.prepare(insertSQL); + assert(preparedInsert.sql == insertSQL); + Row[] rs; + + { + Nullable!int nullableInt; + nullableInt.nullify(); + preparedInsert.setArg(0, nullableInt); + assert(preparedInsert.getArg(0).type == typeid(typeof(null))); + nullableInt = 7; + preparedInsert.setArg(0, nullableInt); + assert(preparedInsert.getArg(0) == 7); + + nullableInt.nullify(); + preparedInsert.setArgs(nullableInt); + assert(preparedInsert.getArg(0).type == typeid(typeof(null))); + nullableInt = 7; + preparedInsert.setArgs(nullableInt); + assert(preparedInsert.getArg(0) == 7); + } + + preparedInsert.setArg(0, 5); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 1); + assert(rs[0][0] == 5); + + preparedInsert.setArg(0, null); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 2); + assert(rs[0][0] == 5); + assert(rs[1].isNull(0)); + assert(rs[1][0].type == typeid(typeof(null))); + + preparedInsert.setArg(0, Variant(null)); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 3); + assert(rs[0][0] == 5); + assert(rs[1].isNull(0)); + assert(rs[2].isNull(0)); + assert(rs[1][0].type == typeid(typeof(null))); + assert(rs[2][0].type == typeid(typeof(null))); +} + +@("lastInsertID") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.connection; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `testPreparedLastInsertID`"); + cn.exec("CREATE TABLE `testPreparedLastInsertID` ( + `a` INTEGER NOT NULL AUTO_INCREMENT, + PRIMARY KEY (a) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()"); + cn.exec(stmt); + assert(stmt.lastInsertID == 1); + cn.exec(stmt); + assert(stmt.lastInsertID == 2); + cn.exec(stmt); + assert(stmt.lastInsertID == 3); +} + +// Test PreparedRegistrations +debug(MYSQLN_TESTS) +{ + // used in integration tests. + PreparedRegistrations!TestPreparedRegistrationsGood1 testPreparedRegistrationsGood1; + PreparedRegistrations!TestPreparedRegistrationsGood2 testPreparedRegistrationsGood2; + + @("PreparedRegistrations") + unittest + { + // Test init + PreparedRegistrations!TestPreparedRegistrationsGood2 pr; + assert(pr.directLookup.keys.length == 0); + + void resetData(bool isQueued1, bool isQueued2, bool isQueued3) + { + pr.directLookup["1"] = TestPreparedRegistrationsGood2(isQueued1, "1"); + pr.directLookup["2"] = TestPreparedRegistrationsGood2(isQueued2, "2"); + pr.directLookup["3"] = TestPreparedRegistrationsGood2(isQueued3, "3"); + assert(pr.directLookup.keys.length == 3); + } + + // Test resetData (sanity check) + resetData(false, true, false); + assert(pr.directLookup["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr.directLookup["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr.directLookup["3"] == TestPreparedRegistrationsGood2(false, "3")); + + // Test opIndex + resetData(false, true, false); + pr.directLookup["1"] = TestPreparedRegistrationsGood2(false, "1"); + pr.directLookup["2"] = TestPreparedRegistrationsGood2(true, "2"); + pr.directLookup["3"] = TestPreparedRegistrationsGood2(false, "3"); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); + assert(pr["4"].isNull); + + // Test queueForRelease + resetData(false, true, false); + pr.queueForRelease("2"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); + + pr.queueForRelease("3"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); + + pr.queueForRelease("4"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); + + // Test unqueueForRelease + resetData(false, true, false); + pr.unqueueForRelease("1"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); + + pr.unqueueForRelease("2"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); + + pr.unqueueForRelease("4"); + assert(pr.directLookup.keys.length == 3); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); + + // Test queueAllForRelease + resetData(false, true, false); + pr.queueAllForRelease(); + assert(pr["1"] == TestPreparedRegistrationsGood2(true, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); + assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); + assert(pr["4"].isNull); + + // Test clear + resetData(false, true, false); + pr.clear(); + assert(pr.directLookup.keys.length == 0); + + // Test registerIfNeeded + auto doRegister(const(char[]) sql) { return TestPreparedRegistrationsGood2(false, sql); } + pr.registerIfNeeded("1", &doRegister); + assert(pr.directLookup.keys.length == 1); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + + pr.registerIfNeeded("1", &doRegister); + assert(pr.directLookup.keys.length == 1); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + + pr.registerIfNeeded("2", &doRegister); + assert(pr.directLookup.keys.length == 2); + assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); + assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); + } +} + +// mysql.result +@("getName") +debug(MYSQLN_TESTS) +unittest +{ + import mysql.test.common; + import mysql.commands; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `row_getName`"); + cn.exec("CREATE TABLE `row_getName` (someValue INTEGER, another INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `row_getName` VALUES (1, 2), (3, 4)"); + + enum sql = "SELECT another, someValue FROM `row_getName`"; + + auto rows = cn.query(sql).array; + assert(rows.length == 2); + assert(rows[0][0] == 2); + assert(rows[0][1] == 1); + assert(rows[0].getName(0) == "another"); + assert(rows[0].getName(1) == "someValue"); + assert(rows[1][0] == 4); + assert(rows[1][1] == 3); + assert(rows[1].getName(0) == "another"); + assert(rows[1].getName(1) == "someValue"); +} diff --git a/integration-tests/source/mysql/test/common.d b/integration-tests/source/mysql/test/common.d index c81e6dbf..2b36b77f 100644 --- a/integration-tests/source/mysql/test/common.d +++ b/integration-tests/source/mysql/test/common.d @@ -1,4 +1,4 @@ -/++ +/++ Package mysql.test contains integration and regression tests, not unittests. Unittests (including regression unittests) are located together with the units they test. @@ -43,8 +43,10 @@ version(DoCoreTests) { import std.file, std.path; - return "testConnectionStr.txt"; - + return "testConnectionStr.txt"; + // This seems highly dependent on where the test is run from. At this + // point, I just am using the current directory to avoid going on a + // hunt for the file, and possibly running into permission issues. /*static string cached; if(!cached) { @@ -55,7 +57,7 @@ version(DoCoreTests) return cached;*/ } - + @property string testConnectionStr() { import std.file, std.string; @@ -70,7 +72,7 @@ version(DoCoreTests) testConnectionStrFile, "host=localhost;port=3306;user=mysqln_test;pwd=pass123;db=mysqln_testdb" ); - + import std.stdio; writeln( "Connection string file for tests wasn't found, so a default "~ @@ -78,20 +80,20 @@ version(DoCoreTests) "run the mysql-native tests again:" ); writeln(testConnectionStrFile); - assert(false, "Halting so the user can check connection string settings."); + writeln("Halting so the user can check connection string settings."); + import core.stdc.stdlib : exit; + exit(1); } - + cached = cast(string) std.file.read(testConnectionStrFile); cached = cached.strip(); } - + return cached; } Connection createCn(string cnStr = testConnectionStr) { - import std.stdio; - writeln("testing with connection string ", testConnectionStr); return new Connection(cnStr); } @@ -101,7 +103,7 @@ version(DoCoreTests) { auto result = cn.queryValue(query); assert(!result.isNull); - + // Timestamp is a bit special as it's converted to a DateTime when // returning from MySQL to avoid having to use a mysql specific type. static if(is(T == DateTime) && is(U == Timestamp)) diff --git a/integration-tests/source/mysql/test/integration.d b/integration-tests/source/mysql/test/integration.d index a4d60e5b..a51eec37 100644 --- a/integration-tests/source/mysql/test/integration.d +++ b/integration-tests/source/mysql/test/integration.d @@ -1,4 +1,4 @@ -module mysql.test.integration; +module mysql.test.integration; import std.algorithm; import std.conv; @@ -115,7 +115,7 @@ debug(MYSQLN_TESTS) unittest { import mysql.prepared; - + struct X { int a, b, c; @@ -457,9 +457,9 @@ unittest count++; } assert(count == 2); - + initBaseTestTables(cn); - + string[] tList = md.tables(); count = 0; foreach (string t; tList) @@ -477,7 +477,7 @@ unittest See "COLUMN_DEFAULT" at: https://mariadb.com/kb/en/library/information-schema-columns-table/ +/ - + ColumnInfo[] ca = md.columns("basetest"); assert( ca[0].schema == schemaName && ca[0].table == "basetest" && ca[0].name == "boolcol" && ca[0].index == 0 && ca[0].nullable && ca[0].type == "bit" && ca[0].charsMax == -1 && ca[0].octetsMax == -1 && @@ -638,7 +638,7 @@ unittest /+ // Commented out because leaving args unspecified is currently unsupported, // and I'm not convinced it should be allowed. - + // Insert null - params defaults to null { cn.truncate("manytypes"); @@ -998,7 +998,7 @@ unittest immutable selectNoRowsSQL = "SELECT * FROM `coupleTypes` WHERE s='no such match'"; auto prepared = cn.prepare(selectSQL); auto preparedSelectNoRows = cn.prepare(selectNoRowsSQL); - + { // Test query ResultRange rseq = cn.query(selectSQL); @@ -1186,7 +1186,7 @@ unittest `name` VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); } - + // Setup current working directory auto saveDir = getcwd(); scope(exit) diff --git a/source/mysql/commands.d b/source/mysql/commands.d index b5a3aadd..ab64c4fd 100644 --- a/source/mysql/commands.d +++ b/source/mysql/commands.d @@ -51,103 +51,6 @@ struct ColumnSpecialization ///ditto alias CSN = ColumnSpecialization; -@("columnSpecial") -debug(MYSQLN_TESTS) -unittest -{ - import std.array; - import std.range; - import mysql.test.common; - mixin(scopedCn); - - // Setup - cn.exec("DROP TABLE IF EXISTS `columnSpecial`"); - cn.exec("CREATE TABLE `columnSpecial` ( - `data` LONGBLOB - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below - auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - auto data = alph.cycle.take(totalSize).array; - cn.exec("INSERT INTO `columnSpecial` VALUES (\""~(cast(string)data)~"\")"); - - // Common stuff - int chunkSize; - immutable selectSQL = "SELECT `data` FROM `columnSpecial`"; - ubyte[] received; - bool lastValueOfFinished; - void receiver(const(ubyte)[] chunk, bool finished) - { - assert(lastValueOfFinished == false); - - if(finished) - assert(chunk.length == chunkSize); - else - assert(chunk.length < chunkSize); // Not always true in general, but true in this unittest - - received ~= chunk; - lastValueOfFinished = finished; - } - - // Sanity check - auto value = cn.queryValue(selectSQL); - assert(!value.isNull); - assert(value.get == data); - - // Use ColumnSpecialization with sql string, - // and totalSize as a multiple of chunkSize - { - chunkSize = 100; - assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); - auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); - - received = null; - lastValueOfFinished = false; - value = cn.queryValue(selectSQL, [columnSpecial]); - assert(!value.isNull); - assert(value.get == data); - //TODO: ColumnSpecialization is not yet implemented - //assert(lastValueOfFinished == true); - //assert(received == data); - } - - // Use ColumnSpecialization with sql string, - // and totalSize as a non-multiple of chunkSize - { - chunkSize = 64; - assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize); - auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); - - received = null; - lastValueOfFinished = false; - value = cn.queryValue(selectSQL, [columnSpecial]); - assert(!value.isNull); - assert(value.get == data); - //TODO: ColumnSpecialization is not yet implemented - //assert(lastValueOfFinished == true); - //assert(received == data); - } - - // Use ColumnSpecialization with prepared statement, - // and totalSize as a multiple of chunkSize - { - chunkSize = 100; - assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); - auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); - - received = null; - lastValueOfFinished = false; - auto prepared = cn.prepare(selectSQL); - prepared.columnSpecials = [columnSpecial]; - value = cn.queryValue(prepared); - assert(!value.isNull); - assert(value.get == data); - //TODO: ColumnSpecialization is not yet implemented - //assert(lastValueOfFinished == true); - //assert(received == data); - } -} - /++ Execute an SQL command or prepared statement, such as INSERT/UPDATE/CREATE/etc. @@ -583,24 +486,6 @@ package void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, re conn.purgeResult(); } -// Test what happends when queryRowTuple receives no rows -@("queryRowTuple_noRows") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.test.common : scopedCn, createCn; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `queryRowTuple_noRows`"); - cn.exec("CREATE TABLE `queryRowTuple_noRows` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable selectSQL = "SELECT * FROM `queryRowTuple_noRows`"; - int queryTupleResult; - assertThrown!MYX(cn.queryRowTuple(selectSQL, queryTupleResult)); -} - /++ Execute an SQL SELECT command or prepared statement and return a single value: the first column of the first row received. @@ -742,273 +627,3 @@ package Nullable!Variant queryValueImpl(ColumnSpecialization[] csa, Connection c } } -@("execOverloads") -debug(MYSQLN_TESTS) -unittest -{ - import std.array; - import mysql.connection; - import mysql.test.common; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `execOverloads`"); - cn.exec("CREATE TABLE `execOverloads` ( - `i` INTEGER, - `s` VARCHAR(50) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)"; - - // Do the inserts, using exec - - // exec: const(char[]) sql - assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1); - assert(cn.exec(prepareSQL, 2, "bb") == 1); - assert(cn.exec(prepareSQL, [Variant(3), Variant("cc")]) == 1); - - // exec: prepared sql - auto prepared = cn.prepare(prepareSQL); - prepared.setArgs(4, "dd"); - assert(cn.exec(prepared) == 1); - - assert(cn.exec(prepared, 5, "ee") == 1); - assert(prepared.getArg(0) == 5); - assert(prepared.getArg(1) == "ee"); - - assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1); - assert(prepared.getArg(0) == 6); - assert(prepared.getArg(1) == "ff"); - - // exec: bcPrepared sql - auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); - bcPrepared.setArgs(7, "gg"); - assert(cn.exec(bcPrepared) == 1); - assert(bcPrepared.getArg(0) == 7); - assert(bcPrepared.getArg(1) == "gg"); - - // Check results - auto rows = cn.query("SELECT * FROM `execOverloads`").array(); - assert(rows.length == 7); - - assert(rows[0].length == 2); - assert(rows[1].length == 2); - assert(rows[2].length == 2); - assert(rows[3].length == 2); - assert(rows[4].length == 2); - assert(rows[5].length == 2); - assert(rows[6].length == 2); - - assert(rows[0][0] == 1); - assert(rows[0][1] == "aa"); - assert(rows[1][0] == 2); - assert(rows[1][1] == "bb"); - assert(rows[2][0] == 3); - assert(rows[2][1] == "cc"); - assert(rows[3][0] == 4); - assert(rows[3][1] == "dd"); - assert(rows[4][0] == 5); - assert(rows[4][1] == "ee"); - assert(rows[5][0] == 6); - assert(rows[5][1] == "ff"); - assert(rows[6][0] == 7); - assert(rows[6][1] == "gg"); -} - -@("queryOverloads") -debug(MYSQLN_TESTS) -unittest -{ - import std.array; - import mysql.connection; - import mysql.test.common; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `queryOverloads`"); - cn.exec("CREATE TABLE `queryOverloads` ( - `i` INTEGER, - `s` VARCHAR(50) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")"); - - immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?"; - - // Test query - { - Row[] rows; - - // String sql - rows = cn.query("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 1); - assert(rows[0][1] == "aa"); - - rows = cn.query(prepareSQL, 2, "bb").array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 2); - assert(rows[0][1] == "bb"); - - rows = cn.query(prepareSQL, [Variant(3), Variant("cc")]).array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 3); - assert(rows[0][1] == "cc"); - - // Prepared sql - auto prepared = cn.prepare(prepareSQL); - prepared.setArgs(1, "aa"); - rows = cn.query(prepared).array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 1); - assert(rows[0][1] == "aa"); - - rows = cn.query(prepared, 2, "bb").array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 2); - assert(rows[0][1] == "bb"); - - rows = cn.query(prepared, [Variant(3), Variant("cc")]).array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 3); - assert(rows[0][1] == "cc"); - - // BCPrepared sql - auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); - bcPrepared.setArgs(1, "aa"); - rows = cn.query(bcPrepared).array; - assert(rows.length == 1); - assert(rows[0].length == 2); - assert(rows[0][0] == 1); - assert(rows[0][1] == "aa"); - } - - // Test queryRow - { - Nullable!Row row; - - // String sql - row = cn.queryRow("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 1); - assert(row[1] == "aa"); - - row = cn.queryRow(prepareSQL, 2, "bb"); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 2); - assert(row[1] == "bb"); - - row = cn.queryRow(prepareSQL, [Variant(3), Variant("cc")]); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 3); - assert(row[1] == "cc"); - - // Prepared sql - auto prepared = cn.prepare(prepareSQL); - prepared.setArgs(1, "aa"); - row = cn.queryRow(prepared); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 1); - assert(row[1] == "aa"); - - row = cn.queryRow(prepared, 2, "bb"); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 2); - assert(row[1] == "bb"); - - row = cn.queryRow(prepared, [Variant(3), Variant("cc")]); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 3); - assert(row[1] == "cc"); - - // BCPrepared sql - auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); - bcPrepared.setArgs(1, "aa"); - row = cn.queryRow(bcPrepared); - assert(!row.isNull); - assert(row.length == 2); - assert(row[0] == 1); - assert(row[1] == "aa"); - } - - // Test queryRowTuple - { - int i; - string s; - - // String sql - cn.queryRowTuple("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"", i, s); - assert(i == 1); - assert(s == "aa"); - - // Prepared sql - auto prepared = cn.prepare(prepareSQL); - prepared.setArgs(2, "bb"); - cn.queryRowTuple(prepared, i, s); - assert(i == 2); - assert(s == "bb"); - - // BCPrepared sql - auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); - bcPrepared.setArgs(3, "cc"); - cn.queryRowTuple(bcPrepared, i, s); - assert(i == 3); - assert(s == "cc"); - } - - // Test queryValue - { - Nullable!Variant value; - - // String sql - value = cn.queryValue("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 1); - - value = cn.queryValue(prepareSQL, 2, "bb"); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 2); - - value = cn.queryValue(prepareSQL, [Variant(3), Variant("cc")]); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 3); - - // Prepared sql - auto prepared = cn.prepare(prepareSQL); - prepared.setArgs(1, "aa"); - value = cn.queryValue(prepared); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 1); - - value = cn.queryValue(prepared, 2, "bb"); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 2); - - value = cn.queryValue(prepared, [Variant(3), Variant("cc")]); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 3); - - // BCPrepared sql - auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); - bcPrepared.setArgs(1, "aa"); - value = cn.queryValue(bcPrepared); - assert(!value.isNull); - assert(value.get.type != typeid(typeof(null))); - assert(value.get == 1); - } -} diff --git a/source/mysql/connection.d b/source/mysql/connection.d index e0042d4e..4cde1359 100644 --- a/source/mysql/connection.d +++ b/source/mysql/connection.d @@ -17,10 +17,6 @@ import mysql.protocol.constants; import mysql.protocol.packets; import mysql.protocol.sockets; import mysql.result; -debug(MYSQLN_TESTS) -{ - import mysql.test.common; -} version(Have_vibe_core) { @@ -97,28 +93,6 @@ Prepared prepareFunction(Connection conn, string name, int numArgs) return prepare(conn, sql); } -/// -@("prepareFunction") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.test.common; - mixin(scopedCn); - - exec(cn, `DROP FUNCTION IF EXISTS hello`); - exec(cn, ` - CREATE FUNCTION hello (s CHAR(20)) - RETURNS CHAR(50) DETERMINISTIC - RETURN CONCAT('Hello ',s,'!') - `); - - auto preparedHello = prepareFunction(cn, "hello", 1); - preparedHello.setArgs("World"); - auto rs = cn.query(preparedHello).array; - assert(rs.length == 1); - assert(rs[0][0] == "Hello World!"); -} - /++ Convenience function to create a prepared statement which calls a stored procedure. @@ -141,33 +115,6 @@ Prepared prepareProcedure(Connection conn, string name, int numArgs) return prepare(conn, sql); } -/// -@("prepareProcedure") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.test.common; - import mysql.test.integration; - mixin(scopedCn); - initBaseTestTables(cn); - - exec(cn, `DROP PROCEDURE IF EXISTS insert2`); - exec(cn, ` - CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50)) - BEGIN - INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2); - END - `); - - auto preparedInsert2 = prepareProcedure(cn, "insert2", 2); - preparedInsert2.setArgs(2001, "inserted string 1"); - cn.exec(preparedInsert2); - - auto rs = query(cn, "SELECT stringcol FROM basetest WHERE intcol=2001").array; - assert(rs.length == 1); - assert(rs[0][0] == "inserted string 1"); -} - private string preparedPlaceholderArgs(int numArgs) { auto sql = "("; @@ -772,71 +719,6 @@ public: connect(clientCapabilities); } - // This also serves as a regression test for #167: - // ResultRange doesn't get invalidated upon reconnect - @("reconnect") - debug(MYSQLN_TESTS) - unittest - { - import std.variant; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `reconnect`"); - cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)"); - - enum sql = "SELECT a FROM `reconnect`"; - - // Sanity check - auto rows = cn.query(sql).array; - assert(rows[0][0] == 1); - assert(rows[1][0] == 2); - assert(rows[2][0] == 3); - - // Ensure reconnect keeps the same connection when it's supposed to - auto range = cn.query(sql); - assert(range.front[0] == 1); - cn.reconnect(); - assert(!cn.closed); // Is open? - assert(range.isValid); // Still valid? - range.popFront(); - assert(range.front[0] == 2); - - // Ensure reconnect reconnects when it's supposed to - range = cn.query(sql); - assert(range.front[0] == 1); - cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities - cn.reconnect(~cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Try manually reconnecting - range = cn.query(sql); - assert(range.front[0] == 1); - cn.connect(cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Try manually closing and connecting - range = cn.query(sql); - assert(range.front[0] == 1); - cn.close(); - assert(cn.closed); // Is closed? - assert(!range.isValid); // Was invalidated? - cn.connect(cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Auto-reconnect upon a command - cn.close(); - assert(cn.closed); - range = cn.query(sql); - assert(!cn.closed); - assert(range.front[0] == 1); - } - private void quit() in { @@ -1147,34 +1029,6 @@ public: preparedRegistrations.queueAllForRelease(); } - @("releaseAll") - debug(MYSQLN_TESTS) - unittest - { - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `releaseAll`"); - cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`"); - auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)"); - assert(cn.isRegistered(preparedSelect)); - assert(cn.isRegistered(preparedInsert)); - - cn.releaseAll(); - assert(!cn.isRegistered(preparedSelect)); - assert(!cn.isRegistered(preparedInsert)); - cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)"); - assert(!cn.isRegistered(preparedSelect)); - assert(!cn.isRegistered(preparedInsert)); - - cn.exec(preparedInsert); - cn.query(preparedSelect).array; - assert(cn.isRegistered(preparedSelect)); - assert(cn.isRegistered(preparedInsert)); - - } - /// Is the given statement registered on this connection as a prepared statement? bool isRegistered(Prepared prepared) { @@ -1193,252 +1047,3 @@ public: return !info.isNull && !info.get.queuedForRelease; } } - -// Test register, release, isRegistered, and auto-register for prepared statements -@("autoRegistration") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.connection; - import mysql.test.common; - - Prepared preparedInsert; - Prepared preparedSelect; - immutable insertSQL = "INSERT INTO `autoRegistration` VALUES (1), (2)"; - immutable selectSQL = "SELECT `val` FROM `autoRegistration`"; - int queryTupleResult; - - { - mixin(scopedCn); - - // Setup - cn.exec("DROP TABLE IF EXISTS `autoRegistration`"); - cn.exec("CREATE TABLE `autoRegistration` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - // Initial register - preparedInsert = cn.prepare(insertSQL); - preparedSelect = cn.prepare(selectSQL); - - // Test basic register, release, isRegistered - assert(cn.isRegistered(preparedInsert)); - assert(cn.isRegistered(preparedSelect)); - cn.release(preparedInsert); - cn.release(preparedSelect); - assert(!cn.isRegistered(preparedInsert)); - assert(!cn.isRegistered(preparedSelect)); - - // Test manual re-register - cn.register(preparedInsert); - cn.register(preparedSelect); - assert(cn.isRegistered(preparedInsert)); - assert(cn.isRegistered(preparedSelect)); - - // Test double register - cn.register(preparedInsert); - cn.register(preparedSelect); - assert(cn.isRegistered(preparedInsert)); - assert(cn.isRegistered(preparedSelect)); - - // Test double release - cn.release(preparedInsert); - cn.release(preparedSelect); - assert(!cn.isRegistered(preparedInsert)); - assert(!cn.isRegistered(preparedSelect)); - cn.release(preparedInsert); - cn.release(preparedSelect); - assert(!cn.isRegistered(preparedInsert)); - assert(!cn.isRegistered(preparedSelect)); - } - - // Note that at this point, both prepared statements still exist, - // but are no longer registered on any connection. In fact, there - // are no open connections anymore. - - // Test auto-register: exec - { - mixin(scopedCn); - - assert(!cn.isRegistered(preparedInsert)); - cn.exec(preparedInsert); - assert(cn.isRegistered(preparedInsert)); - } - - // Test auto-register: query - { - mixin(scopedCn); - - assert(!cn.isRegistered(preparedSelect)); - cn.query(preparedSelect).each(); - assert(cn.isRegistered(preparedSelect)); - } - - // Test auto-register: queryRow - { - mixin(scopedCn); - - assert(!cn.isRegistered(preparedSelect)); - cn.queryRow(preparedSelect); - assert(cn.isRegistered(preparedSelect)); - } - - // Test auto-register: queryRowTuple - { - mixin(scopedCn); - - assert(!cn.isRegistered(preparedSelect)); - cn.queryRowTuple(preparedSelect, queryTupleResult); - assert(cn.isRegistered(preparedSelect)); - } - - // Test auto-register: queryValue - { - mixin(scopedCn); - - assert(!cn.isRegistered(preparedSelect)); - cn.queryValue(preparedSelect); - assert(cn.isRegistered(preparedSelect)); - } -} - -// An attempt to reproduce issue #81: Using mysql-native driver with no default database -// I'm unable to actually reproduce the error, though. -@("issue81") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.escape; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `issue81`"); - cn.exec("CREATE TABLE `issue81` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `issue81` (a) VALUES (1)"); - - auto cn2 = new Connection(text("host=", cn._host, ";port=", cn._port, ";user=", cn._user, ";pwd=", cn._pwd)); - scope(exit) cn2.close(); - - cn2.query("SELECT * FROM `"~mysqlEscape(cn._db).text~"`.`issue81`"); -} - -// Regression test for Issue #154: -// autoPurge can throw an exception if the socket was closed without purging -// -// This simulates a disconnect by closing the socket underneath the Connection -// object itself. -@("dropConnection") -debug(MYSQLN_TESTS) -unittest -{ - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `dropConnection`"); - cn.exec("CREATE TABLE `dropConnection` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `dropConnection` VALUES (1), (2), (3)"); - import mysql.prepared; - { - auto prep = cn.prepare("SELECT * FROM `dropConnection`"); - cn.query(prep); - } - // close the socket forcibly - cn._socket.close(); - // this should still work (it should reconnect). - cn.exec("DROP TABLE `dropConnection`"); -} - -/+ -Test Prepared's ability to be safely refcount-released during a GC cycle -(ie, `Connection.release` must not allocate GC memory). - -Currently disabled because it's not guaranteed to always work -(and apparently, cannot be made to work?) -For relevant discussion, see issue #159: -https://github.com/mysql-d/mysql-native/issues/159 -+/ -version(none) -debug(MYSQLN_TESTS) -{ - /// Proof-of-concept ref-counted Prepared wrapper, just for testing, - /// not really intended for actual use. - private struct RCPreparedPayload - { - Prepared prepared; - Connection conn; // Connection to be released from - - alias prepared this; - - @disable this(this); // not copyable - ~this() - { - // There are a couple calls to this dtor where `conn` happens to be null. - if(conn is null) - return; - - assert(conn.isRegistered(prepared)); - conn.release(prepared); - } - } - ///ditto - alias RCPrepared = RefCounted!(RCPreparedPayload, RefCountedAutoInitialize.no); - ///ditto - private RCPrepared rcPrepare(Connection conn, const(char[]) sql) - { - import std.algorithm.mutation : move; - - auto prepared = conn.prepare(sql); - auto payload = RCPreparedPayload(prepared, conn); - return refCounted(move(payload)); - } - - @("rcPrepared") - unittest - { - import core.memory; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `rcPrepared`"); - cn.exec("CREATE TABLE `rcPrepared` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `rcPrepared` VALUES (1), (2), (3)"); - - // Define this in outer scope to guarantee data is left pending when - // RCPrepared's payload is collected. This will guarantee - // that Connection will need to queue the release. - ResultRange rows; - - void bar() - { - class Foo { RCPrepared p; } - auto foo = new Foo(); - - auto rcStmt = cn.rcPrepare("SELECT * FROM `rcPrepared`"); - foo.p = rcStmt; - rows = cn.query(rcStmt); - - /+ - At this point, there are two references to the prepared statement: - One in a `Foo` object (currently bound to `foo`), and one on the stack. - - Returning from this function will destroy the one on the stack, - and deterministically reduce the refcount to 1. - - So, right here we set `foo` to null to *keep* the Foo object's - reference to the prepared statement, but set adrift the Foo object - itself, ready to be destroyed (along with the only remaining - prepared statement reference it contains) by the next GC cycle. - - Thus, `RCPreparedPayload.~this` and `Connection.release(Prepared)` - will be executed during a GC cycle...and had better not perform - any allocations, or else...boom! - +/ - foo = null; - } - - bar(); - assert(cn.hasPending); // Ensure Connection is forced to queue the release. - GC.collect(); // `Connection.release(Prepared)` better not be allocating, or boom! - } -} diff --git a/source/mysql/exceptions.d b/source/mysql/exceptions.d index 8717a7f2..115cb560 100644 --- a/source/mysql/exceptions.d +++ b/source/mysql/exceptions.d @@ -147,41 +147,3 @@ class MYXInvalidatedRange: MYX super(msg, file, line); } } - -@("wrongFunctionException") -debug(MYSQLN_TESTS) -unittest -{ - import std.exception; - import mysql.commands; - import mysql.connection; - import mysql.prepared; - import mysql.test.common : scopedCn, createCn; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `wrongFunctionException`"); - cn.exec("CREATE TABLE `wrongFunctionException` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable insertSQL = "INSERT INTO `wrongFunctionException` VALUES (1), (2)"; - immutable selectSQL = "SELECT * FROM `wrongFunctionException`"; - Prepared preparedInsert; - Prepared preparedSelect; - int queryTupleResult; - assertNotThrown!MYXWrongFunction(cn.exec(insertSQL)); - assertNotThrown!MYXWrongFunction(cn.query(selectSQL).each()); - assertNotThrown!MYXWrongFunction(cn.queryRowTuple(selectSQL, queryTupleResult)); - assertNotThrown!MYXWrongFunction(preparedInsert = cn.prepare(insertSQL)); - assertNotThrown!MYXWrongFunction(preparedSelect = cn.prepare(selectSQL)); - assertNotThrown!MYXWrongFunction(cn.exec(preparedInsert)); - assertNotThrown!MYXWrongFunction(cn.query(preparedSelect).each()); - assertNotThrown!MYXWrongFunction(cn.queryRowTuple(preparedSelect, queryTupleResult)); - - assertThrown!MYXResultRecieved(cn.exec(selectSQL)); - assertThrown!MYXNoResultRecieved(cn.query(insertSQL).each()); - assertThrown!MYXNoResultRecieved(cn.queryRowTuple(insertSQL, queryTupleResult)); - assertThrown!MYXResultRecieved(cn.exec(preparedSelect)); - assertThrown!MYXNoResultRecieved(cn.query(preparedInsert).each()); - assertThrown!MYXNoResultRecieved(cn.queryRowTuple(preparedInsert, queryTupleResult)); -} diff --git a/source/mysql/package.d b/source/mysql/package.d index d5c977e2..36d62721 100644 --- a/source/mysql/package.d +++ b/source/mysql/package.d @@ -76,7 +76,7 @@ public import mysql.protocol.constants : SvrCapFlags; public import mysql.result; public import mysql.types; -debug(MYSQLN_TESTS) version = DoCoreTests; +/+debug(MYSQLN_TESTS) version = DoCoreTests; debug(MYSQLN_CORE_TESTS) version = DoCoreTests; version(DoCoreTests) @@ -96,4 +96,4 @@ version(DoCoreTests) { void main() {} } -} +}+/ diff --git a/source/mysql/pool.d b/source/mysql/pool.d index 8772a048..2d89c1af 100644 --- a/source/mysql/pool.d +++ b/source/mysql/pool.d @@ -17,10 +17,6 @@ import std.typecons; import mysql.connection; import mysql.prepared; import mysql.protocol.constants; -debug(MYSQLN_TESTS) -{ - import mysql.test.common; -} version(Have_vibe_core) { @@ -195,7 +191,7 @@ version(IncludeMySQLPool) /// Applies any `autoRegister`/`autoRelease` settings to a connection, /// if necessary. - private void applyAuto(T)(T conn) + package void applyAuto(T)(T conn) { foreach(sql, info; preparedRegistrations.directLookup) { @@ -232,50 +228,6 @@ version(IncludeMySQLPool) return m_onNewConnection; } - @("onNewConnection") - debug(MYSQLN_TESTS) - unittest - { - auto count = 0; - void callback(Connection conn) - { - count++; - } - - // Test getting/setting - auto poolA = new MySQLPool(testConnectionStr, &callback); - auto poolB = new MySQLPool(testConnectionStr); - auto poolNoCallback = new MySQLPool(testConnectionStr); - - assert(poolA.onNewConnection == &callback); - assert(poolB.onNewConnection is null); - assert(poolNoCallback.onNewConnection is null); - - poolB.onNewConnection = &callback; - assert(poolB.onNewConnection == &callback); - assert(count == 0); - - // Ensure callback is called - { - auto connA = poolA.lockConnection(); - assert(!connA.closed); - assert(count == 1); - - auto connB = poolB.lockConnection(); - assert(!connB.closed); - assert(count == 2); - } - - // Ensure works with no callback - { - auto oldCount = count; - auto poolC = new MySQLPool(testConnectionStr); - auto connC = poolC.lockConnection(); - assert(!connC.closed); - assert(count == oldCount); - } - } - /++ Forwards to vibe.d's $(LINK2 http://vibed.org/api/vibe.core.connectionpool/ConnectionPool.maxConcurrency, ConnectionPool.maxConcurrency) @@ -470,125 +422,4 @@ version(IncludeMySQLPool) } } } - - @("registration") - debug(MYSQLN_TESTS) - unittest - { - import mysql.commands; - auto pool = new MySQLPool(testConnectionStr); - - // Setup - Connection cn = pool.lockConnection(); - cn.exec("DROP TABLE IF EXISTS `poolRegistration`"); - cn.exec("CREATE TABLE `poolRegistration` ( - `data` LONGBLOB - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - immutable sql = "SELECT * from `poolRegistration`"; - //auto cn2 = pool.lockConnection(); // Seems to return the same connection as `cn` - auto cn2 = pool.createConnection(); - pool.applyAuto(cn2); - assert(cn !is cn2); - - // Tests: - // Initial - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(!cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - // Register on connection #1 - auto prepared = cn.prepare(sql); - { - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - //auto cn3 = pool.lockConnection(); // Seems to return the same connection as `cn` - auto cn3 = pool.createConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } - - // autoRegister - pool.autoRegister(prepared); - { - assert(!pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(!pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - //auto cn3 = pool.lockConnection(); // Seems to return the *same* connection as `cn` - auto cn3 = pool.createConnection(); - pool.applyAuto(cn3); - assert(cn3.isRegistered(sql)); - } - - // autoRelease - pool.autoRelease(prepared); - { - assert(!pool.isAutoCleared(sql)); - assert(!pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - //auto cn3 = pool.lockConnection(); // Seems to return the same connection as `cn` - auto cn3 = pool.createConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } - - // clearAuto - pool.clearAuto(prepared); - { - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - //auto cn3 = pool.lockConnection(); // Seems to return the same connection as `cn` - auto cn3 = pool.createConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } - } - - @("closedConnection") // "cct" - debug(MYSQLN_TESTS) - { - MySQLPool cctPool; - int cctCount=0; - - void cctStart() - { - import std.array; - import mysql.commands; - - cctPool = new MySQLPool(testConnectionStr); - cctPool.onNewConnection = (Connection conn) { cctCount++; }; - assert(cctCount == 0); - - auto cn = cctPool.lockConnection(); - assert(!cn.closed); - cn.close(); - assert(cn.closed); - assert(cctCount == 1); - } - - unittest - { - cctStart(); - assert(cctCount == 1); - - auto cn = cctPool.lockConnection(); - assert(cctCount == 1); - assert(!cn.closed); - } - } } diff --git a/source/mysql/prepared.d b/source/mysql/prepared.d index 385eaf7e..a7b1d6df 100644 --- a/source/mysql/prepared.d +++ b/source/mysql/prepared.d @@ -13,8 +13,6 @@ import mysql.protocol.comms; import mysql.protocol.constants; import mysql.protocol.packets; import mysql.result; -debug(MYSQLN_TESTS) - import mysql.test.common; /++ A struct to represent specializations of prepared statement parameters. @@ -38,101 +36,6 @@ struct ParameterSpecialization ///ditto alias PSN = ParameterSpecialization; -@("paramSpecial") -debug(MYSQLN_TESTS) -unittest -{ - import std.array; - import std.range; - import mysql.connection; - import mysql.test.common; - mixin(scopedCn); - - // Setup - cn.exec("DROP TABLE IF EXISTS `paramSpecial`"); - cn.exec("CREATE TABLE `paramSpecial` ( - `data` LONGBLOB - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below - auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - auto data = alph.cycle.take(totalSize).array; - - int chunkSize; - const(ubyte)[] dataToSend; - bool finished; - uint sender(ubyte[] chunk) - { - assert(!finished); - assert(chunk.length == chunkSize); - - if(dataToSend.length < chunkSize) - { - auto actualSize = cast(uint) dataToSend.length; - chunk[0..actualSize] = dataToSend[]; - finished = true; - dataToSend.length = 0; - return actualSize; - } - else - { - chunk[] = dataToSend[0..chunkSize]; - dataToSend = dataToSend[chunkSize..$]; - return chunkSize; - } - } - - immutable selectSQL = "SELECT `data` FROM `paramSpecial`"; - - // Sanity check - cn.exec("INSERT INTO `paramSpecial` VALUES (\""~(cast(string)data)~"\")"); - auto value = cn.queryValue(selectSQL); - assert(!value.isNull); - assert(value.get == data); - - { - // Clear table - cn.exec("DELETE FROM `paramSpecial`"); - value = cn.queryValue(selectSQL); // Ensure deleted - assert(value.isNull); - - // Test: totalSize as a multiple of chunkSize - chunkSize = 100; - assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); - auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender); - - finished = false; - dataToSend = data; - auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)"); - prepared.setArg(0, cast(ubyte[])[], paramSpecial); - assert(cn.exec(prepared) == 1); - value = cn.queryValue(selectSQL); - assert(!value.isNull); - assert(value.get == data); - } - - { - // Clear table - cn.exec("DELETE FROM `paramSpecial`"); - value = cn.queryValue(selectSQL); // Ensure deleted - assert(value.isNull); - - // Test: totalSize as a non-multiple of chunkSize - chunkSize = 64; - assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize); - auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender); - - finished = false; - dataToSend = data; - auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)"); - prepared.setArg(0, cast(ubyte[])[], paramSpecial); - assert(cn.exec(prepared) == 1); - value = cn.queryValue(selectSQL); - assert(!value.isNull); - assert(value.get == data); - } -} - /++ Encapsulation of a prepared statement. @@ -239,52 +142,6 @@ public: setArg(index, val.get(), psn); } - @("setArg-typeMods") - debug(MYSQLN_TESTS) - unittest - { - import mysql.test.common; - mixin(scopedCn); - - // Setup - cn.exec("DROP TABLE IF EXISTS `setArg-typeMods`"); - cn.exec("CREATE TABLE `setArg-typeMods` ( - `i` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto insertSQL = "INSERT INTO `setArg-typeMods` VALUES (?)"; - - // Sanity check - { - int i = 111; - assert(cn.exec(insertSQL, i) == 1); - auto value = cn.queryValue("SELECT `i` FROM `setArg-typeMods`"); - assert(!value.isNull); - assert(value.get == i); - } - - // Test const(int) - { - const(int) i = 112; - assert(cn.exec(insertSQL, i) == 1); - } - - // Test immutable(int) - { - immutable(int) i = 113; - assert(cn.exec(insertSQL, i) == 1); - } - - // Note: Variant doesn't seem to support - // `shared(T)` or `shared(const(T)`. Only `shared(immutable(T))`. - - // Test shared immutable(int) - { - shared immutable(int) i = 113; - assert(cn.exec(insertSQL, i) == 1); - } - } - /++ Bind a tuple of D variables to the parameters of a prepared statement. @@ -380,67 +237,6 @@ public: return _sql; } - @("setNullArg") - debug(MYSQLN_TESTS) - unittest - { - import mysql.connection; - import mysql.test.common; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `setNullArg`"); - cn.exec("CREATE TABLE `setNullArg` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)"; - immutable selectSQL = "SELECT * FROM `setNullArg`"; - auto preparedInsert = cn.prepare(insertSQL); - assert(preparedInsert.sql == insertSQL); - Row[] rs; - - { - Nullable!int nullableInt; - nullableInt.nullify(); - preparedInsert.setArg(0, nullableInt); - assert(preparedInsert.getArg(0).type == typeid(typeof(null))); - nullableInt = 7; - preparedInsert.setArg(0, nullableInt); - assert(preparedInsert.getArg(0) == 7); - - nullableInt.nullify(); - preparedInsert.setArgs(nullableInt); - assert(preparedInsert.getArg(0).type == typeid(typeof(null))); - nullableInt = 7; - preparedInsert.setArgs(nullableInt); - assert(preparedInsert.getArg(0) == 7); - } - - preparedInsert.setArg(0, 5); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 1); - assert(rs[0][0] == 5); - - preparedInsert.setArg(0, null); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 2); - assert(rs[0][0] == 5); - assert(rs[1].isNull(0)); - assert(rs[1][0].type == typeid(typeof(null))); - - preparedInsert.setArg(0, Variant(null)); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 3); - assert(rs[0][0] == 5); - assert(rs[1].isNull(0)); - assert(rs[2].isNull(0)); - assert(rs[1][0].type == typeid(typeof(null))); - assert(rs[2][0].type == typeid(typeof(null))); - } - /// Gets the number of arguments this prepared statement expects to be passed in. @property ushort numArgs() pure const nothrow { @@ -452,27 +248,6 @@ public: /// from this prepared statement. @property ulong lastInsertID() pure const nothrow { return _lastInsertID; } - @("lastInsertID") - debug(MYSQLN_TESTS) - unittest - { - import mysql.connection; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `testPreparedLastInsertID`"); - cn.exec("CREATE TABLE `testPreparedLastInsertID` ( - `a` INTEGER NOT NULL AUTO_INCREMENT, - PRIMARY KEY (a) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()"); - cn.exec(stmt); - assert(stmt.lastInsertID == 1); - cn.exec(stmt); - assert(stmt.lastInsertID == 2); - cn.exec(stmt); - assert(stmt.lastInsertID == 3); - } - /// Gets the prepared header's field descriptions. @property FieldDescription[] preparedFieldDescriptions() pure { return _headers.fieldDescriptions; } @@ -494,6 +269,26 @@ private enum isPreparedRegistrationsPayload(Payload) = p.queuedForRelease = true; }); +debug(MYSQLN_TESTS) +{ + // Test template constraint + struct TestPreparedRegistrationsBad1 { } + struct TestPreparedRegistrationsBad2 { bool foo = false; } + struct TestPreparedRegistrationsBad3 { int queuedForRelease = 1; } + struct TestPreparedRegistrationsBad4 { bool queuedForRelease = true; } + struct TestPreparedRegistrationsGood1 { bool queuedForRelease = false; } + struct TestPreparedRegistrationsGood2 { bool queuedForRelease = false; const(char)[] id; } + + static assert(!isPreparedRegistrationsPayload!int); + static assert(!isPreparedRegistrationsPayload!bool); + static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad1); + static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad2); + static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad3); + static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad4); + //static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood1); + //static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood2); +} + /++ Common functionality for recordkeeping of prepared statement registration and queueing for unregister. @@ -596,125 +391,3 @@ package struct PreparedRegistrations(Payload) } } -// Test PreparedRegistrations -debug(MYSQLN_TESTS) -{ - // Test template constraint - struct TestPreparedRegistrationsBad1 { } - struct TestPreparedRegistrationsBad2 { bool foo = false; } - struct TestPreparedRegistrationsBad3 { int queuedForRelease = 1; } - struct TestPreparedRegistrationsBad4 { bool queuedForRelease = true; } - struct TestPreparedRegistrationsGood1 { bool queuedForRelease = false; } - struct TestPreparedRegistrationsGood2 { bool queuedForRelease = false; const(char)[] id; } - - static assert(!isPreparedRegistrationsPayload!int); - static assert(!isPreparedRegistrationsPayload!bool); - static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad1); - static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad2); - static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad3); - static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad4); - //static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood1); - //static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood2); - PreparedRegistrations!TestPreparedRegistrationsGood1 testPreparedRegistrationsGood1; - PreparedRegistrations!TestPreparedRegistrationsGood2 testPreparedRegistrationsGood2; - - @("PreparedRegistrations") - unittest - { - // Test init - PreparedRegistrations!TestPreparedRegistrationsGood2 pr; - assert(pr.directLookup.keys.length == 0); - - void resetData(bool isQueued1, bool isQueued2, bool isQueued3) - { - pr.directLookup["1"] = TestPreparedRegistrationsGood2(isQueued1, "1"); - pr.directLookup["2"] = TestPreparedRegistrationsGood2(isQueued2, "2"); - pr.directLookup["3"] = TestPreparedRegistrationsGood2(isQueued3, "3"); - assert(pr.directLookup.keys.length == 3); - } - - // Test resetData (sanity check) - resetData(false, true, false); - assert(pr.directLookup["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr.directLookup["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr.directLookup["3"] == TestPreparedRegistrationsGood2(false, "3")); - - // Test opIndex - resetData(false, true, false); - pr.directLookup["1"] = TestPreparedRegistrationsGood2(false, "1"); - pr.directLookup["2"] = TestPreparedRegistrationsGood2(true, "2"); - pr.directLookup["3"] = TestPreparedRegistrationsGood2(false, "3"); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - assert(pr["4"].isNull); - - // Test queueForRelease - resetData(false, true, false); - pr.queueForRelease("2"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - - pr.queueForRelease("3"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); - - pr.queueForRelease("4"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); - - // Test unqueueForRelease - resetData(false, true, false); - pr.unqueueForRelease("1"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - - pr.unqueueForRelease("2"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - - pr.unqueueForRelease("4"); - assert(pr.directLookup.keys.length == 3); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - - // Test queueAllForRelease - resetData(false, true, false); - pr.queueAllForRelease(); - assert(pr["1"] == TestPreparedRegistrationsGood2(true, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); - assert(pr["3"] == TestPreparedRegistrationsGood2(true, "3")); - assert(pr["4"].isNull); - - // Test clear - resetData(false, true, false); - pr.clear(); - assert(pr.directLookup.keys.length == 0); - - // Test registerIfNeeded - auto doRegister(const(char[]) sql) { return TestPreparedRegistrationsGood2(false, sql); } - pr.registerIfNeeded("1", &doRegister); - assert(pr.directLookup.keys.length == 1); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - - pr.registerIfNeeded("1", &doRegister); - assert(pr.directLookup.keys.length == 1); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - - pr.registerIfNeeded("2", &doRegister); - assert(pr.directLookup.keys.length == 2); - assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); - assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2")); - } -} diff --git a/source/mysql/result.d b/source/mysql/result.d index be9a5555..0928b53c 100644 --- a/source/mysql/result.d +++ b/source/mysql/result.d @@ -81,31 +81,6 @@ public: return _names[index]; } - @("getName") - debug(MYSQLN_TESTS) - unittest - { - import mysql.test.common; - import mysql.commands; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `row_getName`"); - cn.exec("CREATE TABLE `row_getName` (someValue INTEGER, another INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `row_getName` VALUES (1, 2), (3, 4)"); - - enum sql = "SELECT another, someValue FROM `row_getName`"; - - auto rows = cn.query(sql).array; - assert(rows.length == 2); - assert(rows[0][0] == 2); - assert(rows[0][1] == 1); - assert(rows[0].getName(0) == "another"); - assert(rows[0].getName(1) == "someValue"); - assert(rows[1][0] == 4); - assert(rows[1][1] == 3); - assert(rows[1].getName(0) == "another"); - assert(rows[1].getName(1) == "someValue"); - } - /++ Check if a column in the result row was NULL From 592e218bd415fd416b2de12f09e510c73ab02a19 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Mon, 24 May 2021 22:00:07 -0400 Subject: [PATCH 03/13] Remove extraneous main function and extra imports that are no longer relevant. --- source/mysql/package.d | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/source/mysql/package.d b/source/mysql/package.d index 36d62721..b0f919c3 100644 --- a/source/mysql/package.d +++ b/source/mysql/package.d @@ -75,25 +75,3 @@ public import mysql.prepared; public import mysql.protocol.constants : SvrCapFlags; public import mysql.result; public import mysql.types; - -/+debug(MYSQLN_TESTS) version = DoCoreTests; -debug(MYSQLN_CORE_TESTS) version = DoCoreTests; - -version(DoCoreTests) -{ - public import mysql.protocol.comms; - public import mysql.protocol.constants; - public import mysql.protocol.extra_types; - public import mysql.protocol.packet_helpers; - public import mysql.protocol.packets; - public import mysql.protocol.sockets; - - public import mysql.test.common; - public import mysql.test.integration; - public import mysql.test.regression; - - version(MYSQLN_TESTS_NO_MAIN) {} else - { - void main() {} - } -}+/ From 51aa526e812a06c870922fb7adf053f86b9d909b Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Mon, 24 May 2021 22:02:50 -0400 Subject: [PATCH 04/13] these shouldn't have been added. --- testconn/.dub.sdl.swp | Bin 12288 -> 0 bytes testconn/source/.app.d.swp | Bin 12288 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 testconn/.dub.sdl.swp delete mode 100644 testconn/source/.app.d.swp diff --git a/testconn/.dub.sdl.swp b/testconn/.dub.sdl.swp deleted file mode 100644 index fa315badd4a19094e69a5f74014bf5acc102fa3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2y^j+|7>D0W6J5gPP>_-)QeGCMaOl=w6d{Ue4n=T?FX0PFG?(%2*t^_%*6i#$ za&bzebch}hbdge|2O&X60R{X6wAaueMM;^0{Pt|%&bMAWWukYaN2~dIcjlQHTb8F) zyS#dik5?Lmw4dne&EJ1`vo%Mjmxyet``YFe<*AvsGuAz)yp=fKa4#ltC5k;2jdiPD zj-#xrZFLyFK-_O)l8i@fhAO^&M7!U(uKn#chG4TH|koM;2Crsso(R4l>J?F-r zqpUo{fEW-1Vn7Ut0Wly3#DEwO17bi7h=Kp00p$~Y^9j*)kpKVx_V@qmMWV;xH}EC6 zjrv=ob_&mR@HzMl{JlW*5Hv zZ$3sl@D%(CegQpj1$+(mgGI0awmu?y0W4St-vS1I%@h3z{s7Ox18@)A1$V&Dpao8W zFMuR5AO^&M7!U(uKn&34DA9Z`(JNUP`6uIUR~g^3gZqh`WQH>nrf#}9xk*VCdQYs~ z#M>tZEv_jp-8IgjH0-aURaaCtrt|&#xueZ`macbIXxMex zZF4I$)hw;}4$*vHCpL@?)+1ks;>g9NyR{7GJG-1|F(=j+R1_5L+FsM@vHgOQy;88K z@1@S(uY44X`bx^W-* z^P#uFP1RD7%IwAo-n5~1jfiG2QZu~`4X>$|ARA9uVvwpX{_ z+3bvcNdX}qka(*qfrNMkA;>fD5dt0%FNjyD)GBQys;W<|+6Nx`o!#~M;#>?-rAkP9 zx{vIgo%#Lw&dhK2blW?%U8~b`+0z6+PZ09an>T-R_|J#Pg$yB5@d=mu#^N7S_w(dx zomm?~?5wr|k#&W|+__=7ZQ+hbT;?LfE0PYyi01~5%8hQwYwoJYlr!Nu#ihzgF%B%A zW4@oY=jn{np4gLiF2tEb3z{lW2@O$8bm%wR|0~v4} zJOI9b3^u@DzAwl0^SA!Y=URO zKOZLKYj6|12^c7Yqu_535%MK?AG{7a;1qZWeESR718;#>!EeD;a2^~1|AF5B0=L0u zpa-g81FV4O00nwpj)CMSQQ-ftz-wd)zadMzfunfe_0p-!YFVaEQq^;1CXd^%fW8Pg zvxaHiNb}Sh2OZXPxy<0+Y-E`!)?<>>m1Vg?hfLCCNzvr;WtoLZyLWOnH0yG2poVw1 z&_aQ(olm9KQ!*6>z{X0<#?b;D+1b#lOo#WatmH1YRCyz9_jC&)o(2q`d0;qFQ3N}S zwJOG*t>d%fpmoks$2c-=DRdnV5B=`-4qex|VmfI=gs`SpX-f}{ok7ts$k1~nZ++}4 z$9FjmxWrc~IT-uC2vnG`(@$H>m-P5?YEFU@^Q$f!$ZUPf=+wIHiiuBiPxv{LQZdCx z+`A8pW~bPx)GLj2wm$!tsC_b}N+GpWWKnQM!+i;CmM)d zN&8{x3>XSSKow>$YG(aDiZHhqd8szH8s%=I+36OmRaA*ec`j~4cmt>{<*o?qg#kB> za@S}!HoL8&XlC1`bbR7`^7t+- zMdo2Gtv#74(5d3U&jcfYG8XB4ia@Ri`K zkGqloILGvsytz5UbA09%SW{|iFZtQa~wTz&qk2d;*ZQuc9PdQ9&7zGOR#IdB*l79jUw&w?HQr4CJM?D{zOBr7z-#5Beh2rTor((LXg5 zQWe72ul2XQ*0ONs%&F(|I4^>NR!V=#`@42{G2KdeyBA*Dy;OcG>xemCtmvgHVI&zs z;^~pf&H_i|r!C=xVX>Xak)`(73pI Date: Tue, 25 May 2021 20:45:52 -0400 Subject: [PATCH 05/13] Add phobos tests. Work around dub bug that doesn't allow dependencies based on configuration. --- .github/workflows/integration-testing.yml | 28 +- dub.sdl | 2 + integration-tests-phobos/dub.sdl | 8 + integration-tests-phobos/source/dummy.d | 0 integration-tests-vibe/dub.sdl | 9 + integration-tests-vibe/source/dummy.d | 0 integration-tests/dub.sdl | 21 +- integration-tests/source/app.d | 13 + integration-tests/source/mysql/maintests.d | 317 +++++++++++---------- 9 files changed, 217 insertions(+), 181 deletions(-) create mode 100644 integration-tests-phobos/dub.sdl create mode 100644 integration-tests-phobos/source/dummy.d create mode 100644 integration-tests-vibe/dub.sdl create mode 100644 integration-tests-vibe/source/dummy.d diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index c4bddf25..6aefe17b 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -117,15 +117,19 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: sudo apt-get update && sudo apt-get install libevent-dev -y - ## Turns out the unittest-vibe-ut tried to connect to an actualy MySQL on 172.18.0.1 so it's not - ## actually a unit test at all. It's an integration test and should be pulled out from the main - ## codebase into a separate sub module - - name: Run unittest-vibe + - name: Set up test connection string env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt - dub run ":integration-tests" + + - name: Run unittests with Vibe.d + run: | + dub run ":integration-tests-vibe" + + - name: Run unittests with Phobos + run: | + dub run ":integration-tests-phobos" - name: Build The Example Project working-directory: ./examples/homePage @@ -182,15 +186,19 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: sudo apt-get update && sudo apt-get install libevent-dev -y - ## Turns out the unittest-vibe-ut tried to connect to an actualy MySQL on 172.18.0.1 so it's not - ## actually a unit test at all. It's an integration test and should be pulled out from the main - ## codebase into a separate sub module - - name: Run unittest-vibe-ut + - name: Set up test connection string env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt - dub run ":integration-tests" + + - name: Run unittests with Vibe.d + run: | + dub run ":integration-tests-vibe" + + - name: Run unittests with Phobos + run: | + dub run ":integration-tests-phobos" - name: Build The Example Project working-directory: ./examples/homePage diff --git a/dub.sdl b/dub.sdl index 7190feaf..36dfc08a 100644 --- a/dub.sdl +++ b/dub.sdl @@ -9,4 +9,6 @@ dependency "vibe-core" version="~>1.16.0" optional=true toolchainRequirements frontend=">=2.068" subPackage "./integration-tests" +subPackage "./integration-tests-vibe" +subPackage "./integration-tests-phobos" subPackage "./testconn" diff --git a/integration-tests-phobos/dub.sdl b/integration-tests-phobos/dub.sdl new file mode 100644 index 00000000..617c21e7 --- /dev/null +++ b/integration-tests-phobos/dub.sdl @@ -0,0 +1,8 @@ +name "integration-tests-phobos" +description "Phobos tests for mysql-native" +license "BSL-1.0" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" +authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" + +dependency "mysql-native:integration-tests" path="../" +targetType "executable" diff --git a/integration-tests-phobos/source/dummy.d b/integration-tests-phobos/source/dummy.d new file mode 100644 index 00000000..e69de29b diff --git a/integration-tests-vibe/dub.sdl b/integration-tests-vibe/dub.sdl new file mode 100644 index 00000000..28a78bb7 --- /dev/null +++ b/integration-tests-vibe/dub.sdl @@ -0,0 +1,9 @@ +name "integration-tests-vibe" +description "Vibe tests for mysql-native" +license "BSL-1.0" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" +authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" + +dependency "mysql-native:integration-tests" path="../" +dependency "vibe-core" version="~>1.16.0" +targetType "executable" diff --git a/integration-tests-vibe/source/dummy.d b/integration-tests-vibe/source/dummy.d new file mode 100644 index 00000000..e69de29b diff --git a/integration-tests/dub.sdl b/integration-tests/dub.sdl index 7073036d..d42577e3 100644 --- a/integration-tests/dub.sdl +++ b/integration-tests/dub.sdl @@ -1,21 +1,14 @@ name "integration-tests" description "Test harness for integration tests" license "BSL-1.0" -copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, and Nick Sabalausky" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" dependency "mysql-native" path="../" -dependency "unit-threaded" version="~>0.7.45" -dependency "vibe-core" version="~>1.0" +dependency "unit-threaded" version="~>1.0.15" +debugVersions "MYSQLN_TESTS" +versions "unitUnthreaded" +targetType "library" -configuration "unittest" { - targetType "executable" - debugVersions "MYSQLN_TESTS" - versions "MYSQLN_TESTS_NO_MAIN" - versions "unitUnthreaded" - sourceFiles "bin/ut.d" - importPaths "bin/" - buildOptions "unittests" - - preBuildCommands "dub run unit-threaded -c gen_ut_main -- -f bin/ut.d" -} +// this is needed to make the unittests compile, even though there's a warning +buildOptions "unittests" "debugMode" "debugInfo" diff --git a/integration-tests/source/app.d b/integration-tests/source/app.d index 92a3b3e1..3c4986d8 100644 --- a/integration-tests/source/app.d +++ b/integration-tests/source/app.d @@ -2,3 +2,16 @@ import mysql.test.common; import mysql.test.integration; import mysql.test.regression; import mysql.maintests; + +import unit_threaded; + +// manual unit-threaded main function +int main(string[] args) +{ + return args.runTests!( + "mysql.maintests", + "mysql.test.common", + "mysql.test.integration", + "mysql.test.regression" + ); +} diff --git a/integration-tests/source/mysql/maintests.d b/integration-tests/source/mysql/maintests.d index e8e62ab4..b9c29a0d 100644 --- a/integration-tests/source/mysql/maintests.d +++ b/integration-tests/source/mysql/maintests.d @@ -59,7 +59,7 @@ unittest chunkSize = 100; assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); - + received = null; lastValueOfFinished = false; value = cn.queryValue(selectSQL, [columnSpecial]); @@ -69,7 +69,7 @@ unittest //assert(lastValueOfFinished == true); //assert(received == data); } - + // Use ColumnSpecialization with sql string, // and totalSize as a non-multiple of chunkSize { @@ -133,17 +133,17 @@ unittest import mysql.connection; import mysql.test.common; mixin(scopedCn); - + cn.exec("DROP TABLE IF EXISTS `execOverloads`"); cn.exec("CREATE TABLE `execOverloads` ( `i` INTEGER, `s` VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - + immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)"; - + // Do the inserts, using exec - + // exec: const(char[]) sql assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1); assert(cn.exec(prepareSQL, 2, "bb") == 1); @@ -157,18 +157,18 @@ unittest assert(cn.exec(prepared, 5, "ee") == 1); assert(prepared.getArg(0) == 5); assert(prepared.getArg(1) == "ee"); - + assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1); assert(prepared.getArg(0) == 6); assert(prepared.getArg(1) == "ff"); - + // exec: bcPrepared sql auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); bcPrepared.setArgs(7, "gg"); assert(cn.exec(bcPrepared) == 1); assert(bcPrepared.getArg(0) == 7); assert(bcPrepared.getArg(1) == "gg"); - + // Check results auto rows = cn.query("SELECT * FROM `execOverloads`").array(); assert(rows.length == 7); @@ -205,7 +205,7 @@ unittest import mysql.connection; import mysql.test.common; mixin(scopedCn); - + cn.exec("DROP TABLE IF EXISTS `queryOverloads`"); cn.exec("CREATE TABLE `queryOverloads` ( `i` INTEGER, @@ -214,7 +214,7 @@ unittest cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")"); immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?"; - + // Test query { Row[] rows; @@ -826,165 +826,168 @@ unittest } // mysql.pool -@("onNewConnection") -debug(MYSQLN_TESTS) -unittest +version(Have_vibe_core) { - auto count = 0; - void callback(Connection conn) - { - count++; - } + @("onNewConnection") + debug(MYSQLN_TESTS) + unittest + { + auto count = 0; + void callback(Connection conn) + { + count++; + } - // Test getting/setting - auto poolA = new MySQLPool(testConnectionStr, &callback); - auto poolB = new MySQLPool(testConnectionStr); - auto poolNoCallback = new MySQLPool(testConnectionStr); + // Test getting/setting + auto poolA = new MySQLPool(testConnectionStr, &callback); + auto poolB = new MySQLPool(testConnectionStr); + auto poolNoCallback = new MySQLPool(testConnectionStr); - assert(poolA.onNewConnection == &callback); - assert(poolB.onNewConnection is null); - assert(poolNoCallback.onNewConnection is null); + assert(poolA.onNewConnection == &callback); + assert(poolB.onNewConnection is null); + assert(poolNoCallback.onNewConnection is null); - poolB.onNewConnection = &callback; - assert(poolB.onNewConnection == &callback); - assert(count == 0); + poolB.onNewConnection = &callback; + assert(poolB.onNewConnection == &callback); + assert(count == 0); - // Ensure callback is called - { - auto connA = poolA.lockConnection(); - assert(!connA.closed); - assert(count == 1); + // Ensure callback is called + { + auto connA = poolA.lockConnection(); + assert(!connA.closed); + assert(count == 1); - auto connB = poolB.lockConnection(); - assert(!connB.closed); - assert(count == 2); - } + auto connB = poolB.lockConnection(); + assert(!connB.closed); + assert(count == 2); + } - // Ensure works with no callback - { - auto oldCount = count; - auto poolC = new MySQLPool(testConnectionStr); - auto connC = poolC.lockConnection(); - assert(!connC.closed); - assert(count == oldCount); - } -} + // Ensure works with no callback + { + auto oldCount = count; + auto poolC = new MySQLPool(testConnectionStr); + auto connC = poolC.lockConnection(); + assert(!connC.closed); + assert(count == oldCount); + } + } -@("registration") -debug(MYSQLN_TESTS) -unittest -{ - import mysql.commands; - auto pool = new MySQLPool(testConnectionStr); + @("registration") + debug(MYSQLN_TESTS) + unittest + { + import mysql.commands; + auto pool = new MySQLPool(testConnectionStr); - // Setup - auto cn = pool.lockConnection(); - cn.exec("DROP TABLE IF EXISTS `poolRegistration`"); - cn.exec("CREATE TABLE `poolRegistration` ( - `data` LONGBLOB - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - immutable sql = "SELECT * from `poolRegistration`"; - auto cn2 = pool.lockConnection(); - pool.applyAuto(cn2); - assert(cn !is cn2); - - // Tests: - // Initial - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(!cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - // Register on connection #1 - auto prepared = cn.prepare(sql); - { - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - auto cn3 = pool.lockConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } + // Setup + auto cn = pool.lockConnection(); + cn.exec("DROP TABLE IF EXISTS `poolRegistration`"); + cn.exec("CREATE TABLE `poolRegistration` ( + `data` LONGBLOB + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + immutable sql = "SELECT * from `poolRegistration`"; + auto cn2 = pool.lockConnection(); + pool.applyAuto(cn2); + assert(cn !is cn2); + + // Tests: + // Initial + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(!cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + // Register on connection #1 + auto prepared = cn.prepare(sql); + { + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } - // autoRegister - pool.autoRegister(prepared); - { - assert(!pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(!pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - auto cn3 = pool.lockConnection(); - pool.applyAuto(cn3); - assert(cn3.isRegistered(sql)); - } + // autoRegister + pool.autoRegister(prepared); + { + assert(!pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(!pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(cn3.isRegistered(sql)); + } - // autoRelease - pool.autoRelease(prepared); - { - assert(!pool.isAutoCleared(sql)); - assert(!pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - auto cn3 = pool.lockConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } + // autoRelease + pool.autoRelease(prepared); + { + assert(!pool.isAutoCleared(sql)); + assert(!pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } - // clearAuto - pool.clearAuto(prepared); - { - assert(pool.isAutoCleared(sql)); - assert(pool.isAutoRegistered(sql)); - assert(pool.isAutoReleased(sql)); - assert(cn.isRegistered(sql)); - assert(!cn2.isRegistered(sql)); - - auto cn3 = pool.lockConnection(); - pool.applyAuto(cn3); - assert(!cn3.isRegistered(sql)); - } -} + // clearAuto + pool.clearAuto(prepared); + { + assert(pool.isAutoCleared(sql)); + assert(pool.isAutoRegistered(sql)); + assert(pool.isAutoReleased(sql)); + assert(cn.isRegistered(sql)); + assert(!cn2.isRegistered(sql)); + + auto cn3 = pool.lockConnection(); + pool.applyAuto(cn3); + assert(!cn3.isRegistered(sql)); + } + } -@("closedConnection") // "cct" -debug(MYSQLN_TESTS) -{ - import mysql.pool; - MySQLPool cctPool; - int cctCount=0; + @("closedConnection") // "cct" + debug(MYSQLN_TESTS) + { + import mysql.pool; + MySQLPool cctPool; + int cctCount=0; - void cctStart() - { - import std.array; - import mysql.commands; - - cctPool = new MySQLPool(testConnectionStr); - cctPool.onNewConnection = (Connection conn) { cctCount++; }; - assert(cctCount == 0); - - auto cn = cctPool.lockConnection(); - assert(!cn.closed); - cn.close(); - assert(cn.closed); - assert(cctCount == 1); - } + void cctStart() + { + import std.array; + import mysql.commands; + + cctPool = new MySQLPool(testConnectionStr); + cctPool.onNewConnection = (Connection conn) { cctCount++; }; + assert(cctCount == 0); + + auto cn = cctPool.lockConnection(); + assert(!cn.closed); + cn.close(); + assert(cn.closed); + assert(cctCount == 1); + } - unittest - { - cctStart(); - assert(cctCount == 1); + unittest + { + cctStart(); + assert(cctCount == 1); - auto cn = cctPool.lockConnection(); - assert(cctCount == 1); - assert(!cn.closed); - } + auto cn = cctPool.lockConnection(); + assert(cctCount == 1); + assert(!cn.closed); + } + } } // mysql.prepared @@ -1201,7 +1204,7 @@ unittest `a` INTEGER NOT NULL AUTO_INCREMENT, PRIMARY KEY (a) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - + auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()"); cn.exec(stmt); assert(stmt.lastInsertID == 1); @@ -1256,7 +1259,7 @@ debug(MYSQLN_TESTS) assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); assert(pr["2"] == TestPreparedRegistrationsGood2(true, "2")); assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3")); - + pr.queueForRelease("3"); assert(pr.directLookup.keys.length == 3); assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1")); @@ -1301,7 +1304,7 @@ debug(MYSQLN_TESTS) resetData(false, true, false); pr.clear(); assert(pr.directLookup.keys.length == 0); - + // Test registerIfNeeded auto doRegister(const(char[]) sql) { return TestPreparedRegistrationsGood2(false, sql); } pr.registerIfNeeded("1", &doRegister); From 4723082d05bfb6cea94fad367ce8f4f94ee1e67f Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 08:12:37 -0400 Subject: [PATCH 06/13] Remove ubuntu "extra packages", update commented-out mysql-8 tests to reflect the same actions as current tests --- .github/workflows/integration-testing.yml | 70 ++++++++++------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index 6aefe17b..bb401f36 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -41,37 +41,37 @@ jobs: # --health-retries 4 # steps: - # - uses: actions/checkout@v2 - - # - name: Install ${{ matrix.compiler }} - # uses: dlang-community/setup-dlang@v1 - # with: - # compiler: ${{ matrix.compiler }} - - # - name: Install dependencies on Ubuntu - # if: startsWith(matrix.os, 'ubuntu') - # run: sudo apt-get update && sudo apt-get install libevent-dev -y - - # ## Turns out the unittest-vibe-ut tried to connect to an actualy MySQL on 172.18.0.1 so it's not - # ## actually a unit test at all. It's an integration test and should be pulled out from the main - # ## codebase into a separate sub module - # - name: Run unittest-vibe-ut - # env: - # MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} - # run: | - # echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt - # dub run -c unittest-vibe-ut -- -t - - # - name: Build The Example Project - # working-directory: ./examples/homePage - # run: dub build - - # - name: Run Example (MySQL 8) - # working-directory: ./examples/homePage - # env: - # MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} - # run: | - # ./example "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" + # - uses: actions/checkout@v2 + + # - name: Install ${{ matrix.compiler }} + # uses: dlang-community/setup-dlang@v1 + # with: + # compiler: ${{ matrix.compiler }} + + # - name: Set up test connection string + # env: + # MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + # run: | + # echo "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" > testConnectionStr.txt + + # - name: Run unittests with Vibe.d + # run: | + # dub run ":integration-tests-vibe" + + # - name: Run unittests with Phobos + # run: | + # dub run ":integration-tests-phobos" + + # - name: Build The Example Project + # working-directory: ./examples/homePage + # run: dub build + + # - name: Run Example (MySQL 8) + # working-directory: ./examples/homePage + # env: + # MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + # run: | + # ./example "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" mysql57-tests: name: MySQL 5.7 Tests ${{ matrix.compiler }} @@ -113,10 +113,6 @@ jobs: with: compiler: ${{ matrix.compiler }} - - name: Install dependencies on Ubuntu - if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libevent-dev -y - - name: Set up test connection string env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} @@ -182,10 +178,6 @@ jobs: with: compiler: ${{ matrix.compiler }} - - name: Install dependencies on Ubuntu - if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libevent-dev -y - - name: Set up test connection string env: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} From 466fc39335a703e77ffcf77270a6603898933e16 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 09:06:12 -0400 Subject: [PATCH 07/13] Try only building on PRs and pushes to master. Otherwise, github wastes time building my branch in my repo, and the PR. --- .editorconfig | 4 ++++ .github/workflows/dub.yml | 2 ++ .github/workflows/integration-testing.yml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/.editorconfig b/.editorconfig index 8949f97e..883ecf7c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,7 @@ trim_trailing_whitespace=true insert_final_newline=true end_of_line=lf charset=utf-8 + +[*.yml] +indent_style=space +indent_size=2 diff --git a/.github/workflows/dub.yml b/.github/workflows/dub.yml index 33d18666..8891e709 100644 --- a/.github/workflows/dub.yml +++ b/.github/workflows/dub.yml @@ -9,6 +9,8 @@ on: schedule: - cron: '30 7 1 * *' push: + branches: + - master pull_request: jobs: diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index bb401f36..4c6b461b 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -9,6 +9,8 @@ on: schedule: - cron: '30 7 1 * *' push: + branches: + - master pull_request: jobs: From a148f584623c983a423f35fc3f5523d060f7202e Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 09:48:12 -0400 Subject: [PATCH 08/13] Remove obsolete test runner. Update README to reflect new ways of running tests (and update broken links) --- README.md | 50 ++++++--- run_tests.d | 312 ---------------------------------------------------- 2 files changed, 36 insertions(+), 326 deletions(-) delete mode 100644 run_tests.d diff --git a/README.md b/README.md index 1aa95ede..723adfb1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ MySQL native [![GitHub - Builds](https://github.com/mysql-d/mysql-native/actions/workflows/dub.yml/badge.svg)](https://github.com/mysql-d/mysql-native/actions/workflows/dub.yml) [![GitHub - Integration Tests](https://github.com/mysql-d/mysql-native/actions/workflows/integration-testing.yml/badge.svg)](https://github.com/mysql-d/mysql-native/actions/workflows/integration-testing.yml) +*NOTE: we are in the process of migrating to github actions. Documentation will +eventually be generated using github actions, and stored on github. This README +is in flux at the moment, and may contain outdated information* + A [Boost-licensed](http://www.boost.org/LICENSE_1_0.txt) native [D](http://dlang.org) client driver for MySQL and MariaDB. @@ -89,7 +93,7 @@ void main(string[] args) "SELECT * FROM `tablename` WHERE `name`=? OR `name`=?", "Bob", "Bobby"); bobs.close(); // Skip them - + Row[] rs = conn.query( // Same SQL as above, but only prepared once and is reused! "SELECT * FROM `tablename` WHERE `name`=? OR `name`=?", "Bob", "Ann").array; // Get ALL the rows at once @@ -119,26 +123,41 @@ Additional notes This requires MySQL server v4.1.1 or later, or a MariaDB server. Older versions of MySQL server are obsolete, use known-insecure authentication, -and are not supported by this package. +and are not supported by this package. Currently the github actions tests use +MySQL 5.7 and MariaDB 10. MySQL 8 is supported with `mysql_native_password` +authentication, but is not currently tested. Expect this to change in the future. Normally, MySQL clients connect to a server on the same machine via a Unix socket on *nix systems, and through a named pipe on Windows. Neither of these conventions is currently supported. TCP is used for all connections. -For historical reference, see the [old homepage](http://britseyeview.com/software/mysqln/) -for the original release of this project. Note, however, that version has -become out-of-date. +Unfortunately, the original home page of Steve Teale's mysqln is no longer +available. You can see an archive on the [Internet Archive wayback +machine](https://web.archive.org/web/20120323165808/http://britseyeview.com/software/mysqln) Developers - How to run the test suite -------------------------------------- -This package contains various unittests and integration tests. To run them, -run `run-tests`. +Unittests that do not require an actual server are located in the library +codebase. At the moment, there is no mechanism for running just these tests +(this will likely change in the future). + +Unittests that require a working server are all located in the +[integration-tests](integration-tests) subpackage. Due to a [dub +issue](https://github.com/dlang/dub/issues/2136), the integration tests are run +using the [integration-tests-phobos](integration-tests-phobos) and +[integration-tests-vibe](integration-tests-vibe) subpackages. At some point, if this dub issue +is fixed, they will simply become configurations in the main integration-tests +repository. You can run these directly from the main repository folder by +issuing the commands: + +```sh +dub run :integration-tests-phobos +dub run :integration-tests-vibe +``` -The first time you run `run-tests`, it will automatically create a -file `testConnectionStr.txt` in project's base diretory and then exit. -This file is deliberately not contained in the source repository -because it's specific to your system. +The first time you run an integration test, the file `testConnectionStr.txt` +will be created in your current directory Open the `testConnectionStr.txt` file and verify the connection settings inside, modifying them as needed, and if necessary, creating a test user and @@ -148,6 +167,9 @@ The tests will completely clobber anything inside the db schema provided, but they will ONLY modify that one db schema. No other schema will be modified in any way. -After you've configured the connection string, run `run-tests` again -and their tests will be compiled and run, first using Phobos sockets, -then using Vibe sockets. +After you've configured the connection string, run the integration tests again. + +The integration tests use +[unit-threaded](https://code.dlang.org/packages/unit-threaded) which allows for +running individual named tests. Use this for running specific tests instead of +the whole suite. diff --git a/run_tests.d b/run_tests.d deleted file mode 100644 index 1d5099b7..00000000 --- a/run_tests.d +++ /dev/null @@ -1,312 +0,0 @@ -import std.file; -import std.getopt; -import std.process; -import std.file; -import std.process; -import std.stdio; -import std.string; - -// Commandline args -bool useUnitThreaded; -bool coreTestsOnly; -enum Mode { phobos, vibe, combined, travis }; -Mode mode; -string[] unitThreadedArgs; - -// Exit code to be returned -int exitCode=0; - -// Utils /////////////////////////////////////////////// - -pure string flagUseUnitThreaded(bool on) -{ - return on? " --ut" : ""; -} - -pure string flagCoreTestsOnly(bool on) -{ - return on? " --core" : ""; -} - -pure string flagMode(Mode mode) -{ - final switch(mode) - { - case Mode.phobos: return " --mode=phobos"; - case Mode.vibe: return " --mode=vibe"; - case Mode.combined: return " --mode=combined"; - case Mode.travis: return " --mode=travis"; - } -} - -string fixSlashes(string path) -{ - version(Windows) return path.replace(`/`, `\`); - else version(Posix) return path.replace(`\`, `/`); - else static assert(0); -} - -string envGet(string name) -{ - return environment.get(name, null); -} - -bool envBool(string name) -{ - return environment.get(name, null) == "true"; -} - -void tryMkdir(string dir) -{ - if(!exists(dir)) - mkdir(dir); -} - -int runFromCurrentDir(string command) -{ - version(Windows) return run(command); - else return run("./"~command); -} - -int run(string command) -{ - writeln(command); - return spawnShell(command).wait; -} - -int run(string[] command) -{ - writeln(command); - return spawnProcess(command).wait; -} - -bool canRun(string command) -{ - return executeShell(command).status == 0; -} - -string findRdmd() -{ - // LDC/GDC don't always include rdmd, so allow user to specify path to it in $RDMD. - // Otherwise, use "rdmd". - // - // For travis, if "rdmd" doesn't work (ie, LDC/GDC is being tested), then use - // the copy of rdmd that was downloaded by the 'ci_setup.d' script. - auto rdmd = environment.get("RDMD"); - if(rdmd is null) - { - rdmd = "rdmd"; - if(!canRun("rdmd --help")) - { - auto travisOsName = environment.get("TRAVIS_OS_NAME"); - if(travisOsName == "osx") - rdmd = "local-dmd/dmd2/"~travisOsName~"/bin/rdmd"; - else - rdmd = "local-dmd/dmd2/"~travisOsName~"/bin64/rdmd"; - } - } - - return rdmd; -} - -// Main (Process command line) /////////////////////////////////////////////// - -int main(string[] args) -{ - try - { - auto argsHelp = getopt(args, - "ut", "Use unit-threaded (Always includes ut's --trace)", &useUnitThreaded, - "core", "Only run basic core tests (Can only be used with --mode=phobos)", &coreTestsOnly, - std.getopt.config.required, - "m|mode", "'-m=phobos': Run Phobos tests | '-m=vibe': Run Vibe.d tests | '-m=combined': Run core-only Phobos tests and all Vibe.d tests | '-m=travis': Travis-CI mode (autodetect settings from envvars)", &mode, - ); - - if(argsHelp.helpWanted) - { - defaultGetoptPrinter( - "Runs the mysql-native test suite with Phobos sockets, Vibe.d sockets, or combined.\n"~ - "\n"~ - "Usage:\n"~ - " run_tests --mode=(phobos|vibe|combined|travis) [OPTIONS] [-- [UNIT-THREADED OPTIONS]]\n"~ - "\n"~ - "Examples:\n"~ - " run_tests --mode=combined\n"~ - " run_tests --ut --mode=vibe -- --help\n"~ - " run_tests --ut --mode=vibe -- --single --random\n"~ - "\n"~ - "Options:", - argsHelp.options - ); - - return 0; - } - } - catch(Exception e) - { - stderr.writeln(e.msg); - stderr.writeln("For help: run_tests --help"); - stderr.writeln("Recommended: run_tests -m=combined"); - return 1; - } - - if(coreTestsOnly && mode==Mode.combined) - { - stderr.writeln("Cannot use --core and --mode=combined together."); - //stderr.writeln("Instead, use:"); - //stderr.writeln(" run_tests --core --mode=phobos && run_tests --core --mode=vibe"); - stderr.writeln("For help: run_tests --help"); - return 1; - } - - if(coreTestsOnly && mode==Mode.vibe) - { - stderr.writeln("Cannot use --core and --mode=vibe together."); - stderr.writeln("For help: run_tests --help"); - return 1; - } - - if(mode==Mode.travis && (coreTestsOnly || useUnitThreaded)) - { - stderr.writeln("Cannot use --mode=travis together with any other option."); - stderr.writeln("For help: run_tests --help"); - return 1; - } - - unitThreadedArgs = args[1..$]; - runTests(); - return exitCode; -} - -// Run Tests /////////////////////////////////////////////// - -void runTests() -{ - //writeln("unitThreadedArgs: ", unitThreadedArgs); - - // GDC doesn't autocreate the dir (and git doesn't beleive in empty dirs) - tryMkdir("bin"); - - final switch(mode) - { - case Mode.phobos: runPhobosTests(); break; - case Mode.vibe: runVibeTests(); break; - case Mode.combined: runCombinedTests(); break; - case Mode.travis: runTravisTests(); break; - } -} - -void runPhobosTests() -{ - // Setup compilers - auto rdmd = findRdmd(); - auto dmd = environment.get("DMD", "dmd"); - - writeln("Using:"); - writeln(" RDMD=", rdmd); - writeln(" DMD=", dmd); - stdout.flush(); - - // Setup --core - auto debugTestsId = coreTestsOnly? "MYSQLN_CORE_TESTS" : "MYSQLN_TESTS"; - auto msgSuffix = coreTestsOnly? " (core tests only)" : ""; - - // Setup unit-threaded - string dFile; - string utSuffix; - string utArgs; - if(useUnitThreaded) - { - msgSuffix = msgSuffix~" (unit-threaded)"; - auto utVer="0.7.45"; - utSuffix="-ut"; - utArgs="-version=MYSQLN_TESTS_NO_MAIN -version=Have_unit_threaded -Iunit-threaded-"~utVer~"/unit-threaded/source/ --extra-file=unit-threaded-"~utVer~"/unit-threaded/libunit-threaded.a --exclude=unit_threaded"; - dFile = "bin/ut.d"; - - // Setup local unit-threaded - run("dub fetch unit-threaded --version="~utVer~" --cache=local"); - chdir(("unit-threaded-"~utVer~"/unit-threaded").fixSlashes); - run("dub build -c gen_ut_main"); - run("dub build -c library"); - chdir(("../..").fixSlashes); - run(("unit-threaded-"~utVer~"/unit-threaded/gen_ut_main -f bin/ut.d").fixSlashes); - } - else - { - dFile = "source/mysql/package.d"; - } - - // Compile tests - writeln("Compiling Phobos-socket tests", msgSuffix, "..."); - auto status = run(rdmd~" --compiler="~dmd~" --build-only -g -unittest "~utArgs~" -debug="~debugTestsId~" -ofbin/mysqln-tests-phobos"~utSuffix~" -Isource "~dFile); - //$RDMD --compiler=$DMD --build-only -g -unittest $UT_ARGS -debug=$DEBUG_TESTS_ID -ofbin/mysqln-tests-phobos${UT_SUFFIX} -Isource $D_FILE - if(status != 0) - { - exitCode = status; - return; - } - - writeln("Running Phobos-socket tests", msgSuffix, "..."); - status = run(["bin/mysqln-tests-phobos"~utSuffix, "-t"] ~ unitThreadedArgs); - // bin/mysqln-tests-phobos${UT_SUFFIX} -t "$@" - if(status != 0) - { - exitCode = status; - return; - } -} - -void runVibeTests() -{ - auto coreMsg = coreTestsOnly? " (core tests only)" : ""; - writeln("Doing Vibe-socket tests", coreMsg, "..."); - - if(useUnitThreaded) - exitCode = run(["dub", "run", "-c", "unittest-vibe-ut", "--", "-t"] ~ unitThreadedArgs); - else - exitCode = run("dub test -c unittest-vibe"); -} - -void runCombinedTests() -{ - auto phobosStatus = runFromCurrentDir( - "run_tests"~ - flagMode(Mode.phobos)~ - flagCoreTestsOnly(true)~ - flagUseUnitThreaded(useUnitThreaded) - ); - if(phobosStatus != 0) - { - exitCode = phobosStatus; - return; - } - - auto vibeStatus = runFromCurrentDir( - "run_tests"~ - flagMode(Mode.vibe)~ - flagCoreTestsOnly(false)~ - flagUseUnitThreaded(useUnitThreaded) - ); - exitCode = vibeStatus; -} - -void runTravisTests() -{ - useUnitThreaded = envBool("USE_UNIT_THREADED"); - auto noVibe = envBool("NO_VIBE"); - - if(noVibe) - { - auto phobosStatus = runFromCurrentDir( - "run_tests"~ - flagMode(Mode.phobos)~ - flagCoreTestsOnly(false)~ - flagUseUnitThreaded(useUnitThreaded) - ); - exitCode = phobosStatus; - } - else - { - runCombinedTests(); - } -} From 48b019326468c07317a151ea180d423459523fab Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 10:21:42 -0400 Subject: [PATCH 09/13] run library tests in integration test applications. Update README to reflect how the tests can be run. --- README.md | 4 ++-- dub.sdl | 7 +++++++ integration-tests/dub.sdl | 1 + integration-tests/source/app.d | 8 +++++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 723adfb1..b7ce422a 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,7 @@ Developers - How to run the test suite -------------------------------------- Unittests that do not require an actual server are located in the library -codebase. At the moment, there is no mechanism for running just these tests -(this will likely change in the future). +codebase. You can run just these tests using `dub test`. Unittests that require a working server are all located in the [integration-tests](integration-tests) subpackage. Due to a [dub @@ -155,6 +154,7 @@ issuing the commands: dub run :integration-tests-phobos dub run :integration-tests-vibe ``` +This will also run the library tests as well as the integration tests. The first time you run an integration test, the file `testConnectionStr.txt` will be created in your current directory diff --git a/dub.sdl b/dub.sdl index 36dfc08a..766b9730 100644 --- a/dub.sdl +++ b/dub.sdl @@ -12,3 +12,10 @@ subPackage "./integration-tests" subPackage "./integration-tests-vibe" subPackage "./integration-tests-phobos" subPackage "./testconn" + +configuration "library" { +} +configuration "unittest" { + buildOptions "unittests" "debugInfo" "debugMode" + debugVersions "MYSQLN_TESTS" +} diff --git a/integration-tests/dub.sdl b/integration-tests/dub.sdl index d42577e3..ea06b269 100644 --- a/integration-tests/dub.sdl +++ b/integration-tests/dub.sdl @@ -5,6 +5,7 @@ copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsj authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" dependency "mysql-native" path="../" +subConfiguration "mysql-native" "unittest" dependency "unit-threaded" version="~>1.0.15" debugVersions "MYSQLN_TESTS" versions "unitUnthreaded" diff --git a/integration-tests/source/app.d b/integration-tests/source/app.d index 3c4986d8..a79fda82 100644 --- a/integration-tests/source/app.d +++ b/integration-tests/source/app.d @@ -2,6 +2,9 @@ import mysql.test.common; import mysql.test.integration; import mysql.test.regression; import mysql.maintests; +import mysql.protocol.packet_helpers; +import mysql.connection; +import mysql.escape; import unit_threaded; @@ -12,6 +15,9 @@ int main(string[] args) "mysql.maintests", "mysql.test.common", "mysql.test.integration", - "mysql.test.regression" + "mysql.test.regression", + "mysql.protocol.packet_helpers", + "mysql.connection", + "mysql.escape" ); } From 2411644921f8809505a9c46c203d827b761660e7 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 10:43:14 -0400 Subject: [PATCH 10/13] Add docker config and readme on how to use it. --- integration-tests/README.md | 22 ++++++++++++++++++++++ integration-tests/docker-compose.yml | 12 ++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 integration-tests/README.md create mode 100644 integration-tests/docker-compose.yml diff --git a/integration-tests/README.md b/integration-tests/README.md new file mode 100644 index 00000000..474a6bcc --- /dev/null +++ b/integration-tests/README.md @@ -0,0 +1,22 @@ +Integration Tests for MySQL Native +================================== + +This sub-project is intended for proving the functionality of the project against a database instance. + +See the instructions in the [main README](../README.md#developers---how-to-run-the-test-suite) on how to use this subpackage. + +## Docker image + +A docker-compose.yml is supplied for convenience when testing locally. It's preconfigured to use the same username/password that is used by default. + +To run tests on your machine, presuming docker is installed, simply run: + +``` +$ docker-compose up --detach +``` + +Once you are finished, tear down the docker instance + +``` +$ docker-compose down +``` diff --git a/integration-tests/docker-compose.yml b/integration-tests/docker-compose.yml new file mode 100644 index 00000000..4b1cb171 --- /dev/null +++ b/integration-tests/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.7' +services: + mysql: + # Don't use latest (MySQL Server 8.0) as we cannot currently support it + image: mysql:5.7 + restart: always + ports: ['3306:3306'] + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=mysqln_testdb + - MYSQL_USER=mysqln_test + - MYSQL_PASSWORD=pass123 From 6efb6e325805565379d66d127d705b4e2c25ad9f Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Wed, 26 May 2021 11:33:19 -0400 Subject: [PATCH 11/13] Fix spacing. Run test utility in integration tests. --- .github/workflows/integration-testing.yml | 12 + dub.sdl | 4 +- integration-tests/source/mysql/maintests.d | 421 ++++++++++----------- testconn/dub.sdl | 6 +- testconn/source/app.d | 13 +- 5 files changed, 232 insertions(+), 224 deletions(-) diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index 4c6b461b..fa126a93 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -129,6 +129,12 @@ jobs: run: | dub run ":integration-tests-phobos" + - name: Run test connection utility + env: + MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + run: | + dub run ":testconn" -- "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" + - name: Build The Example Project working-directory: ./examples/homePage run: dub build @@ -194,6 +200,12 @@ jobs: run: | dub run ":integration-tests-phobos" + - name: Run test connection utility + env: + MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + run: | + dub run ":testconn" -- "host=localhost;port=$MYSQL_PORT;user=testuser;pwd=passw0rd;db=testdb" + - name: Build The Example Project working-directory: ./examples/homePage run: dub build diff --git a/dub.sdl b/dub.sdl index 766b9730..b936c411 100644 --- a/dub.sdl +++ b/dub.sdl @@ -16,6 +16,6 @@ subPackage "./testconn" configuration "library" { } configuration "unittest" { - buildOptions "unittests" "debugInfo" "debugMode" - debugVersions "MYSQLN_TESTS" + buildOptions "unittests" "debugInfo" "debugMode" + debugVersions "MYSQLN_TESTS" } diff --git a/integration-tests/source/mysql/maintests.d b/integration-tests/source/mysql/maintests.d index b9c29a0d..61e5f107 100644 --- a/integration-tests/source/mysql/maintests.d +++ b/integration-tests/source/mysql/maintests.d @@ -450,90 +450,90 @@ unittest debug(MYSQLN_TESTS) unittest { - import std.variant; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `reconnect`"); - cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)"); - - enum sql = "SELECT a FROM `reconnect`"; - - // Sanity check - auto rows = cn.query(sql).array; - assert(rows[0][0] == 1); - assert(rows[1][0] == 2); - assert(rows[2][0] == 3); - - // Ensure reconnect keeps the same connection when it's supposed to - auto range = cn.query(sql); - assert(range.front[0] == 1); - cn.reconnect(); - assert(!cn.closed); // Is open? - assert(range.isValid); // Still valid? - range.popFront(); - assert(range.front[0] == 2); - - // Ensure reconnect reconnects when it's supposed to - range = cn.query(sql); - assert(range.front[0] == 1); - cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities - cn.reconnect(~cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Try manually reconnecting - range = cn.query(sql); - assert(range.front[0] == 1); - cn.connect(cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Try manually closing and connecting - range = cn.query(sql); - assert(range.front[0] == 1); - cn.close(); - assert(cn.closed); // Is closed? - assert(!range.isValid); // Was invalidated? - cn.connect(cn._clientCapabilities); - assert(!cn.closed); // Is open? - assert(!range.isValid); // Was invalidated? - cn.query(sql).array; // Connection still works? - - // Auto-reconnect upon a command - cn.close(); - assert(cn.closed); - range = cn.query(sql); - assert(!cn.closed); - assert(range.front[0] == 1); + import std.variant; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `reconnect`"); + cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)"); + + enum sql = "SELECT a FROM `reconnect`"; + + // Sanity check + auto rows = cn.query(sql).array; + assert(rows[0][0] == 1); + assert(rows[1][0] == 2); + assert(rows[2][0] == 3); + + // Ensure reconnect keeps the same connection when it's supposed to + auto range = cn.query(sql); + assert(range.front[0] == 1); + cn.reconnect(); + assert(!cn.closed); // Is open? + assert(range.isValid); // Still valid? + range.popFront(); + assert(range.front[0] == 2); + + // Ensure reconnect reconnects when it's supposed to + range = cn.query(sql); + assert(range.front[0] == 1); + cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities + cn.reconnect(~cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Try manually reconnecting + range = cn.query(sql); + assert(range.front[0] == 1); + cn.connect(cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Try manually closing and connecting + range = cn.query(sql); + assert(range.front[0] == 1); + cn.close(); + assert(cn.closed); // Is closed? + assert(!range.isValid); // Was invalidated? + cn.connect(cn._clientCapabilities); + assert(!cn.closed); // Is open? + assert(!range.isValid); // Was invalidated? + cn.query(sql).array; // Connection still works? + + // Auto-reconnect upon a command + cn.close(); + assert(cn.closed); + range = cn.query(sql); + assert(!cn.closed); + assert(range.front[0] == 1); } @("releaseAll") debug(MYSQLN_TESTS) unittest { - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `releaseAll`"); - cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`"); - auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)"); - assert(cn.isRegistered(preparedSelect)); - assert(cn.isRegistered(preparedInsert)); - - cn.releaseAll(); - assert(!cn.isRegistered(preparedSelect)); - assert(!cn.isRegistered(preparedInsert)); - cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)"); - assert(!cn.isRegistered(preparedSelect)); - assert(!cn.isRegistered(preparedInsert)); - - cn.exec(preparedInsert); - cn.query(preparedSelect).array; - assert(cn.isRegistered(preparedSelect)); - assert(cn.isRegistered(preparedInsert)); + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `releaseAll`"); + cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`"); + auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)"); + assert(cn.isRegistered(preparedSelect)); + assert(cn.isRegistered(preparedInsert)); + + cn.releaseAll(); + assert(!cn.isRegistered(preparedSelect)); + assert(!cn.isRegistered(preparedInsert)); + cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)"); + assert(!cn.isRegistered(preparedSelect)); + assert(!cn.isRegistered(preparedInsert)); + + cn.exec(preparedInsert); + cn.query(preparedSelect).array; + assert(cn.isRegistered(preparedSelect)); + assert(cn.isRegistered(preparedInsert)); } // Test register, release, isRegistered, and auto-register for prepared statements @@ -651,7 +651,7 @@ debug(MYSQLN_TESTS) unittest { import mysql.escape; - import std.conv; + import std.conv; mixin(scopedCn); cn.exec("DROP TABLE IF EXISTS `issue81`"); @@ -883,8 +883,8 @@ version(Have_vibe_core) auto cn = pool.lockConnection(); cn.exec("DROP TABLE IF EXISTS `poolRegistration`"); cn.exec("CREATE TABLE `poolRegistration` ( - `data` LONGBLOB - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + `data` LONGBLOB + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); immutable sql = "SELECT * from `poolRegistration`"; auto cn2 = pool.lockConnection(); pool.applyAuto(cn2); @@ -1090,134 +1090,133 @@ unittest debug(MYSQLN_TESTS) unittest { - import mysql.test.common; - mixin(scopedCn); - - // Setup - cn.exec("DROP TABLE IF EXISTS `setArg-typeMods`"); - cn.exec("CREATE TABLE `setArg-typeMods` ( - `i` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto insertSQL = "INSERT INTO `setArg-typeMods` VALUES (?)"; - - // Sanity check - { - int i = 111; - assert(cn.exec(insertSQL, i) == 1); - auto value = cn.queryValue("SELECT `i` FROM `setArg-typeMods`"); - assert(!value.isNull); - assert(value.get == i); - } - - // Test const(int) - { - const(int) i = 112; - assert(cn.exec(insertSQL, i) == 1); - } - - // Test immutable(int) - { - immutable(int) i = 113; - assert(cn.exec(insertSQL, i) == 1); - } - - // Note: Variant doesn't seem to support - // `shared(T)` or `shared(const(T)`. Only `shared(immutable(T))`. - - // Test shared immutable(int) - { - shared immutable(int) i = 113; - assert(cn.exec(insertSQL, i) == 1); - } + import mysql.test.common; + mixin(scopedCn); + + // Setup + cn.exec("DROP TABLE IF EXISTS `setArg-typeMods`"); + cn.exec("CREATE TABLE `setArg-typeMods` ( + `i` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto insertSQL = "INSERT INTO `setArg-typeMods` VALUES (?)"; + + // Sanity check + { + int i = 111; + assert(cn.exec(insertSQL, i) == 1); + auto value = cn.queryValue("SELECT `i` FROM `setArg-typeMods`"); + assert(!value.isNull); + assert(value.get == i); + } + + // Test const(int) + { + const(int) i = 112; + assert(cn.exec(insertSQL, i) == 1); + } + + // Test immutable(int) + { + immutable(int) i = 113; + assert(cn.exec(insertSQL, i) == 1); + } + + // Note: Variant doesn't seem to support + // `shared(T)` or `shared(const(T)`. Only `shared(immutable(T))`. + + // Test shared immutable(int) + { + shared immutable(int) i = 113; + assert(cn.exec(insertSQL, i) == 1); + } } @("setNullArg") debug(MYSQLN_TESTS) unittest { - import mysql.connection; - import mysql.test.common; - mixin(scopedCn); - - cn.exec("DROP TABLE IF EXISTS `setNullArg`"); - cn.exec("CREATE TABLE `setNullArg` ( - `val` INTEGER - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)"; - immutable selectSQL = "SELECT * FROM `setNullArg`"; - auto preparedInsert = cn.prepare(insertSQL); - assert(preparedInsert.sql == insertSQL); - Row[] rs; - - { - Nullable!int nullableInt; - nullableInt.nullify(); - preparedInsert.setArg(0, nullableInt); - assert(preparedInsert.getArg(0).type == typeid(typeof(null))); - nullableInt = 7; - preparedInsert.setArg(0, nullableInt); - assert(preparedInsert.getArg(0) == 7); - - nullableInt.nullify(); - preparedInsert.setArgs(nullableInt); - assert(preparedInsert.getArg(0).type == typeid(typeof(null))); - nullableInt = 7; - preparedInsert.setArgs(nullableInt); - assert(preparedInsert.getArg(0) == 7); - } - - preparedInsert.setArg(0, 5); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 1); - assert(rs[0][0] == 5); - - preparedInsert.setArg(0, null); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 2); - assert(rs[0][0] == 5); - assert(rs[1].isNull(0)); - assert(rs[1][0].type == typeid(typeof(null))); - - preparedInsert.setArg(0, Variant(null)); - cn.exec(preparedInsert); - rs = cn.query(selectSQL).array; - assert(rs.length == 3); - assert(rs[0][0] == 5); - assert(rs[1].isNull(0)); - assert(rs[2].isNull(0)); - assert(rs[1][0].type == typeid(typeof(null))); - assert(rs[2][0].type == typeid(typeof(null))); + import mysql.connection; + import mysql.test.common; + mixin(scopedCn); + + cn.exec("DROP TABLE IF EXISTS `setNullArg`"); + cn.exec("CREATE TABLE `setNullArg` ( + `val` INTEGER + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)"; + immutable selectSQL = "SELECT * FROM `setNullArg`"; + auto preparedInsert = cn.prepare(insertSQL); + assert(preparedInsert.sql == insertSQL); + Row[] rs; + + { + Nullable!int nullableInt; + nullableInt.nullify(); + preparedInsert.setArg(0, nullableInt); + assert(preparedInsert.getArg(0).type == typeid(typeof(null))); + nullableInt = 7; + preparedInsert.setArg(0, nullableInt); + assert(preparedInsert.getArg(0) == 7); + + nullableInt.nullify(); + preparedInsert.setArgs(nullableInt); + assert(preparedInsert.getArg(0).type == typeid(typeof(null))); + nullableInt = 7; + preparedInsert.setArgs(nullableInt); + assert(preparedInsert.getArg(0) == 7); + } + + preparedInsert.setArg(0, 5); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 1); + assert(rs[0][0] == 5); + + preparedInsert.setArg(0, null); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 2); + assert(rs[0][0] == 5); + assert(rs[1].isNull(0)); + assert(rs[1][0].type == typeid(typeof(null))); + + preparedInsert.setArg(0, Variant(null)); + cn.exec(preparedInsert); + rs = cn.query(selectSQL).array; + assert(rs.length == 3); + assert(rs[0][0] == 5); + assert(rs[1].isNull(0)); + assert(rs[2].isNull(0)); + assert(rs[1][0].type == typeid(typeof(null))); + assert(rs[2][0].type == typeid(typeof(null))); } @("lastInsertID") debug(MYSQLN_TESTS) unittest { - import mysql.connection; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `testPreparedLastInsertID`"); - cn.exec("CREATE TABLE `testPreparedLastInsertID` ( - `a` INTEGER NOT NULL AUTO_INCREMENT, - PRIMARY KEY (a) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()"); - cn.exec(stmt); - assert(stmt.lastInsertID == 1); - cn.exec(stmt); - assert(stmt.lastInsertID == 2); - cn.exec(stmt); - assert(stmt.lastInsertID == 3); + import mysql.connection; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `testPreparedLastInsertID`"); + cn.exec("CREATE TABLE `testPreparedLastInsertID` ( + `a` INTEGER NOT NULL AUTO_INCREMENT, + PRIMARY KEY (a) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()"); + cn.exec(stmt); + assert(stmt.lastInsertID == 1); + cn.exec(stmt); + assert(stmt.lastInsertID == 2); + cn.exec(stmt); + assert(stmt.lastInsertID == 3); } // Test PreparedRegistrations debug(MYSQLN_TESTS) { - // used in integration tests. PreparedRegistrations!TestPreparedRegistrationsGood1 testPreparedRegistrationsGood1; PreparedRegistrations!TestPreparedRegistrationsGood2 testPreparedRegistrationsGood2; @@ -1327,23 +1326,23 @@ debug(MYSQLN_TESTS) debug(MYSQLN_TESTS) unittest { - import mysql.test.common; - import mysql.commands; - mixin(scopedCn); - cn.exec("DROP TABLE IF EXISTS `row_getName`"); - cn.exec("CREATE TABLE `row_getName` (someValue INTEGER, another INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); - cn.exec("INSERT INTO `row_getName` VALUES (1, 2), (3, 4)"); - - enum sql = "SELECT another, someValue FROM `row_getName`"; - - auto rows = cn.query(sql).array; - assert(rows.length == 2); - assert(rows[0][0] == 2); - assert(rows[0][1] == 1); - assert(rows[0].getName(0) == "another"); - assert(rows[0].getName(1) == "someValue"); - assert(rows[1][0] == 4); - assert(rows[1][1] == 3); - assert(rows[1].getName(0) == "another"); - assert(rows[1].getName(1) == "someValue"); + import mysql.test.common; + import mysql.commands; + mixin(scopedCn); + cn.exec("DROP TABLE IF EXISTS `row_getName`"); + cn.exec("CREATE TABLE `row_getName` (someValue INTEGER, another INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + cn.exec("INSERT INTO `row_getName` VALUES (1, 2), (3, 4)"); + + enum sql = "SELECT another, someValue FROM `row_getName`"; + + auto rows = cn.query(sql).array; + assert(rows.length == 2); + assert(rows[0][0] == 2); + assert(rows[0][1] == 1); + assert(rows[0].getName(0) == "another"); + assert(rows[0].getName(1) == "someValue"); + assert(rows[1][0] == 4); + assert(rows[1][1] == 3); + assert(rows[1].getName(0) == "another"); + assert(rows[1].getName(1) == "someValue"); } diff --git a/testconn/dub.sdl b/testconn/dub.sdl index a63b9822..e2d13e40 100644 --- a/testconn/dub.sdl +++ b/testconn/dub.sdl @@ -1,7 +1,7 @@ -name "test" -description "Test harness for integration tests" +name "testconn" +description "Test connection utility" license "BSL-1.0" -copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, and Nick Sabalausky" +copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" dependency "mysql-native" version="*" diff --git a/testconn/source/app.d b/testconn/source/app.d index fd4f8d91..2718741a 100644 --- a/testconn/source/app.d +++ b/testconn/source/app.d @@ -1,5 +1,5 @@ -/++ -Usage: app [connection string] +/++ +Usage: testconn [connection string] If connection string isn't provided, the following default connection string will be used: host=localhost;port=3306;user=testuser;pwd=testpassword;db=testdb @@ -21,11 +21,8 @@ void main(string[] args) connStr = args[1]; else writeln("No connection string provided on cmdline, using default:\n", connStr); - - try testMySql(connStr); - catch( Exception e ){ - writeln("Failed: ", e.toString()); - } + + testMySql(connStr); } void testMySql(string connStr) @@ -82,7 +79,7 @@ void testMySql(string connStr) if(caps && SvrCapFlags.MULTI_RESULTS) writeln("\tMultiple result set support"); writeln(); - + MetaData md = MetaData(c); auto dbList = md.databases(); writefln("Found %s databases", dbList.length); From 6e7ccda657fdc4a2c2827426504eae43b34f81c6 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 27 May 2021 09:49:40 -0400 Subject: [PATCH 12/13] Use target type of sourceLibrary to help run unittest configuration. --- dub.sdl | 5 +++-- testconn/dub.sdl | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dub.sdl b/dub.sdl index b936c411..a07b0373 100644 --- a/dub.sdl +++ b/dub.sdl @@ -15,7 +15,8 @@ subPackage "./testconn" configuration "library" { } + configuration "unittest" { - buildOptions "unittests" "debugInfo" "debugMode" - debugVersions "MYSQLN_TESTS" + debugVersions "MYSQLN_TESTS" + targetType "sourceLibrary" } diff --git a/testconn/dub.sdl b/testconn/dub.sdl index e2d13e40..32869a84 100644 --- a/testconn/dub.sdl +++ b/testconn/dub.sdl @@ -4,4 +4,4 @@ license "BSL-1.0" copyright "Copyright (c) 2011-2021 Steve Teale, James W. Oliphant, Simen Endsjø, Sönke Ludwig, Sergey Shamov, Nick Sabalausky, and Steven Schveighoffer" authors "Steve Teale" "James W. Oliphant" "Simen Endsjø" "Sönke Ludwig" "Sergey Shamov" "Nick Sabalausky" "Steven Schveighoffer" -dependency "mysql-native" version="*" +dependency "mysql-native" path="../" From ef97eb154d126e1e95c4b03c60d59b4cd1ef3fae Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 27 May 2021 10:17:34 -0400 Subject: [PATCH 13/13] Add workflow_dispatch to workflows to allow manual triggering of builds (mostly for people who want to run actions on their local branches) --- .github/workflows/dub.yml | 1 + .github/workflows/integration-testing.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/dub.yml b/.github/workflows/dub.yml index 8891e709..befa5fb7 100644 --- a/.github/workflows/dub.yml +++ b/.github/workflows/dub.yml @@ -12,6 +12,7 @@ on: branches: - master pull_request: + workflow_dispatch: jobs: build: diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index fa126a93..3df59d36 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -12,6 +12,7 @@ on: branches: - master pull_request: + workflow_dispatch: jobs: # mysql8-tests: