├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── src ├── main │ └── scala │ │ └── de │ │ └── codecentric │ │ └── gatling │ │ └── jdbc │ │ ├── JdbcDsl.scala │ │ ├── Predef.scala │ │ ├── action │ │ ├── JdbcAction.scala │ │ ├── JdbcCreateTableAction.scala │ │ ├── JdbcDeletionAction.scala │ │ ├── JdbcDeletionActionBuilder.scala │ │ ├── JdbcDropTableAction.scala │ │ ├── JdbcInsertAction.scala │ │ ├── JdbcInsertionActionBuilder.scala │ │ ├── JdbcSelectAction.scala │ │ ├── JdbcSelectionActionBuilder.scala │ │ ├── JdbcTableCreationActionBuilder.scala │ │ └── JdbcTableDroppingActionBuilder.scala │ │ ├── builder │ │ ├── JdbcActionBuilderBase.scala │ │ ├── JdbcDeletionBuilderBase.scala │ │ ├── JdbcInsertionBuilderBase.scala │ │ ├── JdbcSelectionBuilderBase.scala │ │ ├── JdbcTableCreationBuilderBase.scala │ │ ├── JdbcTableDropBuilderBase.scala │ │ └── column │ │ │ └── ColumnHelper.scala │ │ ├── check │ │ ├── JdbcCheckActionBuilder.scala │ │ ├── JdbcCheckSupport.scala │ │ ├── JdbcManyAnyCheck.scala │ │ ├── JdbcSimpleCheck.scala │ │ └── JdbcSingleAnyCheck.scala │ │ ├── package.scala │ │ └── protocol │ │ ├── JdbcComponents.scala │ │ ├── JdbcProtocol.scala │ │ └── JdbcProtocolBuilderBase.scala └── test │ ├── resources │ └── docker │ │ ├── mysql │ │ └── startMySql.sh │ │ └── postgres │ │ └── startPostges.sh │ └── scala │ └── de │ └── codecentric │ └── gatling │ └── jdbc │ ├── action │ ├── BlockingLatchAction.scala │ ├── JdbcActionSpec.scala │ ├── JdbcCreateTableActionSpec.scala │ ├── JdbcDeletionActionSpec.scala │ ├── JdbcDropTableActionSpec.scala │ ├── JdbcInsertActionSpec.scala │ └── JdbcSelectActionSpec.scala │ ├── check │ └── JdbcSimpleCheckSpec.scala │ ├── mock │ └── MockStatsEngine.scala │ ├── protocol │ └── JdbcProtocolSpec.scala │ └── simulation │ ├── CreateTableSimulation.scala │ ├── DeleteSimulation.scala │ ├── DropTableSimulation.scala │ ├── InsertMySqlSimulation.scala │ ├── InsertPostgresSimulation.scala │ ├── InsertSimulation.scala │ ├── SelectAnyCheckSimulation.scala │ ├── SelectCheckSimulation.scala │ └── SelectSimulation.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | 19 | .idea 20 | *.iml 21 | results/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: scala 4 | scala: 5 | - 2.12.8 6 | jdk: 7 | - oraclejdk8 8 | services: 9 | - docker 10 | script: 11 | sbt ++$TRAVIS_SCALA_VERSION test && sbt ++$TRAVIS_SCALA_VERSION gatling:test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gatling JDBC Extension 2 | JDBC support for Gatling 3 | 4 | [![Build Status](https://travis-ci.org/codecentric/gatling-jdbc.svg?branch=master)](https://travis-ci.org/codecentric/gatling-jdbc) 5 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.codecentric/jdbc-gatling_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.codecentric/jdbc-gatling_2.12) 6 | 7 | 8 | The JDBC extension for Gatling was originally created to accompany a blog post that shows how to extend Gatling. 9 | Currently, five SQL operations are being supported. See below for the usage. 10 | 11 | ## :exclamation::exclamation: Even more Attention :exclamation::exclamation: 12 | 13 | The development of this project continues in [this fork](https://github.com/rbraeunlich/gatling-jdbc). 14 | Please refrain from cloning/forking this repository or creating issue. Please use the other one! 15 | 16 | ## :exclamation: Attention :exclamation: 17 | 18 | In order to avoid conflicts with `io.gatling:gatling-jdbc` the artifact name has been changed with version 2.0.1. 19 | Instead of `gatling-jdbc` it is now called `jdbc-gatling` (see issue #8). Apart from this, nothing changes. All package names etc. stayed the same. 20 | 21 | ## Usage 22 | 23 | ```scala 24 | libraryDependencies += "de.codecentric" %% "gatling-jdbc" % "version" 25 | ``` 26 | 27 | ### General 28 | 29 | In order to use the JDBC functionality, your simulation has to import `de.codecentric.gatling.jdbc.Predef._`. 30 | The JDBC configuration is done via `jdbc`, e.g.: 31 | 32 | ```scala 33 | val jdbcConfig = jdbc 34 | .url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE") 35 | .username("sa") 36 | .password("sa") 37 | .driver("org.h2.Driver") 38 | ``` 39 | Those are currently all the options that can be provided and that have to be provided. 40 | 41 | The entry point for the operations is the `jdbc()` method. The method itself takes a request name as parameter. This name will appear in the reports to represent the operation that follows. 42 | 43 | ### CREATE TABLE 44 | 45 | Creating a table is done via `jdbc().create()`. In order to ease the creation of arbitrarily many columns, the helper class ` 46 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._` was created. It is recommended to use it. The datatype of every column has to be provided as a string. 47 | Additionally, constraints are optional, but also have to be passed as strings. E.g.: 48 | ```scala 49 | scenario("createTable"). 50 | exec(jdbc("bar table") 51 | .create() 52 | .table("bar") 53 | .columns( 54 | column( 55 | name("abc"), 56 | dataType("INTEGER"), 57 | constraint("PRIMARY KEY") 58 | ), 59 | column( 60 | name("ac"), 61 | dataType("INTEGER") 62 | ) 63 | ) 64 | ) 65 | ``` 66 | 67 | ### INSERT 68 | 69 | Insertion is done via `jdbc().insert()`. For where to insert, two options are possible. Suppose you have the table from the example above. You can insert the values either by relying on the indices: 70 | ```scala 71 | exec(jdbc("insertion") 72 | .insert() 73 | .into("bar") 74 | .values("${n}, ${n}") 75 | ) 76 | ``` 77 | or by using the column names: 78 | ```scala 79 | exec(jdbc("insertion") 80 | .insert() 81 | .into("bar (abc, ac)") 82 | .values("${n}, ${n}") 83 | ) 84 | ``` 85 | 86 | ### SELECT 87 | 88 | In contrast to the previous operations, select directly requires a parameter and is called via `jdbc().select()`. The intention is to closely resemble the SQL syntax. 89 | Using `where()` is optional for SELECT. Therefore, the following two ways are both valid: 90 | ```scala 91 | exec(jdbc("selection") 92 | .select("*") 93 | .from("bar") 94 | ) 95 | ``` 96 | and 97 | ```scala 98 | exec(jdbc("selection") 99 | .select("*") 100 | .from("bar") 101 | .where("abc=4") 102 | ) 103 | ``` 104 | Of course, as parameter to `select()`, every column or combination of columns can be used, as with basic SQL. 105 | 106 | ### DELETE 107 | 108 | Deletion starts from `jdbc().delete()`. Here, the where clause is optional again. In order to delete certain values the following works: 109 | ```scala 110 | repeat(5, "n"){ 111 | exec(jdbc("deletion") 112 | .delete() 113 | .from("bar") 114 | .where("abc=${n}") 115 | ) 116 | } 117 | ``` 118 | Alternatively, in order to delete everything: 119 | ```scala 120 | exec(jdbc("deletion") 121 | .delete() 122 | .from("bar") 123 | ) 124 | ``` 125 | Please be careful, since no additional validation is being performed and you might lose some data. 126 | 127 | ### DROP TABLE 128 | 129 | The last operation that is being supported is DROP TABLE via `jdbc().drop()` The method only takes a single parameter, the table name. Please be careful again, which table you drop. 130 | Dropping the "bar" table from the first example can be done in the following way: 131 | ```scala 132 | jdbc("drop bar table").drop().table("bar") 133 | ``` 134 | 135 | ### Checks 136 | 137 | Currently, checks are only implemented for SELECT. When importing `de.codecentric.gatling.jdbc.Predef._` two types of checks are provided. 138 | The first type is the SimpleCheck. 139 | 140 | #### SimpleCheck 141 | 142 | The `simpleCheck` method (importet via `Predef`) allows for very basic checks. This method takes a function from `List[Map[String, Any]]` to `Boolean`. 143 | Each element in the list represents a row and the map the individual columns. Checks are simply appended to the selection, e.g.: 144 | ```scala 145 | exec(jdbc("selection") 146 | .select("*") 147 | .from("bar") 148 | .where("abc=4") 149 | .check(simpleCheck(result => result.head("FOO") == 4)) 150 | ) 151 | ``` 152 | A SELECT without a WHERE clause can also be validated with a `simpleCheck`. 153 | 154 | There is also another type of check that is more closely integrated with Gatling, the `CheckBuilders`. 155 | 156 | #### CheckBuilder 157 | 158 | `CheckBuilder` is actually a class provided by Gatling. Based on the Gatling classes, Gatling JDBC provides two types of them. 159 | The `JdbcAnyCheckBuilder` object contains the instances `SingleAnyResult` and `ManyAnyResults`. Both can be used in the tests quickly by calling either `jdbcSingleResponse` or `jdbcManyResponse`. 160 | 161 | The difference between the two is that the single response extracts the head out of the list of results. So you can only verify a `Map[String, Any]`. 162 | Whereas the many response, like the simple checks, returns a `List[Map[String, Any]]`. Validation is performed via the Gatling API. 163 | E.g. checking a single result can look like this: 164 | ```scala 165 | exec(jdbc("selectionSingleCheck") 166 | .select("*") 167 | .from("bar") 168 | .where("abc=4") 169 | .check(jdbcSingleResponse.is(Map[String, Any]("ABC" -> 4, "FOO" -> 4))) 170 | ) 171 | ``` 172 | This validates the data in the two columns "ABC" and "FOO". Please note explicit typing of the map. Without it the compiler will complain. 173 | 174 | A check with multiple results doesn't look very different: 175 | ```scala 176 | exec(jdbc("selectionManyCheck") 177 | .select("*") 178 | .from("bar") 179 | .where("abc=4 OR abc=5") 180 | .check(jdbcManyResponse.is(List( 181 | Map("ABC" -> 4, "FOO" -> 4), 182 | Map("ABC" -> 5, "FOO" -> 5))) 183 | ) 184 | ) 185 | ``` 186 | 187 | The advantage those CheckBuilder provide is that they can access certain functionality provided by the Gatling interfaces and classes they extend. 188 | The most important one is the possibility to save the result of a selection to the current session. 189 | By calling `saveAs` after a check you can place the result in the session under the given name. So e.g. if you want to store the result of the single check you can do it like this: 190 | ```scala 191 | exec(jdbc("selectionSingleCheckSaving") 192 | .select("*") 193 | .from("bar") 194 | .where("abc=4") 195 | .check(jdbcSingleResponse.is(Map[String, Any]("ABC" -> 4, "FOO" -> 4)) 196 | .saveAs("myResult")) 197 | ) 198 | ``` 199 | 200 | ### Final 201 | 202 | Covering all SQL operations is a lot of work and some special commands might not be required for performance tests. 203 | Please keep in mind that the state of this Gatling extension can be considered experimental. Feel free to leave comments and create pull requests. 204 | 205 | ## Publishing 206 | 207 | Firstly, you gotta have in your home `.sbt/1.0/sonatype.sbt` configured to contain your username and password for Sonatype. 208 | Secondly, open the sbt shell an perform the following steps: 209 | 1. `set pgpSecretRing := file("/home//.sbt/gpg/secring.asc")` or where ever it is 210 | 2. `release` 211 | 212 | ## Executing the intergration tests 213 | 214 | If you have to run Docker on your machine as sudo, then to execute the integration tests, sbt has to be started as sudo, too. 215 | Only `sudo sbt gatling:test` will then be allowed to start the container of the databases. -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "de.codecentric" 2 | name := "jdbc-gatling" 3 | scalaVersion := "2.12.8" 4 | libraryDependencies ++= Seq( 5 | "io.gatling.highcharts" % "gatling-charts-highcharts" % "3.0.3", 6 | "io.gatling" % "gatling-test-framework" % "3.0.3", 7 | "org.scalikejdbc" %% "scalikejdbc" % "3.3.2", 8 | "com.h2database" % "h2" % "1.4.198", 9 | "ch.qos.logback" % "logback-classic" % "1.2.3", 10 | "mysql" % "mysql-connector-java" % "8.0.15" % "test", 11 | "org.postgresql" % "postgresql" % "42.2.5" % "test", 12 | "org.scalatest" %% "scalatest" % "3.0.6" % "test", 13 | "org.testcontainers" % "postgresql" % "1.10.6" % "test", 14 | "org.testcontainers" % "mysql" % "1.10.6" % "test" 15 | ) 16 | enablePlugins(GatlingPlugin) 17 | 18 | parallelExecution in Test := false 19 | 20 | //everything below this line is related to the project release 21 | homepage := Some(url("https://github.com/codecentric/gatling-jdbc")) 22 | scmInfo := Some(ScmInfo(url("https://github.com/codecentric/gatling-jdbc"), "git@github.com:codecentric/gatling-jdbc.git")) 23 | developers := List(Developer("rbraeunlich", 24 | "Ronny Bräunlich", 25 | "ronny.braeunlich@codecentric.de", 26 | url("https://github.com/rbraeunlich"))) 27 | licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0")) 28 | 29 | // Add sonatype repository settings 30 | publishTo := Some( 31 | if (isSnapshot.value) 32 | Opts.resolver.sonatypeSnapshots 33 | else 34 | Opts.resolver.sonatypeStaging 35 | ) 36 | 37 | pomIncludeRepository := { _ => false } 38 | publishArtifact in Test := false 39 | publishMavenStyle := true 40 | releasePublishArtifactsAction := PgpKeys.publishSigned.value 41 | releaseProcess += releaseStepCommand("sonatypeReleaseAll") 42 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.5 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.gatling" % "gatling-sbt" % "3.0.0") 2 | // for releasing 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") 4 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") 5 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.4") 6 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0") 7 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.9") 8 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") 9 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/JdbcDsl.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc 2 | 3 | import io.gatling.core.session.Expression 4 | import de.codecentric.gatling.jdbc.protocol.{JdbcProtocol, JdbcProtocolBuilder, JdbcProtocolBuilderBase, JdbcProtocolBuilderConnectionPoolSettingsStep} 5 | import de.codecentric.gatling.jdbc.builder.JdbcActionBuilderBase 6 | import de.codecentric.gatling.jdbc.check.JdbcCheckSupport 7 | 8 | import scala.language.implicitConversions 9 | 10 | /** 11 | * Created by ronny on 10.05.17. 12 | */ 13 | trait JdbcDsl extends JdbcCheckSupport { 14 | 15 | val jdbc = JdbcProtocolBuilderBase 16 | 17 | def jdbc(requestName: Expression[String]) = JdbcActionBuilderBase(requestName) 18 | 19 | implicit def jdbcProtocolBuilder2JdbcProtocol(protocolBuilder: JdbcProtocolBuilder): JdbcProtocol = protocolBuilder.build 20 | 21 | implicit def jdbcProtocolBuilderConnectionPoolSettingsStep2JdbcProtocol(protocolBuilder: JdbcProtocolBuilderConnectionPoolSettingsStep): JdbcProtocol = protocolBuilder.build 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/Predef.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc 2 | 3 | /** 4 | * Created by ronny on 10.05.17. 5 | */ 6 | object Predef extends JdbcDsl { 7 | 8 | type ManyAnyResult = List[Map[String, Any]] 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.stats.{KO, OK} 4 | import io.gatling.core.action.ChainableAction 5 | import io.gatling.core.session.{Expression, Session} 6 | import io.gatling.core.stats.StatsEngine 7 | import io.gatling.core.stats.message.ResponseTimings 8 | import io.gatling.core.util.NameGen 9 | 10 | import scala.util.Try 11 | 12 | /** 13 | * Created by ronny on 12.05.17. 14 | */ 15 | trait JdbcAction extends ChainableAction with NameGen { 16 | 17 | def log(start: Long, end: Long, tried: Try[_], requestName: Expression[String], session: Session, statsEngine: StatsEngine): Unit = { 18 | val status = tried match { 19 | case scala.util.Success(_) => OK 20 | case scala.util.Failure(_) => KO 21 | } 22 | requestName.apply(session).foreach { resolvedRequestName => 23 | statsEngine.logResponse(session, resolvedRequestName, start, end, status, None, None) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcCreateTableAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import de.codecentric.gatling.jdbc.builder.column.ColumnDefinition 4 | import io.gatling.commons.util.Clock 5 | import io.gatling.commons.validation.{Failure, Success} 6 | import io.gatling.core.action.Action 7 | import io.gatling.core.session.{Expression, Session} 8 | import io.gatling.core.stats.StatsEngine 9 | import scalikejdbc._ 10 | 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import scala.concurrent.Future 13 | 14 | /** 15 | * Created by ronny on 10.05.17. 16 | */ 17 | case class JdbcCreateTableAction(requestName: Expression[String], 18 | tableName: Expression[String], 19 | columns: Seq[ColumnDefinition], 20 | clock: Clock, 21 | statsEngine: StatsEngine, 22 | next: Action) extends JdbcAction { 23 | 24 | override def name: String = genName("jdbcCreateTable") 25 | 26 | override def execute(session: Session): Unit = { 27 | val start = clock.nowMillis 28 | val columnStrings = columns.map( 29 | t => ( 30 | t.name.name.apply(session), 31 | t.dataType.dataType.apply(session), 32 | t.columnConstraint.map(_.constraint(session)).getOrElse(Success("")))) 33 | .map { 34 | case (Success(columnName), Success(dataType), Success(constraint)) => s"$columnName $dataType $constraint" 35 | case _ => throw new IllegalArgumentException 36 | }.mkString(",") 37 | 38 | val validatedTableName = tableName.apply(session) 39 | validatedTableName match { 40 | case Success(name) => 41 | val query = s"CREATE TABLE $name($columnStrings)" 42 | val future = Future { 43 | DB autoCommit { implicit session => 44 | SQL(query).execute().apply() 45 | } 46 | } 47 | future.onComplete(result => { 48 | log(start, clock.nowMillis, result, requestName, session, statsEngine) 49 | next ! session 50 | }) 51 | 52 | case Failure(error) => throw new IllegalArgumentException(error) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcDeletionAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.util.Clock 4 | import io.gatling.commons.validation.{Failure, Success, Validation} 5 | import io.gatling.core.action.Action 6 | import io.gatling.core.session.{Expression, Session} 7 | import io.gatling.core.stats.StatsEngine 8 | import scalikejdbc.{DB, SQL} 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.Future 12 | 13 | /** 14 | * Created by ronny on 11.05.17. 15 | */ 16 | case class JdbcDeletionAction(requestName: Expression[String], 17 | tableName: Expression[String], 18 | where: Option[Expression[String]], 19 | clock: Clock, 20 | statsEngine: StatsEngine, 21 | next: Action) extends JdbcAction { 22 | 23 | override def name: String = genName("jdbcDelete") 24 | 25 | override def execute(session: Session): Unit = { 26 | val start = clock.nowMillis 27 | val validatedTableName = tableName.apply(session) 28 | 29 | val wherePart = where.fold("")(_.apply(session) match { 30 | case Success(whereString) => s"WHERE $whereString" 31 | case Failure(e) => throw new IllegalArgumentException(e) 32 | }) 33 | 34 | val result: Validation[Future[Unit]] = for { 35 | tableString <- validatedTableName 36 | sqlString <- Success(s"DELETE FROM $tableString $wherePart") 37 | } yield Future { 38 | DB autoCommit { implicit session => 39 | SQL(sqlString).map(rs => rs.toMap()).execute().apply() 40 | } 41 | } 42 | 43 | result.foreach(_.onComplete(result => { 44 | log(start, clock.nowMillis, result, requestName, session, statsEngine) 45 | next ! session 46 | })) 47 | 48 | result.onFailure(e => throw new IllegalArgumentException(e)) 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcDeletionActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.core.action.Action 4 | import io.gatling.core.action.builder.ActionBuilder 5 | import io.gatling.core.session.Expression 6 | import io.gatling.core.structure.ScenarioContext 7 | 8 | /** 9 | * Created by ronny on 11.05.17. 10 | */ 11 | 12 | case class JdbcDeletionWithoutWhereActionBuilder(requestName: Expression[String], tableName: Expression[String]) extends ActionBuilder { 13 | 14 | def where(where: Expression[String]) = JdbcDeletionWithWhereActionBuilder(requestName, tableName, where) 15 | 16 | override def build(ctx: ScenarioContext, next: Action): Action = { 17 | val statsEngine = ctx.coreComponents.statsEngine 18 | val clock = ctx.coreComponents.clock 19 | JdbcDeletionAction(requestName, tableName, None, clock, statsEngine, next) 20 | } 21 | } 22 | 23 | case class JdbcDeletionWithWhereActionBuilder(requestName: Expression[String], tableName: Expression[String], where: Expression[String]) extends ActionBuilder { 24 | 25 | override def build(ctx: ScenarioContext, next: Action): Action = { 26 | val statsEngine = ctx.coreComponents.statsEngine 27 | val clock = ctx.coreComponents.clock 28 | JdbcDeletionAction(requestName, tableName, Some(where), clock, statsEngine, next) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcDropTableAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.util.Clock 4 | import io.gatling.commons.validation.{Failure, Success} 5 | import io.gatling.core.action.Action 6 | import io.gatling.core.session.{Expression, Session} 7 | import io.gatling.core.stats.StatsEngine 8 | import scalikejdbc.{DB, SQL} 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.Future 12 | 13 | /** 14 | * Created by ronny on 11.05.17. 15 | */ 16 | case class JdbcDropTableAction(requestName: Expression[String], 17 | tableName: Expression[String], 18 | clock: Clock, 19 | statsEngine: StatsEngine, 20 | next: Action) extends JdbcAction { 21 | 22 | override def name: String = genName("jdbcDropTable") 23 | 24 | override def execute(session: Session): Unit = { 25 | val start = clock.nowMillis 26 | val validatedTableName = tableName.apply(session) 27 | validatedTableName match { 28 | case Success(name) => 29 | val query = s"DROP TABLE $name" 30 | val future = Future { 31 | DB autoCommit { implicit session => 32 | SQL(query).map(rs => rs.toMap()).execute().apply() 33 | } 34 | } 35 | future.onComplete(result => { 36 | log(start, clock.nowMillis, result, requestName, session, statsEngine) 37 | next ! session 38 | }) 39 | 40 | case Failure(error) => throw new IllegalArgumentException(error) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcInsertAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.util.Clock 4 | import io.gatling.commons.validation.{Success, Validation} 5 | import io.gatling.core.action.Action 6 | import io.gatling.core.session.{Expression, Session} 7 | import io.gatling.core.stats.StatsEngine 8 | import scalikejdbc.{DB, SQL} 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.Future 12 | 13 | 14 | /** 15 | * Created by ronny on 11.05.17. 16 | */ 17 | case class JdbcInsertAction(requestName: Expression[String], 18 | tableName: Expression[String], 19 | values: Expression[String], 20 | clock: Clock, 21 | statsEngine: StatsEngine, 22 | next: Action) extends JdbcAction { 23 | 24 | override def name: String = genName("jdbcInsert") 25 | 26 | override def execute(session: Session): Unit = { 27 | val start = clock.nowMillis 28 | val validatedTableName = tableName.apply(session) 29 | val validatedValues = values.apply(session) 30 | 31 | val result: Validation[Future[Unit]] = for { 32 | tableValue <- validatedTableName 33 | valuesValue <- validatedValues 34 | sql <- Success(s"INSERT INTO $tableValue VALUES ( $valuesValue )") 35 | } yield { 36 | Future { 37 | DB autoCommit { implicit session => 38 | SQL(sql).execute().apply() 39 | } 40 | } 41 | } 42 | result.foreach(_.onComplete(result => { 43 | log(start, clock.nowMillis, result, requestName, session, statsEngine) 44 | next ! session 45 | })) 46 | 47 | result.onFailure(e => throw new IllegalArgumentException(e)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcInsertionActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.core.action.Action 4 | import io.gatling.core.action.builder.ActionBuilder 5 | import io.gatling.core.session.Expression 6 | import io.gatling.core.structure.ScenarioContext 7 | 8 | /** 9 | * Created by ronny on 11.05.17. 10 | */ 11 | case class JdbcInsertionActionBuilder(requestName: Expression[String], tableName: Expression[String], values: Expression[String]) extends ActionBuilder { 12 | 13 | override def build(ctx: ScenarioContext, next: Action): Action = { 14 | val statsEngine = ctx.coreComponents.statsEngine 15 | val clock = ctx.coreComponents.clock 16 | JdbcInsertAction(requestName, tableName, values, clock, statsEngine, next) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcSelectAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import de.codecentric.gatling.jdbc.JdbcCheck 4 | import io.gatling.commons.stats.KO 5 | import io.gatling.commons.util.Clock 6 | import io.gatling.commons.validation.Success 7 | import io.gatling.core.action.Action 8 | import io.gatling.core.check.Check 9 | import io.gatling.core.session.{Expression, Session} 10 | import io.gatling.core.stats.StatsEngine 11 | import scalikejdbc.{DB, SQL} 12 | 13 | import scala.concurrent.ExecutionContext.Implicits.global 14 | import scala.concurrent.Future 15 | import scala.util.{Failure, Try} 16 | 17 | /** 18 | * Created by ronny on 11.05.17. 19 | */ 20 | case class JdbcSelectAction(requestName: Expression[String], 21 | what: Expression[String], 22 | from: Expression[String], 23 | where: Option[Expression[String]], 24 | checks: List[JdbcCheck], 25 | clock: Clock, 26 | statsEngine: StatsEngine, 27 | next: Action) extends JdbcAction { 28 | 29 | override def name: String = genName("jdbcSelect") 30 | 31 | override def execute(session: Session): Unit = { 32 | val start = clock.nowMillis 33 | val validatedWhat = what.apply(session) 34 | val validatedFrom = from.apply(session) 35 | val validatedWhere = where.map(w => w.apply(session)) 36 | 37 | val sqlString = (validatedWhat, validatedFrom, validatedWhere) match { 38 | case (Success(whatString), Success(fromString), Some(Success(whereString))) => s"SELECT $whatString FROM $fromString WHERE $whereString" 39 | case (Success(whatString), Success(fromString), None) => s"SELECT $whatString FROM $fromString" 40 | case _ => throw new IllegalArgumentException 41 | } 42 | 43 | val future: Future[List[Map[String, Any]]] = Future { 44 | DB autoCommit { implicit session => 45 | SQL(sqlString).map(rs => rs.toMap()).toList().apply() 46 | } 47 | } 48 | future.onComplete { 49 | case scala.util.Success(value) => 50 | next ! Try(performChecks(session, start, value)).recover { 51 | case err => 52 | val logRequestName = requestName(session).toOption.getOrElse("JdbcSelectAction") 53 | statsEngine.logCrash(session, logRequestName, err.getMessage) 54 | session.markAsFailed 55 | }.get 56 | case fail: Failure[_] => 57 | log(start, clock.nowMillis, fail, requestName, session, statsEngine) 58 | next ! session 59 | } 60 | } 61 | 62 | private def performChecks(session: Session, start: Long, tried: List[Map[String, Any]]): Session = { 63 | val (modifiedSession, error) = Check.check(tried, session, checks) 64 | error match { 65 | case Some(failure) => 66 | requestName.apply(session).map { resolvedRequestName => 67 | statsEngine.logResponse(session, resolvedRequestName, start, clock.nowMillis, KO, None, None) 68 | } 69 | modifiedSession.markAsFailed 70 | case _ => 71 | log(start, clock.nowMillis, scala.util.Success(""), requestName, session, statsEngine) 72 | modifiedSession 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcSelectionActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import de.codecentric.gatling.jdbc.check.JdbcCheckActionBuilder 4 | import io.gatling.core.action.Action 5 | import io.gatling.core.action.builder.ActionBuilder 6 | import io.gatling.core.session.Expression 7 | import io.gatling.core.structure.ScenarioContext 8 | 9 | /** 10 | * Created by ronny on 11.05.17. 11 | */ 12 | case class JdbcSelectionWithoutWhereActionBuilder(requestName: Expression[String], what: Expression[String], from: Expression[String]) extends JdbcCheckActionBuilder { 13 | 14 | def where(where: Expression[String]) = JdbcSelectionWithWhereActionBuilder(requestName, what, from, where) 15 | 16 | override def build(ctx: ScenarioContext, next: Action): Action = { 17 | val statsEngine = ctx.coreComponents.statsEngine 18 | val clock = ctx.coreComponents.clock 19 | JdbcSelectAction(requestName, what, from, None, checks.toList, clock, statsEngine, next) 20 | } 21 | 22 | } 23 | 24 | case class JdbcSelectionWithWhereActionBuilder(requestName: Expression[String], what: Expression[String], from: Expression[String], where: Expression[String]) extends JdbcCheckActionBuilder { 25 | 26 | override def build(ctx: ScenarioContext, next: Action): Action = { 27 | val statsEngine = ctx.coreComponents.statsEngine 28 | val clock = ctx.coreComponents.clock 29 | JdbcSelectAction(requestName, what, from, Some(where), checks.toList, clock, statsEngine, next) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcTableCreationActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import de.codecentric.gatling.jdbc.builder.column.ColumnDefinition 4 | import io.gatling.core.action.Action 5 | import io.gatling.core.action.builder.ActionBuilder 6 | import io.gatling.core.session.Expression 7 | import io.gatling.core.structure.ScenarioContext 8 | 9 | import scala.collection.mutable.ArrayBuffer 10 | 11 | /** 12 | * Created by ronny on 10.05.17. 13 | */ 14 | case class JdbcTableCreationActionBuilder(requestName: Expression[String], name: Expression[String], columns: Seq[ColumnDefinition]) extends ActionBuilder { 15 | 16 | override def build(ctx: ScenarioContext, next: Action): Action = { 17 | val statsEngine = ctx.coreComponents.statsEngine 18 | val clock = ctx.coreComponents.clock 19 | JdbcCreateTableAction(requestName, name, columns, clock, statsEngine, next) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/action/JdbcTableDroppingActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.core.action.Action 4 | import io.gatling.core.action.builder.ActionBuilder 5 | import io.gatling.core.session.Expression 6 | import io.gatling.core.structure.ScenarioContext 7 | 8 | /** 9 | * Created by ronny on 11.05.17. 10 | */ 11 | case class JdbcTableDroppingActionBuilder(requestName: Expression[String], tableName: Expression[String]) extends ActionBuilder { 12 | 13 | override def build(ctx: ScenarioContext, next: Action): Action = { 14 | val statsEngine = ctx.coreComponents.statsEngine 15 | val clock = ctx.coreComponents.clock 16 | JdbcDropTableAction(requestName, tableName, clock, statsEngine, next) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcActionBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import io.gatling.core.config.GatlingConfiguration 4 | import io.gatling.core.session.Expression 5 | 6 | /** 7 | * Created by ronny on 10.05.17. 8 | */ 9 | case class JdbcActionBuilderBase(requestName: Expression[String]) { 10 | 11 | def create() = JdbcTableCreationBuilderBase(requestName) 12 | 13 | def insert() = JdbcInsertionBuilderBase(requestName) 14 | 15 | def select(what: Expression[String]) = JdbcSelectionBuilderBase(requestName, what) 16 | 17 | def drop() = JdbcTableDropBuilderBase(requestName) 18 | 19 | def delete() = JdbcDeletionBuilderBase(requestName) 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcDeletionBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import de.codecentric.gatling.jdbc.action.JdbcDeletionWithoutWhereActionBuilder 4 | import io.gatling.core.session.Expression 5 | 6 | /** 7 | * Created by ronny on 11.05.17. 8 | */ 9 | case class JdbcDeletionBuilderBase(requestName: Expression[String]) { 10 | 11 | def from(tableName: Expression[String]) = JdbcDeletionWithoutWhereActionBuilder(requestName, tableName) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcInsertionBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import de.codecentric.gatling.jdbc.action.JdbcInsertionActionBuilder 4 | import io.gatling.core.session.Expression 5 | 6 | /** 7 | * Created by ronny on 11.05.17. 8 | */ 9 | case class JdbcInsertionBuilderBase(requestName: Expression[String]) { 10 | 11 | /** 12 | * The name can either be the table name (INSERT INTO table_name VALUES (...)) or the table name followed by the column names (INSERT INTO table_name (column1, ...) VALUES (...)). 13 | * For the latter one has to provide the string "table_name (column1, ...)" INCLUDING the parenthesis. 14 | */ 15 | def into(name: Expression[String]) = JdbcInsertionValuesStep(requestName, name) 16 | 17 | } 18 | 19 | case class JdbcInsertionValuesStep(requestName: Expression[String], tableName: Expression[String]) { 20 | 21 | /** 22 | * Although inserting several values is possible, they should be all in a single string. 23 | */ 24 | def values(values: Expression[String]) = JdbcInsertionActionBuilder(requestName, tableName, values) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcSelectionBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import de.codecentric.gatling.jdbc.action.JdbcSelectionWithoutWhereActionBuilder 4 | import io.gatling.core.session.Expression 5 | 6 | /** 7 | * Created by ronny on 11.05.17. 8 | */ 9 | case class JdbcSelectionBuilderBase(requestName: Expression[String], what: Expression[String]) { 10 | 11 | def from(from: Expression[String]) = JdbcSelectionWithoutWhereActionBuilder(requestName, what, from) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcTableCreationBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import de.codecentric.gatling.jdbc.action.JdbcTableCreationActionBuilder 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnDefinition 5 | import io.gatling.core.session.Expression 6 | 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | /** 10 | * Created by ronny on 10.05.17. 11 | */ 12 | case class JdbcTableCreationBuilderBase(requestName: Expression[String]) { 13 | 14 | def table(name: Expression[String]) = JdbcTableCreationColumnsStep(requestName, name) 15 | 16 | } 17 | 18 | case class JdbcTableCreationColumnsStep(requestName: Expression[String], tableName: Expression[String]) { 19 | 20 | def columns(column: ColumnDefinition, moreColumns: ColumnDefinition*) = JdbcTableCreationActionBuilder(requestName, tableName, column +: moreColumns) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/JdbcTableDropBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder 2 | 3 | import de.codecentric.gatling.jdbc.action.JdbcTableDroppingActionBuilder 4 | import io.gatling.core.session.Expression 5 | 6 | /** 7 | * Created by ronny on 11.05.17. 8 | */ 9 | case class JdbcTableDropBuilderBase(requestName: Expression[String]) { 10 | 11 | def table(name: Expression[String]) = JdbcTableDroppingActionBuilder(requestName, name) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/builder/column/ColumnHelper.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.builder.column 2 | 3 | import io.gatling.core.session.Expression 4 | 5 | /** 6 | * Created by ronny on 11.05.17. 7 | */ 8 | object ColumnHelper { 9 | 10 | def column(name: ColumnName, dataType: ColumnDataType): ColumnDefinition = ColumnDefinition(name, dataType, None) 11 | 12 | def column(name: ColumnName, dataType: ColumnDataType, columnConstraint: ColumnConstraint): ColumnDefinition = ColumnDefinition(name, dataType, Some(columnConstraint)) 13 | 14 | def name(name: Expression[String]) = ColumnName(name) 15 | 16 | def dataType(dataType: Expression[String]) = ColumnDataType(dataType) 17 | 18 | def constraint(constraint: Expression[String]) = ColumnConstraint(constraint) 19 | 20 | } 21 | 22 | case class ColumnName(name: Expression[String]) 23 | 24 | case class ColumnDataType(dataType: Expression[String]) 25 | 26 | case class ColumnConstraint(constraint: Expression[String]) 27 | 28 | case class ColumnDefinition(name: ColumnName, dataType: ColumnDataType, columnConstraint: Option[ColumnConstraint]) 29 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/check/JdbcCheckActionBuilder.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import de.codecentric.gatling.jdbc.JdbcCheck 4 | import io.gatling.core.action.builder.ActionBuilder 5 | 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | /** 9 | * Created by ronny on 15.05.17. 10 | */ 11 | trait JdbcCheckActionBuilder extends ActionBuilder { 12 | 13 | protected val checks: ArrayBuffer[JdbcCheck] = ArrayBuffer.empty 14 | 15 | def check(check: JdbcCheck): ActionBuilder = { 16 | checks += check 17 | this 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/check/JdbcCheckSupport.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import de.codecentric.gatling.jdbc.JdbcCheck 4 | import de.codecentric.gatling.jdbc.Predef.ManyAnyResult 5 | import io.gatling.core.check._ 6 | 7 | import scala.annotation.implicitNotFound 8 | 9 | /** 10 | * Created by ronny on 15.05.17. 11 | */ 12 | trait JdbcCheckSupport { 13 | 14 | def simpleCheck = JdbcSimpleCheck 15 | 16 | val jdbcSingleResponse = JdbcSingleAnyCheck.SingleAnyResult 17 | implicit val jdbcSingleAnyCheckMaterializer: CheckMaterializer[JdbcSingleAnyCheck.JdbcSingleAnyCheckType, JdbcCheck, ManyAnyResult, Map[String, Any]] = JdbcSingleAnyCheck.SingleAnyCheckMaterializer 18 | 19 | val jdbcManyResponse = JdbcManyAnyCheck.ManyAnyResults 20 | implicit val jdbcManyCheckMaterializer: CheckMaterializer[JdbcManyAnyCheck.JdbcManyAnyCheckType, JdbcCheck, ManyAnyResult, ManyAnyResult] = JdbcManyAnyCheck.ManyAnyCheckMaterializer 21 | 22 | @implicitNotFound("Could not find a CheckMaterializer. This check might not be valid for JDBC.") 23 | implicit def findCheckBuilder2JdbcCheck[A, P, X](findCheckBuilder: FindCheckBuilder[A, P, X])(implicit CheckMaterializer: CheckMaterializer[A, JdbcCheck, ManyAnyResult, P]): JdbcCheck = 24 | findCheckBuilder.find.exists 25 | 26 | @implicitNotFound("Could not find a CheckMaterializer. This check might not be valid for JDBC.") 27 | implicit def checkBuilder2JdbcCheck[A, P, X](checkBuilder: CheckBuilder[A, P, X])(implicit materializer: CheckMaterializer[A, JdbcCheck, ManyAnyResult, P]): JdbcCheck = 28 | checkBuilder.build(materializer) 29 | 30 | } -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/check/JdbcManyAnyCheck.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import de.codecentric.gatling.jdbc.JdbcCheck 4 | import de.codecentric.gatling.jdbc.Predef.ManyAnyResult 5 | import io.gatling.commons.validation.{Validation, _} 6 | import io.gatling.core.check.extractor.{Extractor, FindAllArity} 7 | import io.gatling.core.check.{CheckMaterializer, DefaultFindCheckBuilder, Preparer, Specializer} 8 | import io.gatling.core.session.{Expression, _} 9 | 10 | object JdbcManyAnyCheck { 11 | 12 | trait JdbcManyAnyCheckType 13 | 14 | val ManyAnyPreparer: Preparer[ManyAnyResult, ManyAnyResult] = something => something.success 15 | 16 | val ManyAnyCheckMaterializer: CheckMaterializer[JdbcManyAnyCheckType, JdbcCheck, ManyAnyResult, ManyAnyResult] = new CheckMaterializer[JdbcManyAnyCheckType, JdbcCheck, ManyAnyResult, ManyAnyResult] { 17 | override protected def preparer: Preparer[ManyAnyResult, ManyAnyResult] = ManyAnyPreparer 18 | 19 | override protected def specializer: Specializer[JdbcCheck, ManyAnyResult] = identity 20 | } 21 | 22 | val ManyAnyExtractor: Expression[Extractor[ManyAnyResult, ManyAnyResult] with FindAllArity] = 23 | new Extractor[ManyAnyResult, ManyAnyResult] with FindAllArity { 24 | override def name: String = "manyAny" 25 | 26 | override def apply(prepared: ManyAnyResult): Validation[Option[ManyAnyResult]] = Some(prepared).success 27 | }.expressionSuccess 28 | 29 | val ManyAnyResults = new DefaultFindCheckBuilder[JdbcManyAnyCheckType, ManyAnyResult, ManyAnyResult]( 30 | ManyAnyExtractor, 31 | displayActualValue = true 32 | ) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/check/JdbcSimpleCheck.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import java.sql.ResultSet 4 | import java.util 5 | 6 | import de.codecentric.gatling.jdbc.JdbcCheck 7 | import io.gatling.commons.validation.{Failure, Validation} 8 | import io.gatling.core.check.{Check, CheckResult} 9 | import io.gatling.core.session.Session 10 | import scalikejdbc.WrappedResultSet 11 | 12 | import scala.collection.mutable 13 | 14 | /** 15 | * Created by ronny on 15.05.17. 16 | */ 17 | case class JdbcSimpleCheck(func: List[Map[String, Any]] => Boolean) extends JdbcCheck { 18 | override def check(response: List[Map[String, Any]], session: Session)(implicit preparedCache: util.Map[Any, Any]): Validation[CheckResult] = { 19 | if (func(response)) { 20 | CheckResult.NoopCheckResultSuccess 21 | } else { 22 | Failure("JDBC check failed") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/check/JdbcSingleAnyCheck.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import de.codecentric.gatling.jdbc.JdbcCheck 4 | import de.codecentric.gatling.jdbc.Predef.ManyAnyResult 5 | import io.gatling.commons.validation.{Validation, _} 6 | import io.gatling.core.check.extractor.{Extractor, SingleArity} 7 | import io.gatling.core.check.{CheckMaterializer, DefaultFindCheckBuilder, Preparer, Specializer} 8 | import io.gatling.core.session._ 9 | 10 | object JdbcSingleAnyCheck { 11 | 12 | trait JdbcSingleAnyCheckType 13 | 14 | val SingleAnyPreparer: Preparer[ManyAnyResult, Map[String, Any]] = something => something.head.success 15 | 16 | val SingleAnyCheckMaterializer: CheckMaterializer[JdbcSingleAnyCheckType, JdbcCheck, ManyAnyResult, Map[String, Any]] = new CheckMaterializer[JdbcSingleAnyCheckType, JdbcCheck, ManyAnyResult, Map[String, Any]] { 17 | override protected def preparer: Preparer[ManyAnyResult, Map[String, Any]] = SingleAnyPreparer 18 | 19 | override protected def specializer: Specializer[JdbcCheck, ManyAnyResult] = identity 20 | } 21 | 22 | val SingleAnyExtractor: Expression[Extractor[Map[String, Any], Map[String, Any]] with SingleArity] = 23 | new Extractor[Map[String, Any], Map[String, Any]] with SingleArity { 24 | override def name: String = "singleAny" 25 | 26 | override def apply(prepared: Map[String, Any]): Validation[Option[Map[String, Any]]] = Some(prepared).success 27 | }.expressionSuccess 28 | 29 | val SingleAnyResult = new DefaultFindCheckBuilder[JdbcSingleAnyCheckType, Map[String, Any], Map[String, Any]]( 30 | SingleAnyExtractor, 31 | displayActualValue = true 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/package.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling 2 | 3 | import io.gatling.core.check.Check 4 | 5 | /** 6 | * Created by ronny on 15.05.17. 7 | */ 8 | package object jdbc { 9 | 10 | type JdbcCheck = Check[List[Map[String, Any]]] 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/protocol/JdbcComponents.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.protocol 2 | 3 | import io.gatling.core.protocol.ProtocolComponents 4 | import io.gatling.core.session.Session 5 | 6 | /** 7 | * Created by ronny on 10.05.17. 8 | */ 9 | case class JdbcComponents(protocol: JdbcProtocol) extends ProtocolComponents { 10 | 11 | override def onStart: Session => Session = ProtocolComponents.NoopOnStart 12 | 13 | override def onExit: Session => Unit = ProtocolComponents.NoopOnExit 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/protocol/JdbcProtocol.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.protocol 2 | 3 | import io.gatling.core.{CoreComponents, protocol} 4 | import io.gatling.core.config.GatlingConfiguration 5 | import io.gatling.core.protocol.{Protocol, ProtocolKey} 6 | import scalikejdbc.ConnectionPool.CPSettings 7 | import scalikejdbc._ 8 | 9 | /** 10 | * Created by ronny on 10.05.17. 11 | */ 12 | class JdbcProtocol(url: String, username: String, pwd: String, driver: String, connectionPoolSettings: Option[CPSettings]) extends Protocol { 13 | 14 | Class.forName(driver) 15 | 16 | GlobalSettings.loggingSQLAndTime = LoggingSQLAndTimeSettings(singleLineMode = true) 17 | 18 | if (connectionPoolSettings.isDefined) { 19 | ConnectionPool.singleton(url, username, pwd, connectionPoolSettings.get) 20 | } else { 21 | ConnectionPool.singleton(url, username, pwd) 22 | } 23 | } 24 | 25 | object JdbcProtocol { 26 | 27 | val jdbcProtocolKey: ProtocolKey[JdbcProtocol, JdbcComponents] = new ProtocolKey[JdbcProtocol, JdbcComponents] { 28 | 29 | override def protocolClass: Class[protocol.Protocol] = classOf[JdbcProtocol].asInstanceOf[Class[io.gatling.core.protocol.Protocol]] 30 | 31 | override def defaultProtocolValue(configuration: GatlingConfiguration): JdbcProtocol = 32 | throw new IllegalStateException("Can't provide a default value for JdbcProtocol") 33 | 34 | override def newComponents(coreComponents: CoreComponents): JdbcProtocol => JdbcComponents = { 35 | protocol => JdbcComponents(protocol) 36 | } 37 | 38 | } 39 | 40 | def apply(url: String, username: String, pwd: String, driver: String, connectionPoolSettings: Option[CPSettings]): JdbcProtocol = 41 | new JdbcProtocol(url, username, pwd, driver, connectionPoolSettings) 42 | } -------------------------------------------------------------------------------- /src/main/scala/de/codecentric/gatling/jdbc/protocol/JdbcProtocolBuilderBase.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.protocol 2 | 3 | import scalikejdbc.ConnectionPool.CPSettings 4 | 5 | /** 6 | * Created by ronny on 10.05.17. 7 | */ 8 | case object JdbcProtocolBuilderBase { 9 | 10 | def url(url: String) = JdbcProtocolBuilderUsernameStep(url) 11 | 12 | } 13 | 14 | case class JdbcProtocolBuilderUsernameStep(url: String) { 15 | 16 | def username(name: String) = JdbcProtocolBuilderPasswordStep(url, name) 17 | 18 | } 19 | 20 | case class JdbcProtocolBuilderPasswordStep(url: String, username: String) { 21 | 22 | def password(pwd: String) = JdbcProtocolBuilderDriverStep(url, username, pwd) 23 | 24 | } 25 | 26 | case class JdbcProtocolBuilderDriverStep(url: String, username: String, password: String) { 27 | 28 | /** 29 | * The fully qualified name of the driver as usually loaded by Class.forName(...) 30 | */ 31 | def driver(driver: String) = JdbcProtocolBuilderConnectionPoolSettingsStep(url, username, password, driver) 32 | 33 | } 34 | 35 | case class JdbcProtocolBuilderConnectionPoolSettingsStep(url: String, username: String, password: String, driver: String) { 36 | 37 | def build = JdbcProtocol(url, username, password, driver, None) 38 | 39 | def connectionPoolSettings(connectionPoolSettings: CPSettings) = JdbcProtocolBuilder(url, username, password, driver, connectionPoolSettings) 40 | 41 | } 42 | 43 | case class JdbcProtocolBuilder(url: String, username: String, pwd: String, driver: String, connectionPoolSettings: CPSettings) { 44 | 45 | def build = JdbcProtocol(url, username, pwd, driver, Some(connectionPoolSettings)) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/resources/docker/mysql/startMySql.sh: -------------------------------------------------------------------------------- 1 | docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7 -------------------------------------------------------------------------------- /src/test/resources/docker/postgres/startPostges.sh: -------------------------------------------------------------------------------- 1 | docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres:9.6 -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/BlockingLatchAction.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import java.util.concurrent.CountDownLatch 4 | 5 | import io.gatling.core.action.Action 6 | import io.gatling.core.session.Session 7 | 8 | /** 9 | * This action has a latch with a count 1 on which one can wait in order 10 | * to test async execution. When execute is being called the latch is opened. 11 | */ 12 | class BlockingLatchAction extends Action{ 13 | 14 | val latch: CountDownLatch = new CountDownLatch(1) 15 | 16 | override def name: String = "latch action" 17 | 18 | override def execute(session: Session): Unit = latch.countDown() 19 | } 20 | 21 | object BlockingLatchAction{ 22 | def apply(): BlockingLatchAction = new BlockingLatchAction() 23 | } -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import java.time.Instant 4 | import java.util.concurrent.TimeUnit 5 | 6 | import de.codecentric.gatling.jdbc.mock.MockStatsEngine 7 | import io.gatling.core.action.Action 8 | import io.gatling.core.session.Session 9 | import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, FlatSpec, Matchers} 10 | import scalikejdbc.{ConnectionPool, GlobalSettings, LoggingSQLAndTimeSettings} 11 | 12 | /** 13 | * Created by ronny on 12.05.17. 14 | */ 15 | trait JdbcActionSpec extends FlatSpec with BeforeAndAfter with BeforeAndAfterAll with Matchers { 16 | 17 | val session = Session("scenario", 0, Instant.now().getEpochSecond) 18 | val next = new Action { 19 | override def name: String = "mockAction" 20 | 21 | override def execute(session: Session): Unit = {} 22 | } 23 | val statsEngine = new MockStatsEngine 24 | 25 | override def beforeAll(): Unit = { 26 | Class.forName("org.h2.Driver") 27 | GlobalSettings.loggingSQLAndTime = LoggingSQLAndTimeSettings(singleLineMode = true) 28 | ConnectionPool.singleton("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE", "sa", "sa") 29 | } 30 | 31 | before { 32 | statsEngine.dataWriterMsg = List() 33 | } 34 | 35 | override def afterAll(): Unit = { 36 | ConnectionPool.closeAll() 37 | } 38 | 39 | 40 | def waitForLatch(latchAction: BlockingLatchAction): Boolean = { 41 | latchAction.latch.await(2L, TimeUnit.SECONDS) 42 | } 43 | } 44 | 45 | case class NextAction(session: Session, var called: Boolean = false) extends BlockingLatchAction { 46 | override def name: String = "next Action" 47 | 48 | override def execute(s: Session): Unit = { 49 | if(s == session) called = true 50 | super.execute(s) 51 | } 52 | 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcCreateTableActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 4 | import io.gatling.commons.stats.{KO, OK} 5 | import io.gatling.commons.util.DefaultClock 6 | import io.gatling.core.Predef._ 7 | import io.gatling.core.action.Action 8 | import io.gatling.core.session.Session 9 | import io.gatling.core.stats.writer.ResponseMessage 10 | import org.scalatest.Matchers._ 11 | import org.scalatest._ 12 | import scalikejdbc._ 13 | 14 | /** 15 | * Created by ronny on 12.05.17. 16 | */ 17 | class JdbcCreateTableActionSpec extends JdbcActionSpec { 18 | 19 | private val clock = new DefaultClock 20 | 21 | "JdbcCreateTableAction" should "use the request name in the log message" in { 22 | val requestName = "name" 23 | val latchAction = BlockingLatchAction() 24 | val action = JdbcCreateTableAction(requestName, "table", Seq(column(name("foo"), dataType("INTEGER"))), clock, statsEngine, latchAction) 25 | 26 | action.execute(session) 27 | 28 | waitForLatch(latchAction) 29 | statsEngine.dataWriterMsg should have length 1 30 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].name should equal(requestName) 31 | } 32 | 33 | it should "create the table with given name and columns" in { 34 | val action = JdbcCreateTableAction("request", "new_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, next) 35 | 36 | action.execute(session) 37 | 38 | val result = DB readOnly { implicit session => 39 | sql"""SELECT * FROM information_schema.tables WHERE TABLE_NAME = 'NEW_TABLE' """.map(rs => rs.toMap()).single().apply() 40 | } 41 | result should not be empty 42 | } 43 | 44 | it should "log an OK message when successfully creating the table" in { 45 | val latchAction = BlockingLatchAction() 46 | val action = JdbcCreateTableAction("request", "ok_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, latchAction) 47 | 48 | action.execute(session) 49 | 50 | waitForLatch(latchAction) 51 | statsEngine.dataWriterMsg should have length 1 52 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 53 | } 54 | 55 | it should "log a KO message if an error occurs" in { 56 | val latchAction = BlockingLatchAction() 57 | val latchAction2 = BlockingLatchAction() 58 | val action = JdbcCreateTableAction("request", "ko_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, latchAction) 59 | val action2 = JdbcCreateTableAction("request", "ko_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, latchAction2) 60 | 61 | action.execute(session) 62 | waitForLatch(latchAction) 63 | action2.execute(session) 64 | 65 | waitForLatch(latchAction2) 66 | statsEngine.dataWriterMsg should have length 2 67 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 68 | } 69 | 70 | it should "throw an IAE when the column name cannot be validated" in { 71 | val action = JdbcCreateTableAction("request", "exc_table", Seq(column(name("${foo}"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, next) 72 | 73 | an[IllegalArgumentException] should be thrownBy action.execute(session) 74 | } 75 | 76 | it should "throw an IAE when the column data type cannot be validated" in { 77 | val action = JdbcCreateTableAction("request", "exc_table", Seq(column(name("foo"), dataType("${INTEGER}"), constraint("PRIMARY KEY"))), clock, statsEngine, next) 78 | 79 | an[IllegalArgumentException] should be thrownBy action.execute(session) 80 | } 81 | 82 | it should "throw an IAE when the column constraint cannot be validated" in { 83 | val action = JdbcCreateTableAction("request", "exc_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("${constraint}"))), clock, statsEngine, next) 84 | 85 | an[IllegalArgumentException] should be thrownBy action.execute(session) 86 | } 87 | 88 | it should "throw an IAE when the table name cannot be validated" in { 89 | val action = JdbcCreateTableAction("request", "${exc_table}", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, next) 90 | 91 | an[IllegalArgumentException] should be thrownBy action.execute(session) 92 | } 93 | 94 | it should "pass the session to the next action" in { 95 | val nextAction = NextAction(session) 96 | val action = JdbcCreateTableAction("request", "next_table", Seq(column(name("foo"), dataType("INTEGER"), constraint("PRIMARY KEY"))), clock, statsEngine, nextAction) 97 | 98 | action.execute(session) 99 | 100 | waitForLatch(nextAction) 101 | nextAction.called should be(true) 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcDeletionActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.stats.{KO, OK} 4 | import io.gatling.commons.util.DefaultClock 5 | import io.gatling.core.stats.writer.ResponseMessage 6 | import org.scalatest.Matchers.equal 7 | import org.scalatest.Matchers._ 8 | import io.gatling.core.Predef._ 9 | import io.gatling.core.action.Action 10 | import io.gatling.core.session.Session 11 | import scalikejdbc.DB 12 | import scalikejdbc._ 13 | 14 | /** 15 | * Created by ronny on 12.05.17. 16 | */ 17 | class JdbcDeletionActionSpec extends JdbcActionSpec { 18 | 19 | private val clock = new DefaultClock 20 | 21 | "JdbcDeletionAction" should "use the request name in the log message" in { 22 | val requestName = "name" 23 | val latchAction = BlockingLatchAction() 24 | val action = JdbcDeletionAction(requestName, "table", None, clock, statsEngine, latchAction) 25 | 26 | action.execute(session) 27 | 28 | waitForLatch(latchAction) 29 | statsEngine.dataWriterMsg should have length 1 30 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].name should equal(requestName) 31 | } 32 | 33 | it should "delete the data specied by the where clause" in { 34 | DB autoCommit { implicit session => 35 | sql"""CREATE TABLE foo(bar INTEGER ); INSERT INTO foo VALUES (1);INSERT INTO foo VALUES (2)""".execute().apply() 36 | } 37 | val latchAction = BlockingLatchAction() 38 | val action = JdbcDeletionAction("request", "foo", Some("bar = 2"), clock, statsEngine, latchAction) 39 | 40 | action.execute(session) 41 | 42 | waitForLatch(latchAction) 43 | val result = DB readOnly { implicit session => 44 | sql"""SELECT COUNT(*) FROM foo""".map(rs => rs.int(1)).single().apply() 45 | } 46 | result should contain(1) 47 | } 48 | 49 | it should "delete all data when no where specified" in { 50 | DB autoCommit { implicit session => 51 | sql"""CREATE TABLE bar(foo INTEGER ); INSERT INTO bar VALUES (1);INSERT INTO bar VALUES (2)""".execute().apply() 52 | } 53 | val latchAction = BlockingLatchAction() 54 | val action = JdbcDeletionAction("request", "bar", None, clock, statsEngine, latchAction) 55 | 56 | action.execute(session) 57 | 58 | waitForLatch(latchAction) 59 | val result = DB readOnly { implicit session => 60 | sql"""SELECT COUNT(*) FROM bar""".map(rs => rs.int(1)).single().apply() 61 | } 62 | result should contain(0) 63 | } 64 | 65 | it should "log an OK value when being successful" in { 66 | DB autoCommit { implicit session => 67 | sql"""CREATE TABLE table_1(bar INTEGER ); INSERT INTO table_1 VALUES (1);""".execute().apply() 68 | } 69 | val latchAction = BlockingLatchAction() 70 | val action = JdbcDeletionAction("request", "table_1", Some("bar = 1"), clock, statsEngine, latchAction) 71 | 72 | action.execute(session) 73 | 74 | waitForLatch(latchAction) 75 | statsEngine.dataWriterMsg should have length 1 76 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 77 | } 78 | 79 | it should "log an KO value when being unsuccessful" in { 80 | val latchAction = BlockingLatchAction() 81 | val action = JdbcDeletionAction("request", "non_existing", Some("bar = 1"), clock, statsEngine, latchAction) 82 | 83 | action.execute(session) 84 | 85 | waitForLatch(latchAction) 86 | statsEngine.dataWriterMsg should have length 1 87 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 88 | } 89 | 90 | it should "throw an IAE when the table name cannot be resolved" in { 91 | val action = JdbcDeletionAction("request", "${what}", Some("bar = 1"), clock, statsEngine, next) 92 | 93 | an[IllegalArgumentException] should be thrownBy action.execute(session) 94 | } 95 | 96 | it should "throw an IAE when the where clause cannot be resolved" in { 97 | val action = JdbcDeletionAction("request", "what", Some("${bar} = 1"), clock, statsEngine, next) 98 | 99 | an[IllegalArgumentException] should be thrownBy action.execute(session) 100 | } 101 | 102 | it should "pass the session to the next action" in { 103 | val nextAction = NextAction(session) 104 | DB autoCommit { implicit session => 105 | sql"""CREATE TABLE what(nothing INTEGER )""".execute().apply() 106 | } 107 | val action = JdbcDeletionAction("request", "what", None, clock, statsEngine, nextAction) 108 | 109 | action.execute(session) 110 | 111 | waitForLatch(nextAction) 112 | nextAction.called should be(true) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcDropTableActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import io.gatling.commons.stats.{KO, OK} 6 | import io.gatling.commons.util.DefaultClock 7 | import io.gatling.core.Predef._ 8 | import io.gatling.core.stats.writer.ResponseMessage 9 | import scalikejdbc._ 10 | /** 11 | * Created by ronny on 15.05.17. 12 | */ 13 | class JdbcDropTableActionSpec extends JdbcActionSpec { 14 | 15 | private val clock = new DefaultClock 16 | 17 | "JdbcDropTableAction" should "use the request name in the log message" in { 18 | val requestName = "request" 19 | val latchAction = BlockingLatchAction() 20 | val action = JdbcDropTableAction(requestName, "table", clock, statsEngine, latchAction) 21 | 22 | action.execute(session) 23 | 24 | waitForLatch(latchAction) 25 | statsEngine.dataWriterMsg should have length 1 26 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].name should equal(requestName) 27 | } 28 | 29 | it should "drop the table with the specified name" in { 30 | DB autoCommit{ implicit session => 31 | sql"""CREATE TABLE delete_me(id INTEGER PRIMARY KEY )""".execute().apply() 32 | } 33 | val latchAction = BlockingLatchAction() 34 | val action = JdbcDropTableAction("deleteRequest", "DELETE_ME", clock, statsEngine, latchAction) 35 | 36 | action.execute(session) 37 | 38 | waitForLatch(latchAction) 39 | val result = DB readOnly { implicit session => 40 | sql"""SELECT * FROM information_schema.tables WHERE TABLE_NAME = 'DELETE_ME' """.map(rs => rs.toMap()).single().apply() 41 | } 42 | result should be(empty) 43 | } 44 | 45 | it should "throw an IAE if the expression cannot be resolved" in { 46 | val action = JdbcDropTableAction("deleteRequest", "${table}", clock, statsEngine, next) 47 | 48 | an[IllegalArgumentException] shouldBe thrownBy(action.execute(session)) 49 | } 50 | 51 | it should "log an OK value when being successful" in { 52 | DB autoCommit{ implicit session => 53 | sql"""CREATE TABLE delete_other(id INTEGER PRIMARY KEY )""".execute().apply() 54 | } 55 | val latchAction = BlockingLatchAction() 56 | val action = JdbcDropTableAction("deleteRequest", "DELETE_OTHER", clock, statsEngine, latchAction) 57 | 58 | action.execute(session) 59 | 60 | waitForLatch(latchAction) 61 | statsEngine.dataWriterMsg should have length 1 62 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 63 | } 64 | 65 | it should "log a KO value when being unsuccessful" in { 66 | val latchAction = BlockingLatchAction() 67 | val action = JdbcDropTableAction("deleteRequest", "DELETE_YOU", clock, statsEngine, latchAction) 68 | 69 | action.execute(session) 70 | 71 | waitForLatch(latchAction) 72 | statsEngine.dataWriterMsg should have length 1 73 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 74 | } 75 | 76 | it should "pass the session to the next action" in { 77 | val nextAction = NextAction(session) 78 | val action = JdbcDropTableAction("deleteRequest", "DELETE_SOMETHING", clock, statsEngine, nextAction) 79 | 80 | action.execute(session) 81 | 82 | waitForLatch(nextAction) 83 | nextAction.called should be(true) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcInsertActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.stats.{KO, OK} 4 | import io.gatling.commons.util.DefaultClock 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.stats.writer.ResponseMessage 7 | import scalikejdbc._ 8 | 9 | /** 10 | * Created by ronny on 15.05.17. 11 | */ 12 | class JdbcInsertActionSpec extends JdbcActionSpec { 13 | 14 | private val clock = new DefaultClock 15 | 16 | "JdbcInsertAction" should "use the request name in the log message" in { 17 | val requestName = "request" 18 | val latchAction = BlockingLatchAction() 19 | val action = JdbcInsertAction(requestName, "table", "", clock, statsEngine, latchAction) 20 | 21 | action.execute(session) 22 | 23 | waitForLatch(latchAction) 24 | statsEngine.dataWriterMsg should have length 1 25 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].name should equal(requestName) 26 | } 27 | 28 | it should "insert the specified values" in { 29 | DB autoCommit { implicit session => 30 | sql"""CREATE TABLE insert_me(id INTEGER PRIMARY KEY )""".execute().apply() 31 | } 32 | val latchAction = BlockingLatchAction() 33 | val action = JdbcInsertAction("insert", "INSERT_ME", "42", clock, statsEngine, latchAction) 34 | 35 | action.execute(session) 36 | 37 | waitForLatch(latchAction) 38 | val result = DB readOnly { implicit session => 39 | sql"""SELECT * FROM INSERT_ME WHERE id = 42 """.map(rs => rs.toMap()).single().apply() 40 | } 41 | result should not be empty 42 | } 43 | 44 | it should "log an OK value when being successful" in { 45 | DB autoCommit { implicit session => 46 | sql"""CREATE TABLE insert_again(id INTEGER PRIMARY KEY )""".execute().apply() 47 | } 48 | val latchAction = BlockingLatchAction() 49 | val action = JdbcInsertAction("insert", "INSERT_AGAIN", "42", clock, statsEngine, latchAction) 50 | 51 | action.execute(session) 52 | 53 | waitForLatch(latchAction) 54 | statsEngine.dataWriterMsg should have length 1 55 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 56 | } 57 | 58 | it should "log a KO value when being unsuccessful" in { 59 | val latchAction = BlockingLatchAction() 60 | val action = JdbcInsertAction("insert", "INSERT_NOBODY", "42", clock, statsEngine, latchAction) 61 | 62 | action.execute(session) 63 | 64 | waitForLatch(latchAction) 65 | statsEngine.dataWriterMsg should have length 1 66 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 67 | } 68 | 69 | it should "throw an IAE when it cannot evaluate the table expression" in { 70 | val action = JdbcInsertAction("insert", "${table}", "42", clock, statsEngine, next) 71 | 72 | an[IllegalArgumentException] should be thrownBy action.execute(session) 73 | } 74 | 75 | it should "throw an IAE when it cannot evaluate the value expression" in { 76 | val action = JdbcInsertAction("insert", "table", "${value}", clock, statsEngine, next) 77 | 78 | an[IllegalArgumentException] should be thrownBy action.execute(session) 79 | } 80 | 81 | it should "pass the session to the next action" in { 82 | DB autoCommit { implicit session => 83 | sql"""CREATE TABLE insert_next(id INTEGER PRIMARY KEY )""".execute().apply() 84 | } 85 | val nextAction = NextAction(session) 86 | val action = JdbcInsertAction("insert", "INSERT_NEXT", "42", clock, statsEngine, nextAction) 87 | 88 | action.execute(session) 89 | 90 | waitForLatch(nextAction) 91 | nextAction.called should be(true) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/action/JdbcSelectActionSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.action 2 | 3 | import io.gatling.commons.stats.{KO, OK} 4 | import io.gatling.core.Predef._ 5 | import io.gatling.core.stats.writer.ResponseMessage 6 | import scalikejdbc._ 7 | import de.codecentric.gatling.jdbc.Predef._ 8 | import io.gatling.commons.util.DefaultClock 9 | 10 | /** 11 | * Created by ronny on 15.05.17. 12 | */ 13 | class JdbcSelectActionSpec extends JdbcActionSpec { 14 | 15 | private val clock = new DefaultClock 16 | 17 | "JdbcSelectAction" should "use the request name in the log message" in { 18 | val requestName = "simulation" 19 | val latchAction = BlockingLatchAction() 20 | val action = JdbcSelectAction(requestName, "*", "table", None, List.empty, clock, statsEngine, next) 21 | 22 | action.execute(session) 23 | 24 | waitForLatch(latchAction) 25 | statsEngine.dataWriterMsg should have length 1 26 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].name should equal(requestName) 27 | } 28 | 29 | it should "select all values without where clause" in { 30 | DB autoCommit { implicit session => 31 | sql"""CREATE TABLE selection(id INTEGER PRIMARY KEY ); INSERT INTO SELECTION VALUES (1);INSERT INTO SELECTION VALUES (2)""".execute().apply() 32 | } 33 | val latchAction = BlockingLatchAction() 34 | val action = JdbcSelectAction("request", "*", "SELECTION", None, List(simpleCheck(list => list.length == 2)), clock, statsEngine, latchAction) 35 | 36 | action.execute(session) 37 | 38 | waitForLatch(latchAction) 39 | statsEngine.dataWriterMsg should have length 1 40 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 41 | } 42 | 43 | it should "select values specified by where clause" in { 44 | DB autoCommit { implicit session => 45 | sql"""CREATE TABLE limited(id INTEGER PRIMARY KEY ); INSERT INTO LIMITED VALUES (1);INSERT INTO LIMITED VALUES (2)""".execute().apply() 46 | } 47 | val latchAction = BlockingLatchAction() 48 | val action = JdbcSelectAction("request", "*", "LIMITED", Some("id=2"), List(simpleCheck(list => list.length == 1)), clock, statsEngine, latchAction) 49 | 50 | action.execute(session) 51 | 52 | waitForLatch(latchAction) 53 | statsEngine.dataWriterMsg should have length 1 54 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 55 | } 56 | 57 | it should "log an OK value after successful selection" in { 58 | DB autoCommit { implicit session => 59 | sql"""CREATE TABLE success(id INTEGER PRIMARY KEY )""".execute().apply() 60 | } 61 | val latchAction = BlockingLatchAction() 62 | val action = JdbcSelectAction("request", "*", "SUCCESS", None, List.empty, clock, statsEngine, latchAction) 63 | 64 | action.execute(session) 65 | 66 | waitForLatch(latchAction) 67 | statsEngine.dataWriterMsg should have length 1 68 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 69 | } 70 | 71 | it should "log an KO value after unsuccessful selection" in { 72 | val latchAction = BlockingLatchAction() 73 | val action = JdbcSelectAction("request", "*", "failure", None, List.empty, clock, statsEngine, latchAction) 74 | 75 | action.execute(session) 76 | 77 | waitForLatch(latchAction) 78 | statsEngine.dataWriterMsg should have length 1 79 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 80 | } 81 | 82 | it should "log a KO value if a check fails" in { 83 | DB autoCommit { implicit session => 84 | sql"""CREATE TABLE checkTable(id INTEGER PRIMARY KEY )""".execute().apply() 85 | } 86 | val latchAction = BlockingLatchAction() 87 | val action = JdbcSelectAction("request", "*", "CHECKTABLE", None, List(simpleCheck(_ => false)), clock, statsEngine, latchAction) 88 | 89 | action.execute(session) 90 | 91 | waitForLatch(latchAction) 92 | statsEngine.dataWriterMsg should have length 1 93 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(KO) 94 | } 95 | 96 | it should "log a OK value if a check is successful" in { 97 | DB autoCommit { implicit session => 98 | sql"""CREATE TABLE check_again(id INTEGER PRIMARY KEY )""".execute().apply() 99 | } 100 | val latchAction = BlockingLatchAction() 101 | val action = JdbcSelectAction("request", "*", "CHECK_AGAIN", None, List(simpleCheck(_ => true)), clock, statsEngine, latchAction) 102 | 103 | action.execute(session) 104 | 105 | waitForLatch(latchAction) 106 | statsEngine.dataWriterMsg should have length 1 107 | statsEngine.dataWriterMsg.head(session).toOption.get.asInstanceOf[ResponseMessage].status should equal(OK) 108 | } 109 | 110 | it should "throw an IAE when it cannot evaluate the what expression" in { 111 | val action = JdbcSelectAction("request", "${what}", "table", None, List(simpleCheck(_ => true)), clock, statsEngine, next) 112 | 113 | an[IllegalArgumentException] should be thrownBy action.execute(session) 114 | } 115 | 116 | it should "throw an IAE when it cannot evaluate the from expression" in { 117 | val action = JdbcSelectAction("request", "*", "${from}", None, List(simpleCheck(_ => true)), clock, statsEngine, next) 118 | 119 | an[IllegalArgumentException] should be thrownBy action.execute(session) 120 | } 121 | 122 | it should "throw an IAE when it cannot evaluate the where expression" in { 123 | val action = JdbcSelectAction("request", "*", "table", Some("${where}"), List(simpleCheck(_ => true)), clock, statsEngine, next) 124 | 125 | an[IllegalArgumentException] should be thrownBy action.execute(session) 126 | } 127 | 128 | it should "pass the session to the next action" in { 129 | DB autoCommit { implicit session => 130 | sql"""CREATE TABLE insert_next(id INTEGER PRIMARY KEY )""".execute().apply() 131 | } 132 | val nextAction = NextAction(session) 133 | val action = JdbcSelectAction("request", "*", "INSERT_NEXT", None, List(simpleCheck(_ => true)), clock, statsEngine, nextAction) 134 | 135 | action.execute(session) 136 | 137 | waitForLatch(nextAction) 138 | nextAction.called should be(true) 139 | } 140 | 141 | it should "pass the session to the next action even when a check crashes" in { 142 | DB autoCommit { implicit session => 143 | sql"""CREATE TABLE crashes(id INTEGER PRIMARY KEY )""".execute().apply() 144 | } 145 | val nextAction = NextAction(session.markAsFailed) 146 | val action = JdbcSelectAction("request", "*", "CRASHES", None, List(simpleCheck(_ => throw new RuntimeException("Test error"))), clock, statsEngine, nextAction) 147 | 148 | action.execute(session) 149 | 150 | waitForLatch(nextAction) 151 | nextAction.called should be(true) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/check/JdbcSimpleCheckSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.check 2 | 3 | import java.time.Instant 4 | import java.util 5 | 6 | import io.gatling.commons.validation.{Failure, Success} 7 | import io.gatling.core.check.CheckResult 8 | import io.gatling.core.session.Session 9 | import org.scalatest.{FlatSpec, Matchers} 10 | 11 | import scala.collection.mutable 12 | 13 | /** 14 | * Created by ronny on 15.05.17. 15 | */ 16 | class JdbcSimpleCheckSpec extends FlatSpec with Matchers { 17 | 18 | val session = Session("scenario", 0, Instant.now.getEpochSecond) 19 | 20 | implicit val cache: util.Map[Any, Any] = new util.HashMap[Any, Any]() 21 | 22 | "JdbcSimpleCheck" should "log a success if the function returns true" in { 23 | val check = JdbcSimpleCheck(_ => true) 24 | val result = check.check(List.empty, session) 25 | 26 | result should equal(CheckResult.NoopCheckResultSuccess) 27 | } 28 | 29 | it should "log a failure if the function returns false" in { 30 | val check = JdbcSimpleCheck(_ => false) 31 | val result = check.check(List.empty, session) 32 | 33 | result should equal(Failure("JDBC check failed")) 34 | } 35 | 36 | it should "provide the response list to the function" in { 37 | val list = List(Map("foo" -> "bar"), Map("bar" -> "foo")) 38 | val check = JdbcSimpleCheck(response => response eq list) 39 | val result = check.check(list, session) 40 | 41 | result should equal(CheckResult.NoopCheckResultSuccess) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/mock/MockStatsEngine.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011-2017 GatlingCorp (http://gatling.io) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.codecentric.gatling.jdbc.mock 17 | 18 | import java.util.Date 19 | 20 | import io.gatling.commons.stats.Status 21 | import io.gatling.core.session.{GroupBlock, Session} 22 | import io.gatling.core.stats.StatsEngine 23 | import io.gatling.core.stats.message.ResponseTimings 24 | import io.gatling.core.stats.writer._ 25 | import akka.actor.ActorRef 26 | import com.typesafe.scalalogging.StrictLogging 27 | 28 | class MockStatsEngine extends StatsEngine with StrictLogging { 29 | 30 | var dataWriterMsg: List[DataWriterMessage] = List() 31 | 32 | override def start(): Unit = {} 33 | 34 | override def stop(replyTo: ActorRef, exception: Option[Exception]): Unit = {} 35 | 36 | override def logUser(userMessage: UserMessage): Unit = {} 37 | 38 | override def logResponse( 39 | session: Session, 40 | requestName: String, 41 | startTimestamp: Long, 42 | endTimestamp: Long, 43 | status: Status, 44 | responseCode: Option[String], 45 | message: Option[String] 46 | ): Unit = 47 | handle(ResponseMessage( 48 | session.scenario, 49 | session.userId, 50 | session.groupHierarchy, 51 | requestName, 52 | startTimestamp, 53 | endTimestamp, 54 | status, 55 | None, 56 | message 57 | )) 58 | 59 | override def logGroupEnd(session: Session, group: GroupBlock, exitTimestamp: Long): Unit = 60 | handle(GroupMessage(session.scenario, session.userId, group.hierarchy, group.startTimestamp, exitTimestamp, group.cumulatedResponseTime, group.status)) 61 | 62 | override def logCrash(session: Session, requestName: String, error: String): Unit = 63 | handle(ErrorMessage(error, new Date().getTime)) 64 | 65 | override def reportUnbuildableRequest(session: Session, requestName: String, errorMessage: String): Unit = {} 66 | 67 | private def handle(msg: DataWriterMessage) = { 68 | dataWriterMsg = msg :: dataWriterMsg 69 | logger.info(msg.toString) 70 | } 71 | } -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/protocol/JdbcProtocolSpec.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.protocol 2 | 3 | import de.codecentric.gatling.jdbc.Predef.jdbc 4 | import org.scalatest.{FlatSpec, Matchers} 5 | import scalikejdbc.{ConnectionPool, ConnectionPoolSettings} 6 | 7 | class JdbcProtocolSpec extends FlatSpec with Matchers { 8 | 9 | "JdbcProtocol" should "use default connection pool setting if none are provided" in { 10 | jdbc 11 | .url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE") 12 | .username("sa") 13 | .password("sa") 14 | .driver("org.h2.Driver") 15 | .build 16 | 17 | ConnectionPool.get().settings should equal(ConnectionPoolSettings()) 18 | } 19 | 20 | it should "use custom connection pool settings if they are given" in { 21 | val settings = ConnectionPoolSettings(maxSize = 20) 22 | jdbc 23 | .url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE") 24 | .username("sa") 25 | .password("sa") 26 | .driver("org.h2.Driver") 27 | .connectionPoolSettings(settings) 28 | .build 29 | 30 | ConnectionPool.get().settings should equal(settings) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/CreateTableSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class CreateTableSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("create table") 16 | .exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ), 25 | column( 26 | name("ac"), 27 | dataType("INTEGER") 28 | ) 29 | ) 30 | ) 31 | 32 | 33 | setUp(testScenario.inject(atOnceUsers(1))) 34 | .protocols(jdbcConfig) 35 | .assertions(global.failedRequests.count.is(0)) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/DeleteSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class DeleteSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("createTable"). 16 | exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ) 25 | ) 26 | ).repeat(10, "n") { 27 | exec(jdbc("insertion") 28 | .insert() 29 | .into("bar") 30 | .values("${n}") 31 | ) 32 | }.repeat(5, "n") { 33 | exec(jdbc("deletion") 34 | .delete() 35 | .from("bar") 36 | .where("abc=${n}")) 37 | } 38 | 39 | 40 | setUp(testScenario.inject(atOnceUsers(1))) 41 | .protocols(jdbcConfig) 42 | .assertions(global.failedRequests.count.is(0)) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/DropTableSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class DropTableSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("createTable"). 16 | exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ), 25 | column( 26 | name("ac"), 27 | dataType("INTEGER") 28 | ) 29 | ) 30 | ).exec(jdbc("drop bar table").drop().table("bar")) 31 | 32 | 33 | setUp(testScenario.inject(atOnceUsers(1))) 34 | .protocols(jdbcConfig) 35 | .assertions(global.failedRequests.count.is(0)) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/InsertMySqlSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | import org.testcontainers.containers.MySQLContainer 8 | 9 | /** 10 | * Created by ronny on 10.05.17. 11 | */ 12 | class InsertMySqlSimulation extends Simulation { 13 | 14 | val mySql = new MySQLContainer() 15 | mySql.start() 16 | 17 | val jdbcConfig = jdbc.url(mySql.getJdbcUrl).username(mySql.getUsername).password(mySql.getPassword).driver(mySql.getDriverClassName) 18 | 19 | val tableIdentFeeder = for (x <- 0 until 10) yield Map("tableId" -> x) 20 | 21 | val uniqueNumberFeeder = for (x <- 0 until 100) yield Map("unique" -> x) 22 | 23 | after { 24 | mySql.stop() 25 | } 26 | 27 | val createTables = scenario("createTable").feed(tableIdentFeeder.iterator). 28 | exec(jdbc("create") 29 | .create() 30 | .table("bar${tableId}") 31 | .columns( 32 | column( 33 | name("abc"), 34 | dataType("INTEGER"), 35 | constraint("PRIMARY KEY") 36 | ) 37 | ) 38 | ) 39 | 40 | val fillTables = feed(uniqueNumberFeeder).repeat(100, "n") { 41 | feed(tableIdentFeeder.iterator.toArray.random). 42 | exec(jdbc("insertion") 43 | .insert() 44 | .into("bar${tableId}") 45 | .values("${unique}${n}") 46 | ) 47 | } 48 | 49 | 50 | setUp( 51 | createTables.inject(atOnceUsers(10)), 52 | scenario("fillTables").pause(5).exec(fillTables).inject(atOnceUsers(100)) 53 | ).protocols(jdbcConfig) 54 | .assertions(global.successfulRequests.percent.gte(99)) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/InsertPostgresSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | import org.testcontainers.containers.PostgreSQLContainer 8 | 9 | /** 10 | * Created by ronny on 10.05.17. 11 | */ 12 | class InsertPostgresSimulation extends Simulation { 13 | 14 | val postgres = new PostgreSQLContainer() 15 | postgres.start() 16 | 17 | val jdbcConfig = jdbc.url(postgres.getJdbcUrl).username(postgres.getUsername).password(postgres.getPassword).driver(postgres.getDriverClassName) 18 | 19 | val tableIdentFeeder = for (x <- 0 until 10) yield Map("tableId" -> x) 20 | 21 | val uniqueNumberFeeder = for (x <- 0 until 100) yield Map("unique" -> x) 22 | 23 | after { 24 | postgres.stop() 25 | } 26 | 27 | val createTables = scenario("createTable").feed(tableIdentFeeder.iterator). 28 | exec(jdbc("create") 29 | .create() 30 | .table("bar${tableId}") 31 | .columns( 32 | column( 33 | name("abc"), 34 | dataType("INTEGER"), 35 | constraint("PRIMARY KEY") 36 | ) 37 | ) 38 | ) 39 | 40 | val fillTables = feed(uniqueNumberFeeder).repeat(100, "n") { 41 | feed(tableIdentFeeder.iterator.toArray.random). 42 | exec(jdbc("insertion") 43 | .insert() 44 | .into("bar${tableId}") 45 | .values("${unique}${n}") 46 | ) 47 | } 48 | 49 | 50 | setUp( 51 | createTables.inject(atOnceUsers(10)), 52 | scenario("fillTables").pause(5).exec(fillTables).inject(atOnceUsers(100)) 53 | ).protocols(jdbcConfig) 54 | .assertions(global.successfulRequests.percent.gte(99)) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/InsertSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | import scala.concurrent.duration._ 9 | import scala.util.Random 10 | 11 | /** 12 | * Created by ronny on 10.05.17. 13 | */ 14 | class InsertSimulation extends Simulation { 15 | 16 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 17 | val feeder = Iterator.continually(Map("rand" -> (Random.alphanumeric.take(20).mkString + "@foo.com"))) 18 | 19 | val createTable = scenario("create table") 20 | .exec(jdbc("bar table") 21 | .create() 22 | .table("bar") 23 | .columns( 24 | column( 25 | name("abc"), 26 | dataType("VARCHAR"), 27 | constraint("PRIMARY KEY") 28 | ) 29 | ) 30 | ) 31 | 32 | val insertion = scenario("insertion") 33 | .pause(3.seconds) 34 | .feed(feeder) 35 | .repeat(10, "n") { 36 | exec(jdbc("insertion") 37 | .insert() 38 | .into("bar (abc)") 39 | .values("'${rand} + ${n}'") 40 | ) 41 | } 42 | 43 | setUp( 44 | createTable.inject(atOnceUsers(1)), 45 | insertion.inject(atOnceUsers(10)) 46 | ).protocols(jdbcConfig) 47 | .assertions(global.failedRequests.count.is(0)) 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/SelectAnyCheckSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class SelectAnyCheckSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("createTable"). 16 | exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ), 25 | column( 26 | name("foo"), 27 | dataType("INTEGER") 28 | ) 29 | ) 30 | ).repeat(10, "n") { 31 | exec(jdbc("insertion") 32 | .insert() 33 | .into("bar") 34 | .values("${n}, ${n}") 35 | ) 36 | }.pause(1). 37 | exec(jdbc("selectionSingleCheck") 38 | .select("*") 39 | .from("bar") 40 | .where("abc=4") 41 | .check(jdbcSingleResponse.is(Map[String, Any]("ABC" -> 4, "FOO" -> 4)) 42 | .saveAs("myResult")) 43 | ).pause(1). 44 | exec(jdbc("selectionManyCheck") 45 | .select("*") 46 | .from("bar") 47 | .where("abc=4 OR abc=5") 48 | .check(jdbcManyResponse.is(List( 49 | Map("ABC" -> 4, "FOO" -> 4), 50 | Map("ABC" -> 5, "FOO" -> 5))) 51 | ) 52 | ) 53 | //.exec(session => session("something").as[List[Map[String, Any]]]) 54 | 55 | 56 | setUp(testScenario.inject(atOnceUsers(1))) 57 | .protocols(jdbcConfig) 58 | .assertions(global.failedRequests.count.is(0)) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/SelectCheckSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class SelectCheckSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("createTable"). 16 | exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ), 25 | column( 26 | name("foo"), 27 | dataType("INTEGER") 28 | ) 29 | ) 30 | ).repeat(10, "n") { 31 | exec(jdbc("insertion") 32 | .insert() 33 | .into("bar") 34 | .values("${n}, ${n}") 35 | ) 36 | }.pause(1). 37 | exec(jdbc("selection") 38 | .select("*") 39 | .from("bar") 40 | .where("abc=4") 41 | .check(simpleCheck(result => result.head("FOO") == 4)) 42 | ) 43 | 44 | 45 | setUp(testScenario.inject(atOnceUsers(1))) 46 | .protocols(jdbcConfig) 47 | .assertions(global.failedRequests.count.is(0)) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/de/codecentric/gatling/jdbc/simulation/SelectSimulation.scala: -------------------------------------------------------------------------------- 1 | package de.codecentric.gatling.jdbc.simulation 2 | 3 | import de.codecentric.gatling.jdbc.Predef._ 4 | import de.codecentric.gatling.jdbc.builder.column.ColumnHelper._ 5 | import io.gatling.core.Predef._ 6 | import io.gatling.core.scenario.Simulation 7 | 8 | /** 9 | * Created by ronny on 10.05.17. 10 | */ 11 | class SelectSimulation extends Simulation { 12 | 13 | val jdbcConfig = jdbc.url("jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE").username("sa").password("sa").driver("org.h2.Driver") 14 | 15 | val testScenario = scenario("createTable"). 16 | exec(jdbc("bar table") 17 | .create() 18 | .table("bar") 19 | .columns( 20 | column( 21 | name("abc"), 22 | dataType("INTEGER"), 23 | constraint("PRIMARY KEY") 24 | ) 25 | ) 26 | ).repeat(10, "n") { 27 | exec(jdbc("insertion") 28 | .insert() 29 | .into("bar") 30 | .values("${n}") 31 | ) 32 | }.pause(1). 33 | exec(jdbc("selection") 34 | .select("*") 35 | .from("bar") 36 | .where("abc=4") 37 | ) 38 | 39 | 40 | setUp(testScenario.inject(atOnceUsers(1))) 41 | .protocols(jdbcConfig) 42 | .assertions(global.failedRequests.count.is(0)) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "2.1.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------