Skip to content

feat: new profile type parser #120

Merged
merged 21 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,63 @@ profile:
}
```

#### New style profile:

New profile YAML configuration example:

```yaml
apiVersion: link.ru/v1alpha1
kind: PerformanceTestProfiles
metadata:
name: performance-test-profile
description: performance test profile
spec:
- name: maxPerf
period: 10.05.2022 - 20.05.2022
protocol: http
profile:
- request: request-1
intensity: 100 rph
groups: ["Group1"]
params:
method: POST
path: /test/a
headers:
- 'Content-Type: application/json'
- 'Connection: keep-alive'
body: '{"a": "b"}'
- request: request-2
intensity: 200 rph
groups: ["Group1", "Group2"]
params:
method: GET
path: /test/b
body: '{"c": "d"}'
- request: request-3
intensity: 200 rph
groups: [ "Group1", "Group2" ]
params:
method: GET
path: /test/c
body: '{"e": "f"}'
```

*Simulation setUp*

```scala
class Debug extends Simulation {
val profileConfigName = "profile.yml"
val scn = ProfileBuilderNew.buildFromYaml(profileConfigName).selectProfile("maxPerf").toRandomScenario

setUp(
scn.inject(
atOnceUsers(10)
).protocols(httpProtocol)
)
.maxDuration(10)
}
```

### redis

This module allows you to use Redis commands.
Expand Down
1 change: 1 addition & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ object Dependencies {
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser",
"io.circe" %% "circe-yaml",
).map(_ % "0.14.1")

lazy val scalaTesting: Seq[ModuleID] = scalaCheck ++ scalaTest ++ scalaMock ++ scalaTestPlus
Expand Down
75 changes: 75 additions & 0 deletions src/main/scala/ru/tinkoff/gatling/profile/ProfileBuilderNew.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ru.tinkoff.gatling.profile

import io.circe
import io.circe.DecodingFailure
import io.circe.yaml._
import io.circe.generic.auto._
import io.gatling.core.Predef._
import io.gatling.core.structure.{ChainBuilder, ScenarioBuilder}
import io.gatling.http.Predef._
import io.gatling.http.request.builder.HttpRequestBuilder
import ru.tinkoff.gatling.utils.IntensityConverter.getIntensityFromString

import scala.io.{BufferedSource, Source}
import scala.util.matching.Regex

case class Params(method: String, path: String, headers: Option[List[String]], body: Option[String])

case class Request(request: String, intensity: String, groups: Option[List[String]], params: Params) {

val requestIntensity: Double = getIntensityFromString(intensity)

def toRequest: HttpRequestBuilder = {
val regexHeader: Regex = """(.+?): (.+)""".r
val requestBody: String = params.body.getOrElse("")
val requestHeaders: List[String] = params.headers.getOrElse(List.empty[String])
http(request)
.httpRequest(params.method, params.path)
.body(StringBody(requestBody))
.headers(requestHeaders.map { case regexHeader(a, b) => (a, b) }.toMap)
}

def toExec: ChainBuilder = exec(toRequest)
def toTuple: (Double, ChainBuilder) = (requestIntensity, toExec)

}

case class OneProfile(name: String, period: String, protocol: String, profile: List[Request]) {

def toRandomScenario: ScenarioBuilder = {
val requests: List[(Double, ChainBuilder)] = profile.map(request => request.toTuple)
val intensitySum: Double = requests.map { case (intensity, _) => intensity }.sum
val prepRequests: List[(Double, ChainBuilder)] =
requests.map { case (intensity, chain) => (100 * intensity / intensitySum, chain) }
scenario(name)
.randomSwitch(prepRequests: _*)
}

}

case class Metadata(name: String, description: String)

case class Yaml(apiVersion: String, kind: String, metadata: Metadata, spec: List[OneProfile]) {

def selectProfile(profileName: String): OneProfile = {
spec.find(_.name == profileName).getOrElse(throw new NoSuchElementException(s"Selected wrong profile: $profileName"))
}

}

object ProfileBuilderNew {

def buildFromYaml(path: String): Yaml = {
val bufferedSource: BufferedSource = Source.fromResource(path)
val yamlContent: String = bufferedSource.mkString
bufferedSource.close
val yamlParsed: Either[circe.Error, Yaml] = parser.parse(yamlContent).flatMap(json => json.as[Yaml])
yamlParsed match {
case Right(yaml) => yaml
case Left(DecodingFailure(_, value)) =>
throw new IllegalArgumentException(s"""Field "${value.head.productElement(0)}" is not filled""")
case Left(error) => throw error
}
}

}
19 changes: 19 additions & 0 deletions src/test/resources/profileTemplates/profile1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: link.ru/v1alpha1
kind: PerformanceTestProfiles
metadata:
name: performance-test-profile
description: performance test profile
spec:
- name: maxPerf
period: 10.05.2022 - 20.05.2022
protocol: http
profile:
- request: request-1
intensity: 100 rph
groups: ["Group1"]
params:
method: POST
path: /test/a
headers:
- 'greetings: Hello world!'
body: '{"a": "b"}'
52 changes: 52 additions & 0 deletions src/test/scala/ru/tinkoff/gatling/profile/ProfileBuilderTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ru.tinkoff.gatling.profile

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks

class ProfileBuilderTest extends AnyFlatSpec with Matchers with ScalaCheckDrivenPropertyChecks {

val profile1FromFile: String = "profileTemplates/profile1.yml"
val parsedYaml: Yaml = Yaml(
"link.ru/v1alpha1",
"PerformanceTestProfiles",
Metadata("performance-test-profile", "performance test profile"),
List(
OneProfile(
"maxPerf",
"10.05.2022 - 20.05.2022",
"http",
List(
Request(
"request-1",
"100 rph",
Some(List("Group1")),
Params("POST", "/test/a", Some(List("greetings: Hello world!")), Some("""{"a": "b"}""")),
),
),
),
),
)
val parsedProfile: OneProfile = OneProfile(
"maxPerf",
"10.05.2022 - 20.05.2022",
"http",
List(
Request(
"request-1",
"100 rph",
Some(List("Group1")),
Params("POST", "/test/a", Some(List("greetings: Hello world!")), Some("""{"a": "b"}""")),
),
),
)

it should "load profile yaml correctly" in {
ProfileBuilderNew.buildFromYaml(profile1FromFile) shouldBe parsedYaml
}

it should "get profile from parsed yaml correctly" in {
ProfileBuilderNew.buildFromYaml(profile1FromFile).selectProfile("maxPerf") shouldBe parsedProfile
}

}