diff --git a/.github/scripts/extract_and_generate_html.sh b/.github/scripts/extract_and_generate_html.sh new file mode 100755 index 0000000..13870ec --- /dev/null +++ b/.github/scripts/extract_and_generate_html.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# Destination directory path +destination_dir="doc/gatling" + +# Function to copy JS and CSS files +copy_js_and_css() { + mkdir -p "${destination_dir}/js" + mkdir -p "${destination_dir}/style" + cp -rf "./loading/target/gatling/${1}/js" "${destination_dir}" + cp -rf "./loading/target/gatling/${1}/style" "${destination_dir}" +} + +# Function to perform replacements in HTML files +replace_in_html_files() { + search="$1" + replace="$2" + directory="$3" + find "${destination_dir}/${directory}" -type f -name "*.html" -exec sed -i -e "s|$search|$replace|g" {} \; +} + +# Fonction pour extraire et formater les résultats +generate_html_table() { + run="$1" + stats_file="./loading/target/gatling/$run/js/stats.json" + + # Extraire les données nécessaires du fichier JSON avec jq + tableContent=$(jq -r ' + (.contents[] | " + + " + .stats.name + " + " + (.stats.numberOfRequests.ok | tostring) + " + " + (.stats.numberOfRequests.ko | tostring) + " + " + (.stats.minResponseTime.total | tostring) + " + " + (.stats.maxResponseTime.total | tostring) + " + " + (.stats.meanResponseTime.total | tostring) + " + " + (.stats.standardDeviation.total | tostring) + " + " + (.stats.meanNumberOfRequestsPerSecond.total | tostring) + " + " + )' "$stats_file") + + # Créer le tableau HTML + htmlTable=" + + $tableContent +
RequestSuccess ✅Errors ❌MinMaxAvg.Std. Dev.RPS
" + + # Imprimer le contenu HTML + echo "$htmlTable" +} + +# Read the contents of lastRun.txt and sort it +last_runs=($(sort -n < loading/target/gatling/lastRun.txt)) + +# Create the HTML template +template_file="./.github/scripts/template.html" +output_file="loading/target/gatling/summary.html" +cp "$template_file" "$output_file" + +# Initialize the content variable +content="" + +# Copy JS and CSS for the first run +copy_js_and_css "${last_runs[0]}" + +# Iterate through the directories obtained from lastRun.txt +for run in "${last_runs[@]}"; do + # Extract name and date from the entry + report_name="${run%-*}" + timestamp="${run#*-}" + year="${timestamp:0:4}" + month="${timestamp:4:2}" + day="${timestamp:6:2}" + hour="${timestamp:8:2}" + minute="${timestamp:10:2}" + second="${timestamp:12:2}" + + formatted_date="${year}-${month}-${day} ${hour}:${minute}:${second}" + + # Construct the desired output + content+="
  • Results for $report_name at $formatted_date

  • " + + html_output=$(generate_html_table ${run}) + + # Append le contenu de la table à la variable content + content+="$html_output" + + # Copy .html and .log files to the destination directory + mkdir -p "${destination_dir}/$run" + cp -rf "./loading/target/gatling/$run"/* "${destination_dir}/$run" + + # Remove unnecessary directories + rm -rf "${destination_dir}/$run"/js + rm -rf "${destination_dir}/$run"/style + + # Perform replacements in HTML files + replace_in_html_files "src=\"js/" "src=\"../js/" "$run" + replace_in_html_files "\"style/" "\"../style/" "$run" + + find "${destination_dir}/${run}" -type f -name "*.html" -exec sed -i 's/details_link\" href=\//details_link\" href=\"\.\//g' {} \; + + replace_in_html_files "details_link\" href=\"" "details_link\" href=\"./" "$run" + + # Add "SUMMARY" link back to the Summary + replace_in_html_files '
    ' '
    ' "$run" +done + +ls -la ${destination_dir} + +tree ${destination_dir} + +# Replace {{CONTENT}} in the template with the generated content +# sed -i "s|{{CONTENT}}|$content|g" "$output_file" +sed -i -e "/{{CONTENT}}/r /dev/stdin" "$output_file" <<< "$content" + +# Copy the summary.html file to the destination directory +cp "$output_file" "${destination_dir}" diff --git a/.github/scripts/template.html b/.github/scripts/template.html new file mode 100644 index 0000000..aff31a0 --- /dev/null +++ b/.github/scripts/template.html @@ -0,0 +1,33 @@ + + + + + + + + Gatling Stats - Global Information + + +
    +
    + +
    +
    +
    +
    +
    +
    +

    > Suit Tests

    +
      + {{CONTENT}} +
    +
    +
    +
    +
    +
    + + diff --git a/.github/workflows/build-feature.yml b/.github/workflows/build-feature.yml index 71dacdc..e9ee0e6 100644 --- a/.github/workflows/build-feature.yml +++ b/.github/workflows/build-feature.yml @@ -26,6 +26,9 @@ concurrency: jobs: analyze_code: + environment: + name: analyze-code-quality + url: https://sonarcloud.io/summary/new_code?id=Raouf25_spring-boot-asynchronous-api name: Analyze Code Quality runs-on: ubuntu-latest steps: @@ -84,9 +87,9 @@ jobs: non_regression_test: name: Non Regression Test - environment: - name: dev-karate-report - url: ${{ steps.deployment.outputs.page_url }}karate-summary.html +# environment: +# name: dev-karate-report +# url: ${{ steps.deployment.outputs.page_url }}karate/karate-summary.html needs: deploy runs-on: ubuntu-latest steps: @@ -115,13 +118,136 @@ jobs: - name: Non Regression Test continue-on-error: true run: | - mvn clean test --f ./nrt/pom.xml -PflyServer -Dapi.url='https://spring-boot-asynchronous-api.fly.dev' + mvn clean test --f ./nrt/pom.xml -PflyServer -Dapi.url='https://spring-boot-asynchronous-api.fly.dev' # Create a "doc" directory and move karate reports to it - name: Get report run: | - mkdir -p doc - mv ./nrt/target/karate-reports/* ./doc + mkdir -p doc/karate + mv ./nrt/target/karate-reports/* ./doc/karate + + - uses: actions/upload-artifact@master + with: + name: karate-reports + path: doc/karate +# - name: Upload artifact +# uses: actions/upload-pages-artifact@v1 +# with: +# # Upload entire repository +# path: './doc' +# - name: Deploy to GitHub Pages +# id: deployment +# uses: actions/deploy-pages@v1 + + loading_test: + name: Loading Test +# environment: +# name: prep-loading-report +# url: ${{ steps.deployment.outputs.page_url }}gatling/summary.html + needs: non_regression_test + runs-on: ubuntu-latest + steps: + # Checkout the code from the repository + - name: Checkout + uses: actions/checkout@v3 + + # Execute Gatling Test Suite + - name: Execute Gatling Test Suite + continue-on-error: true + run: | + mvn clean gatling:test --f loading/pom.xml + + - name: Install jq + run: | + sudo apt-get update -y + sudo apt-get install jq -y + shell: bash + + # New step to generate gatling-summary.html + - name: Generate gatling-summary HTML + run: | + .github/scripts/extract_and_generate_html.sh + shell: bash + + - uses: actions/upload-artifact@master + with: + name: gatling-reports + path: doc/gatling + +# - name: Upload artifact +# uses: actions/upload-pages-artifact@v1 +# with: +# # Upload entire repository +# path: './doc' +# - name: Deploy to GitHub Pages +# id: deployment +# uses: actions/deploy-pages@v1 + + - name: Generate Job Summary + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs') + const lastRuns = fs.readFileSync(`loading/target/gatling/lastRun.txt`).toString().trim().split('\n'); + + for(const run of lastRuns) { + const formattedLine = run.replace(/(.*)-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, ' $1 at $2-$3-$4 $5:$6:$7'); + const results = JSON.parse(fs.readFileSync(`loading/target/gatling/${run}/js/stats.json`).toString()); + let tableContent = [ + [ + {data: 'Request', header: true}, + {data: 'Success ✅', header: true}, + {data: 'Errors ❌', header: true}, + {data: 'Min', header: true}, + {data: 'Max', header: true}, + {data: 'Avg.', header: true}, + {data: 'Std. Dev.', header: true}, + {data: 'RPS', header: true}, + ] + ]; + + for(const result in results.contents) { + const requestMetrics = results.contents[result].stats; + tableContent.push([ + requestMetrics.name, + requestMetrics.numberOfRequests.ok.toString(), + requestMetrics.numberOfRequests.ko.toString(), + requestMetrics.minResponseTime.total.toString(), + requestMetrics.maxResponseTime.total.toString(), + requestMetrics.meanResponseTime.total.toString(), + requestMetrics.standardDeviation.total.toString(), + requestMetrics.meanNumberOfRequestsPerSecond.total.toString(), + ]); + } + + await core.summary + .addHeading(`Results for ${formattedLine}`) + .addTable(tableContent) + .addQuote('All times are in millisecond (ms). RPS means "Requests per Second"') + .write() + } + + deploy_reports: + name: Deploy Reports + needs: + - non_regression_test + - loading_test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Retrieve saved Non Regression Test + uses: actions/download-artifact@v3 + with: + name: karate-reports + path: doc/karate + + - name: Retrieve saved Loading Test + uses: actions/download-artifact@v3 + with: + name: gatling-reports + path: doc/gatling - name: Upload artifact uses: actions/upload-pages-artifact@v1 @@ -131,3 +257,8 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 + + - name: Echo URL Reports + run: | + echo "Non Regression Test url : ${{ steps.deployment.outputs.page_url }}karate/karate-summary.html" + echo "Loading Test url : ${{ steps.deployment.outputs.page_url }}gatling/summary.html" diff --git a/.gitignore b/.gitignore index 9d0b50f..105e0f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar app/target/ +loading/target/ nrt/target/ +**/.DS_Store ### STS ### .apt_generated @@ -29,3 +31,5 @@ build/ ### VS Code ### .vscode/ + +.DS_Store diff --git a/Dockerfile b/Dockerfile index 65bfbda..c7dcdd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,15 +11,37 @@ RUN mvn -f /build/pom.xml clean package # Package stage # # base image to build a JRE -FROM amazoncorretto:19.0.2-alpine as corretto-jdk +FROM amazoncorretto:19.0.2-alpine AS deps + +# Identify dependencies +COPY --from=MAVEN_BUILD ./build/app/target/*-SNAPSHOT.jar /app/app.jar +RUN mkdir /app/unpacked && \ + cd /app/unpacked && \ + unzip ../app.jar && \ + cd .. && \ + $JAVA_HOME/bin/jdeps \ + --ignore-missing-deps \ + --print-module-deps \ + -q \ + --recursive \ + --multi-release 17 \ + --class-path="./unpacked/BOOT-INF/lib/*" \ + --module-path="./unpacked/BOOT-INF/lib/*" \ + ./app.jar > /deps.info + +# base image to build a JRE +FROM amazoncorretto:19.0.2-alpine AS corretto-jdk # required for strip-debug to work RUN apk add --no-cache binutils +# copy module dependencies info +COPY --from=deps /deps.info /deps.info + # Build small JRE image RUN $JAVA_HOME/bin/jlink \ --verbose \ - --add-modules ALL-MODULE-PATH \ + --add-modules $(cat /deps.info) \ --strip-debug \ --no-man-pages \ --no-header-files \ diff --git a/Readme.md b/Readme.md index e465ce1..2c88a0a 100644 --- a/Readme.md +++ b/Readme.md @@ -3,7 +3,7 @@ ## Overview -This project is a sample of an asynchronous API built in Spring Boot. It provides asynchronous endpoints that are called asynchronously and can be used as a starting point to build more complex systems. +Spring-Boot-Asynchronous-API is a sample project showcasing the implementation of an asynchronous API built using Spring Boot. It offers asynchronous endpoints, allowing for concurrent and non-blocking execution, making it an excellent foundation for developing more complex systems. ## Prerequisites * Java 19 @@ -21,11 +21,19 @@ mvn spring-boot:run ``` Once the server is up and running, you can access the web service at http://localhost:8080/. -## how run nrt locally: +## Running Non-Regression Tests Locally: +You can run non-regression tests locally with the following command: ```shell mvn clean test -D"skip.tests"=false -Plocal -Dapi.url=http://localhost:8080 ``` +## Running Gatling Loading Tests Locally: +To run Gatling loading tests locally, navigate to the loading directory and use the following command: +```shell +cd loading +mvn -Dgatling.simulation.name=HttpSimulation4 clean gatling:test +``` + ## Github Actions Github Actions is a CI/CD platform that allows developers to automate their workflow with custom scripts. This project contains a Github Action workflow that executes the following steps: @@ -36,20 +44,24 @@ Github Actions is a CI/CD platform that allows developers to automate their work 4. Deploy in Dev environment - ✅ [swagger documentation](https://spring-boot-asynchronous-api.fly.dev/swagger-ui/index.html) 5. Run non regression tests & publish the generated reports - - ✅ [karate report]( https://raouf25.github.io/spring-boot-asynchronous-api/karate-summary.html ) + - ✅ [karate report](https://raouf25.github.io/spring-boot-asynchronous-api/karate/karate-summary.html) -The workflow also runs non-regression tests as part of the build process. The reports are published to the `reports` directory and can be accessed from the Github Actions UI. The reports can also be generated locally using the `mvn test` command. + The workflow also runs non-regression tests as part of the build process. The reports are published to the `reports` directory and can be accessed from the Github Actions UI. The reports can also be generated locally using the `mvn test` command. +6. Run loading tests & publish the generated reports + - ✅ [gatling report](https://raouf25.github.io/spring-boot-asynchronous-api/gatling/summary.html) ## Documentation For more information, please refer to the : - [Testing a Java Spring Boot REST API with Karate](https://semaphoreci.com/community/tutorials/testing-a-java-spring-boot-rest-api-with-karate) - [jacoco-multi-module-sample](https://medium.com/javarevisited/merging-integration-unit-and-functional-test-reports-with-jacoco-de5cde9b56e1) - [jacoco and sonar](https://www.baeldung.com/sonarqube-jacoco-code-coverage) - +- [gatling loading test](https://github.com/krizsan/gatling-examples) --------------- -## Steps of project building: -1. ✅ Rest API implementation: [swagger documentation](https://spring-boot-asynchronous-api.fly.dev/swagger-ui/index.html) -2. ✅ Non Regression Test: [karate report]( https://raouf25.github.io/spring-boot-asynchronous-api/karate-summary.html ) -3. ✅ Code coverage (sonar & jacoco): [code coverage in sonar](https://sonarcloud.io/summary/new_code?id=Raouf25_spring-boot-asynchronous-api) -4. 🚧 CI/CD releasing +## Project Building Steps: +The project building steps are as follows: +1. ✅ Implement the REST API. You can access the [Swagger documentation](https://spring-boot-asynchronous-api.fly.dev/swagger-ui/index.html) +2. ✅ Perform Non-Regression Testing. Review [Karate report](https://raouf25.github.io/spring-boot-asynchronous-api/karate/karate-summary.html) +3. ✅ Monitor and Improve Code Coverage. View [code coverage in sonar](https://sonarcloud.io/summary/new_code?id=Raouf25_spring-boot-asynchronous-api) +4. ✅ Run Loading Tests. Explore the [Gatling report](https://raouf25.github.io/spring-boot-asynchronous-api/gatling/summary.html) +5. 🚧 Ongoing work on CI/CD and releases. diff --git a/app/pom.xml b/app/pom.xml index 91f67ee..7d2f242 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -80,7 +80,7 @@ io.swagger swagger-annotations - 1.6.11 + 1.6.12 compile diff --git a/loading/pom.xml b/loading/pom.xml new file mode 100644 index 0000000..b265745 --- /dev/null +++ b/loading/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + com.mak + spring-boot-asynchronous-api + 0.0.1-SNAPSHOT + ../pom.xml + + + spring-boot-asynchronous-api-loading + spring-boot-asynchronous-api-loading + + + + + 19 + 19 + UTF-8 + + 2.12.3 + 2.12 + + 3.3.1 + 3.0.5 + + com.mak.springbootasynchronousapi.gatling.simulations + HttpSimulation4 + + + + + org.scala-lang + scala-library + ${scala.version} + + + + + io.gatling.highcharts + gatling-charts-highcharts + ${gatling.version} + test + + + + + src/main/scala + src/test/scala/ + + + + io.gatling + gatling-maven-plugin + ${gatling-plugin.version} + + + true + + ${gatling.simulations.package}.* + + + + + + diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/common/HttpSimulationBaseClass.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/common/HttpSimulationBaseClass.scala new file mode 100755 index 0000000..ab3b3a8 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/common/HttpSimulationBaseClass.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.common + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +import scala.concurrent.duration._ + +/** + * Abstract Gatling simulation class that specifies properties common to multiple Gatling simulations. + * Certain simulation parameters have been extracted to class variables which allows for subclasses + * to modify certain behaviour of the basic simulation by setting new values of these variables. + * + * @author Ivan Krizsan + */ +abstract class HttpSimulationBaseClass extends Simulation { + /* Base URL of requests sent in scenario 1. */ + protected var scenario1BaseURL = "" + /* Additional request path appended after the base URL in scenario 1. */ + protected var scenario1RequestPath = "" + /* Final number of users in the simulation. */ + protected var finalUserCount = 10 + /* Time period after which the number of users is to reach the final number of users. */ + protected var userCountRampUpTime : FiniteDuration = (20 seconds) + + /** + * Creates the HTTP protocol builder used in the simulation. + */ + def createHttpProtocolBuilder(): HttpProtocolBuilder = { + val httpProtocolBuilder = http + .baseUrl(scenario1BaseURL) + .acceptHeader("text/plain") + .userAgentHeader("Gatling") + httpProtocolBuilder + } + + /** + * Creates the scenario builder used in the simulation. + */ + def createScenarioBuilder(): ScenarioBuilder = { + val scenarioBuilder = scenario("Scenario1") + .exec( + http("myRequest1") + .get("/" + scenario1RequestPath) + ) + scenarioBuilder + } + + /** + * Performs setup of the simulation. + * Needs to be called from the constructor of child classes, after the instance + * variables have been initialized, in order to create a scenario using the instance + * variable values of the child classes. + */ + def doSetUp(): Unit = { + val theScenarioBuilder = createScenarioBuilder() + val theHttpProtocolBuilder = createHttpProtocolBuilder() + + setUp( + theScenarioBuilder.inject(rampUsers(finalUserCount).during(userCountRampUpTime)) + ).protocols(theHttpProtocolBuilder) + } +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation1.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation1.scala new file mode 100755 index 0000000..5d466a1 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation1.scala @@ -0,0 +1,72 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +/** + * Example Gatling load test that sends one HTTP GET requests to a URL. + * Note that the request is redirected and this causes the request count to become two. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation1 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation1 extends Simulation { + /* Place for arbitrary Scala code that is to be executed before the simulation begins. */ + before { + println("***** My simulation is about to begin! *****") + } + + /* Place for arbitrary Scala code that is to be executed after the simulation has ended. */ + after { + println("***** My simulation has ended! ******") + } + + /* + * A HTTP protocol builder is used to specify common properties of request(s) to be sent, + * for instance the base URL, HTTP headers that are to be enclosed with all requests etc. + */ + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + + /* + * A scenario consists of one or more requests. For instance logging into a e-commerce + * website, placing an order and then logging out. + * One simulation can contain many scenarios. + */ + /* Scenario1 is a name that describes the scenario. */ + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + /* myRequest1 is a name that describes the request. */ + http("myRequest1") + .get("/") + ) + + /* + * Define the load simulation. + * Here we can specify how many users we want to simulate, if the number of users is to increase + * gradually or if all the simulated users are to start sending requests at once etc. + * We also specify the HTTP protocol builder to be used by the load simulation. + */ + setUp( + theScenarioBuilder.inject(atOnceUsers(1)) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation2.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation2.scala new file mode 100755 index 0000000..f397555 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation2.scala @@ -0,0 +1,59 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +import scala.concurrent.duration._ + +/** + * Example Gatling load test that sends two HTTP GET requests with a short pause between. + * The first request will be redirected, again making it look like there were two requests sent. + * The second request will not be redirected. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation2 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation2 extends Simulation { + + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + + /* + * This scenario consists of two GET requests; one to the base URL and one to /computers relative + * to the base URL. + * Between the requests there will be a pause for five seconds. + * Note that in order to get access to different durations, we must add the following import: + * import scala.concurrent.duration._ + */ + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("GET to base URL") + .get("/")) + .pace(4 seconds) + .exec( + http("GET to /computers") + .get("/computers")) + + setUp( + theScenarioBuilder.inject(atOnceUsers(1)) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation3.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation3.scala new file mode 100755 index 0000000..816464b --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation3.scala @@ -0,0 +1,49 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +/** + * Example Gatling load test simulating ten users that each sends one single HTTP GET request. + * All the users will start sending requests immediately when the simulation is started. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation3 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation3 extends Simulation { + + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("myRequest1") + .get("/computers")) + + setUp( + /* + * Here we specify that ten simulated users shall start sending requests immediately + * in the Scenario1 scenario. + */ + theScenarioBuilder.inject(atOnceUsers(10)) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation4.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation4.scala new file mode 100755 index 0000000..9e3b7b2 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation4.scala @@ -0,0 +1,55 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +import scala.concurrent.duration._ + +/** + * Example Gatling load test simulating a number of users that rises up to 10 users over + * a period of 20 seconds. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation4 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation4 extends Simulation { + + val theHttpProtocolBuilder: HttpProtocolBuilder = http + // .baseUrl("https://spring-boot-efficient-search-api.fly.dev/api") + .baseUrl("https://spring-boot-asynchronous-api.fly.dev") + + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("myRequest1") + // .get("/cars?country=Japan")) + .get("/api/v1/players")) + + setUp( + /* + * Increase the number of users that sends requests in the scenario Scenario1 to + * ten users during a period of 20 seconds. + */ + theScenarioBuilder.inject( + rampUsers(9000).during(1 minutes) + ) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation5.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation5.scala new file mode 100755 index 0000000..dec70bd --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation5.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.body.Body +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder + +/** + * Example Gatling load test that sends one HTTP POST requests to a URL. + * The body of the POST request contains the string "Farty Towels!". + * Two ways of passing HTTP headers are shown. + * Two URL parameters, "login" and "password", are passed in the URL of the request. + * Note that this simulation should fail, since a non-expected HTTP status code should be returned. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation5 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation5 extends Simulation { + + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + .acceptHeader("application/xml, text/html, text/plain, application/json, */*") + .acceptCharsetHeader("UTF-8") + .acceptEncodingHeader("gzip, deflate") + + val theCommonHeaders = Map( + "Accept" -> "application/xml, text/html, text/plain, application/json, */*", + "Accept-Encoding" -> "gzip, deflate") + + val theBody : Body = StringBody("Farty Towels!") + + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("Login and Post Data") + .post("/computers") + .body(theBody) + .headers(theCommonHeaders) + .queryParam("login", "admin") + .queryParam("password", "secret") + ) + + setUp( + theScenarioBuilder.inject(atOnceUsers(1)) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation6.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation6.scala new file mode 100755 index 0000000..28dd667 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation6.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +/** + * Example Gatling load test that sends one HTTP GET requests to a bad URL. + * The resulting HTTP status code should be 404, which is verified in the simulation. + * In addition, we also verify that the maximum response time during the simulation is less than 200 ms. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation6 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation6 extends Simulation { + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("Bad Request") + .get("/unknown") + /* + * Check the response of this request. It should be a HTTP status 404. + * Since the expected result is 404, the request will be verified as being OK + * and the simulation will thus succeed. + */ + .check( + status.is(404), + regex("Action not found").count.is(2) + ) + ) + + setUp( + theScenarioBuilder.inject(atOnceUsers(1)) + ) + /* + * This asserts that, for all the requests in all the scenarios in the simulation + * the maximum response time should be less than 200 ms. + * If this is not the case when the simulation runs, the simulation will considered to have failed. + */ + .assertions( + global.responseTime.max.lte(500), + forAll.failedRequests.count.lte(5), + details("Bad Request").successfulRequests.percent.gte(90) + ) + .protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation7.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation7.scala new file mode 100755 index 0000000..979a5dd --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation7.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import io.gatling.core.Predef._ +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocolBuilder +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +/** + * Example Gatling load test that sends one HTTP GET requests to a URL. + * The resulting HTTP status code should be one of the status codes in a list of expected status codes. + * The response body is examined and the request is considered to have failed if the specified regular + * expression is not matched. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation7 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation7 extends Simulation { + val theHttpProtocolBuilder: HttpProtocolBuilder = http + .baseUrl("http://computer-database.gatling.io") + + val theScenarioBuilder: ScenarioBuilder = scenario("Scenario1") + .exec( + http("Request Computers List") + .get("/computers") + /* Several checks on the response can be specified. */ + .check( + /* Check that the HTTP status returned is 200 or 201. */ + status.find.in(200, 202), + /* Check that there is at least one match of the supplied regular expression in the response body. */ + regex("Computer database").count.gte(1) + ) + ) + + setUp( + theScenarioBuilder.inject(atOnceUsers(1)) + ).protocols(theHttpProtocolBuilder) +} diff --git a/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation8.scala b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation8.scala new file mode 100755 index 0000000..7a41862 --- /dev/null +++ b/loading/src/test/scala/com/mak/springbootasynchronousapi/gatling/simulations/HttpSimulation8.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2016-2020 Ivan Krizsan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mak.springbootasynchronousapi.gatling.simulations + +import com.mak.springbootasynchronousapi.gatling.common.HttpSimulationBaseClass + +import scala.concurrent.duration._ + +/** + * Example Gatling load test that is based on an abstract base class. + * Run this simulation with: + * mvn -Dgatling.simulation.name=HttpSimulation8 gatling:test + * + * @author Ivan Krizsan + */ +class HttpSimulation8 extends HttpSimulationBaseClass{ + scenario1BaseURL = "http://computer-database.gatling.io" + scenario1RequestPath = "computers" + finalUserCount = 4 + userCountRampUpTime = (5 seconds) + + /* + * doSetUp needs to be called here, after the instance variables have been + * initialized but before the check for a scenario has been performed. + * The call to doSetUp() cannot be located to a before-hook since it is called too late. + */ + doSetUp() +} diff --git a/pom.xml b/pom.xml index 76cb601..f29f169 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ app nrt + loading