How to run kotest which are not tagged by default? - kotlin

In the kotest framework, there is a way to group tests with custom tags and you can run the particular group by selecting via Gradle parameter like gradle test -Dkotest.tags="TestGroupOne"
I have two test cases one is with a tag and another one is without a tag
object Linux : Tag()
class MyTests : StringSpec({
"without tag" {
"hello".length shouldBe 5
}
"with tag".config(tags = setOf(Linux)) {
"world" should startWith("wo2")
}
})
Now if I run gradle build it runs both tests, but I would like to run the tests which are not tagged by default. In the above example, the test without tag should run if there is no parameter passed in gradle
One way to achieve this behaviour is by adding a task in build.gradle.kts file
val test by tasks.getting(Test::class) {
systemProperties = System.getProperties()
.toList()
.associate { it.first.toString() to it.second }
if(!systemProperties.containsKey("kotest.tags"))
systemProperties["kotest.tags"] = "!Linux"
}
As you can see, when there is no parameter passed for -Dkotest.tags I'm manually adding the value !Linux to the systemProperties so that the build script will run tests which are not tagged by default.
Question: Is there any better way to achieve this?
I even tried adding systemProp.gradle.kotest.tags="!Linux" in gradle.properties file but there is no effect.

Your solution is not very robust in the sense that you depend on the concrete tag that is used. It seems that there is no easier solution for that, because the syntax for tag expressions does not allow to write something like "!any".
However, it is possible to write a Kotest extension for what you need that looks like this:
import io.kotest.core.TagExpression
import io.kotest.core.config.ProjectConfiguration
import io.kotest.core.extensions.ProjectExtension
import io.kotest.core.extensions.TestCaseExtension
import io.kotest.core.project.ProjectContext
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import io.kotest.engine.tags.runtimeTags
object NoTagsExtension : TestCaseExtension, ProjectExtension {
private lateinit var config: ProjectConfiguration
override suspend fun interceptProject(context: ProjectContext, callback: suspend (ProjectContext) -> Unit) {
config = context.configuration
callback(context)
}
override suspend fun intercept(testCase: TestCase, execute: suspend (TestCase) -> TestResult): TestResult {
return if (config.runtimeTags().expression == TagExpression.Empty.expression) {
if (testCase.spec.tags().isEmpty() && testCase.config.tags.isEmpty()) {
execute(testCase)
} else TestResult.Ignored("currently running only tests without tags")
} else execute(testCase)
}
}
The first function interceptProject is just there to obtain the project configuration in order to determine the specified set of tags for the current test run.
The second function intercept is for each test-case. There we determine if any tags have been specified. If no tags were specified (i.e. we have an empty tag expression), we skip all test where any tag has been configured at the spec or test-case. Otherwise, we execute the test normally, and it will then possibly ignored by Kotlin's built-in mechanisms, depending on its tags.
The extension can be activated project-wide in the ProjectConfig:
class ProjectConfig : AbstractProjectConfig() {
override fun extensions(): List<Extension> = super.extensions() + NoTagsExtension
}
Now, with the extension in place, only tests without tag run by default, regardless of what tags you use in your project.

Related

Why is the kotlin-gradle-plugin failing to create a PSIFile from CodeInsightTestFixture.configureByText?

I created an IntelliJ plugin using the template https://github.com/JetBrains/intellij-platform-plugin-template. The template comes with a test that runs on an XML file. I want to create a similar test for a Kotlin file. Here's the template test file plus my added test (test2):
package org.jetbrains.plugins.template
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.psi.xml.XmlFile
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.util.PsiErrorElementUtil
#TestDataPath("\$CONTENT_ROOT/src/test/testData")
class MyPluginTest : BasePlatformTestCase() {
fun testXMLFile() {
val psiFile = myFixture.configureByText(XmlFileType.INSTANCE, "<foo>bar</foo>")
val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java)
assertFalse(PsiErrorElementUtil.hasErrors(project, xmlFile.virtualFile))
assertNotNull(xmlFile.rootTag)
xmlFile.rootTag?.let {
assertEquals("foo", it.name)
assertEquals("bar", it.value.text)
}
}
override fun getTestDataPath() = "src/test/testData/rename"
fun testRename() {
myFixture.testRename("foo.xml", "foo_after.xml", "a2")
}
// Here's my test
fun test2() {
val fileText: String = """
package com.loganmay.test
data class MyClass(val myString: String)
""".trimIndent()
val psiFile = myFixture.configureByText("a.kt", fileText)
val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java)
}
}
Without changing the build.gradle file, that test fails with:
Expected instance of: com.intellij.psi.xml.XmlFile actual: com.intellij.psi.impl.source.PsiPlainTextFileImpl
I want it to parse the text as a PsiFile that's also a KtFile. From various sources, I've been led to believe that the fixture is parsing it as a plain text file because the test project doesn't have access to the Kotlin compiler. So, I added:
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
to the build.gradle. Then, when I run the test, configureByText throws an exception with a big trace, the root exception of which is:
Caused by: java.lang.Throwable: 'filetype.archive.display.name' is not found in java.util.PropertyResourceBundle#4ecbb519(messages.CoreBundle)
... 53 more
org.jetbrains.plugins.template.MyPluginTest > test2 FAILED
com.intellij.diagnostic.PluginException at ComponentManagerImpl.kt:511
Caused by: java.util.MissingResourceException at Registry.java:164
Does anyone have any insight into what the issue is or know how to resolve it?
Notes:
I also tried importing the kotlin compiler and casting psiFile as KtFile, which produced the same error, an idea I got from here
This project has a test like this that may be working
This post and this post recommend adding the kotlin gradle plugin, which I did
This question seems similar
Yann Cebron replied on the jetbrains help forum with an answer for Java, which also worked for Kotlin.
The solution is to add a dependency to the IntelliJ gradle plugin. The template comes with these lines in the build.gradle:
intellij {
pluginName.set(properties("pluginName"))
version.set(properties("platformVersion"))
type.set(properties("platformType"))
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty))
}
So, didn't need to do anything there. In my gradle.properties, I added
platformPlugins = com.intellij.java, org.jetbrains.kotlin
To my plugin.xml, I added:
<depends>com.intellij.modules.java</depends>
<depends>org.jetbrains.kotlin</depends>
I was able to remove
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
from the build.gradle which I mentioned above.
Now, the test works for Java and Kotlin files.

Creating an IntelliJ Module Programmatically

I'm trying to make a proper project, like the one setup when you go through the Kotlin wizard or something, but I can't figure it out.
class SpigotKtWizard : ModuleBuilder() {
override fun setupRootModel(modifiableRootModel: ModifiableRootModel?) {}
override fun getModuleType(): ModuleType<*> {
return SpigotKtModuleType.instance
}
override fun createWizardSteps(wizardContext: WizardContext, modulesProvider: ModulesProvider): Array<ModuleWizardStep> {
return arrayOf(BuildOptionsStep())
}
override fun createProject(name: String?, path: String?): Project? {
val project = super.createProject(name, path) ?: return null
val d = project.baseDir.createChildData(this, "Test")
File(d.path).writeText("Testing boyyyy")
return project
}
}
This is what I have currently, and I'm getting this:
But other projects (and specifically, the same design I'm trying to achieve), look more like this:
Is there a page in the docs that I missed?
In pic 2 you're displaying a "project" instead of a "module", see the offical doc.
You may want to implement a DirectoryProjectGeneratorBase<YourSettingsBean> and register a (as an example you may refer to this file my julia plugin) directoryProjectGenerator in plugin.xml.
In your implementation of generateProject, you can create files, set files as source root/test root/excluded root by using:
ApplicationManager.getApplication().runWriteAction {
val modifiableModel: ModifiableRootModel = ModifiableModelsProvider.SERVICE.getInstance().getModuleModifiableModel(module)
module.rootManager.modifiableModel.apply {
inheritSdk()
contentEntries.firstOrNull()?.apply {
addExcludeFolder(findOrCreate(baseDir, "out", module))
addSourceFolder(findOrCreate(baseDir, "src", module), false)
}
commit()
}
ModifiableModelsProvider.SERVICE.getInstance().commitModuleModifiableModel(modifiableModel)
}
This should be missing in the doc, BTW. So it's not your fault. But I recommend you to take a look at the existing plugin projects (like the julia plugin mentioned above, the mathemetica plugin or the covscript plugin), which are extremely helpful for new comers.

Kotlin dynamically compile a class from source code at runtime

Is it possible to compile and instantiate Kotlin class at runtime? I'm talking about something like that but using Kotlin API: How do I programmatically compile and instantiate a Java class?
As example:
I'm getting full class definition as String:
val example = "package example\n" +
"\n" +
"fun main(args: Array<String>) {\n" +
" println(\"Hello World\")\n" +
"}\n"
And then inserting it into some class.kt and running it so I'm getting "Hello World" printed in console at runtime.
You might want to look at Kotlin Scripting, see https://github.com/andrewoma/kotlin-script
Alternatively, you'll need to write your own eval(kotlin-code-string-here) method which will dump the text inside blah.kt file for example, compile it using an external Kotlin compiler into blah.class then dynamically load those classes into the runtime using the Java Classloader doing something like this:
MainClass.class.classLoader.loadClass("com.mypackage.MyClass")
This might be very slow and unreliable.
Another no so great option is to make use of Rhino and run JavaScript inside your Kotlin code. So once again, you'll have an eval(kotlin-code-string-here) method which will dump the content to a blah.kt file, then you would use a Kotlin2JS compiler to compile it to JavaScript and directly execute the JavaScript inside Kotlin using Rhino which is not great either.
Another option is to make use of Kotlin Scripting or an external Kotlin compiler (in both cases, the Kotlin compiler will have to start up) and doing something like this will also allow you to execute dynamically, albeit, only on Unix systems.
Runtime.getRuntime().exec(""" "kotlin code here" > blah.kts | sh""")
I'm not aware of a clean solution for this, Kotlin was not designed to be run like like PHP / JavaScript / Python which just interprets text dynamically, it has to compile to bytecode first before it can do anything on the JVM; so in each scenario, you will need to compile that code first in one way or another, whether to bytecode or to javascript and in both cases load it into you application using the Java Classloader or Rhino.
Please check this solution for dependencies, jar resources, etc. Code below isn't enough for successful execution.
However, to compile dynamic class you can do the following:
val classLoader = Thread.currentThread().contextClassLoader
val engineManager = ScriptEngineManager(classLoader)
setIdeaIoUseFallback() // hack to have ability to do this from IntelliJ Idea context
val ktsEngine: ScriptEngine = engineManager.getEngineByExtension("kts")
ktsEngine.eval("object MyClass { val number = 123 } ")
println(ktsEngine.eval("MyClass.number"))
Please note: there is code injection possible here. Please be careful and use dedicated process or dedicated ClassLoader for this.
KotlinScript can be used to compile Kotlin source code (e.g. to generate a jar file that can then be loaded).
Here's a Java project which demonstrates this (code would be cleaner in Kotlin):
https://github.com/alexoooo/sample-kotlin-compile/blob/main/src/main/java/io/github/alexoooo/sample/compile/KotlinCompilerFacade.java
Note that the code you provide would be generated as a nested class (inside the script).
Here is a Kotlin version:
#KotlinScript
object KotlinDynamicCompiler {
//-----------------------------------------------------------------------------------------------------------------
const val scriptClassName = "__"
const val classNamePrefix = "${scriptClassName}$"
private val baseClassType: KotlinType = KotlinType(KotlinDynamicCompiler::class.java.kotlin)
private val contextClass: KClass<*> = ScriptCompilationConfiguration::class.java.kotlin
//-----------------------------------------------------------------------------------------------------------------
fun compile(
kotlinCode: String, outputJarFile: Path, classpathLocations: List<Path>, classLoader: ClassLoader
): String? {
Files.createDirectories(outputJarFile.parent)
val scriptCompilationConfiguration = createCompilationConfigurationFromTemplate(
baseClassType, defaultJvmScriptingHostConfiguration, contextClass
) {
jvm {
val classloaderClasspath: List<File> = classpathFromClassloader(classLoader, false)!!
val classpathFiles = classloaderClasspath + classpathLocations.map { it.toFile() }
updateClasspath(classpathFiles)
}
hostConfiguration(ScriptingHostConfiguration (defaultJvmScriptingHostConfiguration) {
jvm {
compilationCache(
CompiledScriptJarsCache { _, _ ->
outputJarFile.toFile()
}
)
}
})
}
val scriptCompilerProxy = ScriptJvmCompilerIsolated(defaultJvmScriptingHostConfiguration)
val result = scriptCompilerProxy.compile(
kotlinCode.toScriptSource(KotlinCode.scriptClassName), scriptCompilationConfiguration)
val errors = result.reports.filter { it.severity == ScriptDiagnostic.Severity.ERROR }
return when {
errors.isEmpty() -> null
else -> errors.joinToString(" | ")
}
}
}

Is there any way to iterate all fields of a data class without using reflection?

I know an alternative of reflection which is using javassist, but using javassist is a little bit complex. And because of lambda or some other features in koltin, the javassist doesn't work well sometimes. So is there any other way to iterate all fields of a data class without using reflection.
There are two ways. The first is relatively easy, and is essentially what's mentioned in the comments: assuming you know how many fields there are, you can unpack it and throw that into a list, and iterate over those. Or alternatively use them directly:
data class Test(val x: String, val y: String) {
fun getData() : List<Any> = listOf(x, y)
}
data class Test(val x: String, val y: String)
...
val (x, y) = Test("x", "y")
// And optionally throw those in a list
Although iterating like this is a slight extra step, this is at least one way you can relatively easy unpack a data class.
If you don't know how many fields there are (or you don't want to refactor), you have two options:
The first is using reflection. But as you mentioned, you don't want this.
That leaves a second, somewhat more complicated preprocessing option: annotations. Note that this only works with data classes you control - beyond that, you're stuck with reflection or implementations from the library/framework coder.
Annotations can be used for several things. One of which is metadata, but also code generation. This is a somewhat complicated alternative, and requires an additional module in order to get compile order right. If it isn't compiled in the right order, you'll end up with unprocessed annotations, which kinda defeats the purpose.
I've also created a version you can use with Gradle, but that's at the end of the post and it's a shortcut to implementing it yourself.
Note that I have only tested this with a pure Kotlin project - I've personally had problems with annotations between Java and Kotlin (although that was with Lombok), so I do not guarantee this will work at compile time if called from Java. Also note that this is complex, but avoids runtime reflection.
Explanation
The main issue here is a certain memory concern. This will create a new list every time you call the method, which makes it very similar to the method used by enums.
Local testing over 10000 iterations also show a general consistency of ~200 milliseconds to execute my approach, versus roughly 600 for reflection. However, for one iteration, mine uses ~20 milliseconds, where as reflection uses between 400 and 500 milliseconds. On one run, reflection took 1500 (!) milliseconds, while my approach took 18 milliseconds.
See also Java Reflection: Why is it so slow?. This appears to affect Kotlin as well.
The memory impact of creating a new list every time it's called can be noticeable though, but it'll also be collected so it shouldn't be that big a problem.
For reference, the code used for benchmarking (this will make sense after the rest of the post):
#AutoUnpack data class ExampleDataClass(val x: String, val y: Int, var m: Boolean)
fun main(a: Array<String>) {
var mine = 0L
var reflect = 0L
// for(i in 0 until 10000) {
var start = System.currentTimeMillis()
val cls = ExampleDataClass("example", 42, false)
for (field in cls) {
println(field)
}
mine += System.currentTimeMillis() - start
start = System.currentTimeMillis()
for (prop in ExampleDataClass::class.memberProperties) {
println("${prop.name} = ${prop.get(cls)}")
}
reflect += System.currentTimeMillis() - start
// }
println(mine)
println(reflect)
}
Setting up from scratch
This bases itself around two modules: a consumer module, and a processor module. The processor HAS to be in a separate module. It needs to be compiled separately from the consumer for the annotations to work properly.
First of all, your consumer project needs the annotation processor:
apply plugin: 'kotlin-kapt'
Additionally, you need to add stub generation. It complains it's unused while compiling, but without it, the generator seems to break for me:
kapt {
generateStubs = true
}
Now that that's in order, create a new module for the unpacker. Add the Kotlin plugin if you didn't already. You do not need the annotation processor Gradle plugin in this project. That's only needed by the consumer. You do, however, need kotlinpoet:
implementation "com.squareup:kotlinpoet:1.2.0"
This is to simplify aspects of the code generation itself, which is the important part here.
Now, create the annotation:
#Retention(AnnotationRetention.SOURCE)
#Target(AnnotationTarget.CLASS)
annotation class AutoUnpack
This is pretty much all you need. The retention is set to source because it has no value at runtime, and it only targets compile time.
Next, there's the processor itself. This is somewhat complicated, so bear with me. For reference, this uses the javax.* packages for annotation processing. Android note: this might work assuming you can plug in a Java module on a compileOnly scope without getting the Android SDK restrictions. As I mentioned earlier, this is mainly for pure Kotlin; Android might work, but I haven't tested that.
Anyways, the generator:
Because I couldn't find a way to generate the method into the class without touching the rest (and because according to this, that isn't possible), I'm going with an extension function generation approach.
You'll need a class UnpackCodeGenerator : AbstractProcessor(). In there, you'll first need two lines of boilerplate:
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf(AutoUnpack::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
Moving on, there's the processing. Override the process function:
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
// Find elements with the annotation
val annotatedElements = roundEnv.getElementsAnnotatedWith(AutoUnpack::class.java)
if(annotatedElements.isEmpty()) {
// Self-explanatory
return false;
}
// Iterate the elements
annotatedElements.forEach { element ->
// Grab the name and package
val name = element.simpleName.toString()
val pkg = processingEnv.elementUtils.getPackageOf(element).toString()
// Then generate the class
generateClass(name,
if (pkg == "unnamed package") "" else pkg, // This is a patch for an issue where classes in the root
// package return package as "unnamed package" rather than empty,
// which breaks syntax because "package unnamed package" isn't legal.
element)
}
// Return true for success
return true;
}
This just sets up some of the later framework. The real magic happens in the generateClass function:
private fun generateClass(className: String, pkg: String, element: Element){
val elements = element.enclosedElements
val classVariables = elements
.filter {
val name = if (it.simpleName.contains("\$delegate"))
it.simpleName.toString().substring(0, it.simpleName.indexOf("$"))
else it.simpleName.toString()
it.kind == ElementKind.FIELD // Find fields
&& Modifier.STATIC !in it.modifiers // that aren't static (thanks to sebaslogen for issue #1: https://github.com/LunarWatcher/KClassUnpacker/issues/1)
// Additionally, we have to ignore private fields. Extension functions can't access these, and accessing
// them is a bad idea anyway. Kotlin lets you expose get without exposing set. If you, by default, don't
// allow access to the getter, there's a high chance exposing it is a bad idea.
&& elements.any { getter -> getter.kind == ElementKind.METHOD // find methods
&& getter.simpleName.toString() ==
"get${name[0].toUpperCase().toString() + (if (name.length > 1) name.substring(1) else "")}" // that matches the getter name (by the standard convention)
&& Modifier.PUBLIC in getter.modifiers // that are marked public
}
} // Grab the variables
.map {
// Map the name now. Also supports later filtering
if (it.simpleName.endsWith("\$delegate")) {
// Support by lazy
it.simpleName.subSequence(0, it.simpleName.indexOf("$"))
} else it.simpleName
}
if (classVariables.isEmpty()) return; // Self-explanatory
val file = FileSpec.builder(pkg, className)
.addFunction(FunSpec.builder("iterator") // For automatic unpacking in a for loop
.receiver(element.asType().asTypeName().copy()) // Add it as an extension function of the class
.addStatement("return listOf(${classVariables.joinToString(", ")}).iterator()") // add the return statement. Create a list, push an iterator.
.addModifiers(KModifier.PUBLIC, KModifier.OPERATOR) // This needs to be public. Because it's an iterator, the function also needs the `operator` keyword
.build()
).build()
// Grab the generate directory.
val genDir = processingEnv.options["kapt.kotlin.generated"]!!
// Then write the file.
file.writeTo(File(genDir, "$pkg/${element.simpleName.replace("\\.kt".toRegex(), "")}Generated.kt"))
}
All of the relevant lines have comments explaining use, in case you're not familiar with what this does.
Finally, in order to get the processor to process, you need to register it. In the module for the generator, add a file called javax.annotation.processing.Processor under main/resources/META-INF/services. In there you write:
com.package.of.UnpackCodeGenerator
From here, you need to link it using compileOnly and kapt. If you added it as a module to your project, you can do:
kapt project(":ClassUnpacker")
compileOnly project(":ClassUnpacker")
Alternative source setup:
Like I mentioned earlier, I bundled this into a jar for convenience. It's under the same license as SO uses (CC-BY-SA 3.0), and it contains the exact same code as in the answer (although compiled into a single project).
If you want to use this one, just add the Jitpack repo:
repositories {
// Other repos here
maven { url 'https://jitpack.io' }
}
And hook it up with:
kapt 'com.github.LunarWatcher:KClassUnpacker:v1.0.1'
compileOnly "com.github.LunarWatcher:KClassUnpacker:v1.0.1"
Note that the version here may not be up to date: the up to date list of versions is available here. The code in the post still aims to reflect the repo, but versions aren't really important enough to edit every time.
Usage
Regardless of which way you ended up using to get the annotations, the usage is relatively easy:
#AutoUnpack data class ExampleDataClass(val x: String, val y: Int, var m: Boolean)
fun main(a: Array<String>) {
val cls = ExampleDataClass("example", 42, false)
for(field in cls) {
println(field)
}
}
This prints:
example
42
false
Now you have a reflection-less way of iterating fields.
Note that local testing has been done partially with IntelliJ, but IntelliJ doesn't seem to like me - I've had various failed builds where gradlew clean && gradlew build from a command line oddly works fine. I'm not sure whether this is a local problem, or if this is a general problem, but you might have some issues like this if you build from IntelliJ.
Also, you might get errors if the build fails. The IntelliJ linter builds on top of the build directory for some sources, so if the build fails and the file with the extension function isn't generated, that'll cause it to appear as an error. Building usually fixes this when I tested (with both modules and from Jitpack).
You'll also likely have to enable the annotation processor setting if you use Android Studio or IntelliJ.
here is another idea, that i came up with, but am not satisfied with...but it has some pros and cons:
pros:
adding/removing fields to/from the data class causes compiler errors at field-iteration sites
no boiler-plate code needed
cons:
won't work if default values are defined for arguments
declaration:
data class Memento(
val testType: TestTypeData,
val notes: String,
val examinationTime: MillisSinceEpoch?,
val administeredBy: String,
val signature: SignatureViewHolder.SignatureData,
val signerName: String,
val signerRole: SignerRole
) : Serializable
iterating through all fields (can use this directly at call sites, or apply the Visitor pattern, and use this in the accept method to call all the visit methods):
val iterateThroughAllMyFields: Memento = someValue
Memento(
testType = iterateThroughAllMyFields.testType.also { testType ->
// do something with testType
},
notes = iterateThroughAllMyFields.notes.also { notes ->
// do something with notes
},
examinationTime = iterateThroughAllMyFields.examinationTime.also { examinationTime ->
// do something with examinationTime
},
administeredBy = iterateThroughAllMyFields.administeredBy.also { administeredBy ->
// do something with administeredBy
},
signature = iterateThroughAllMyFields.signature.also { signature ->
// do something with signature
},
signerName = iterateThroughAllMyFields.signerName.also { signerName ->
// do something with signerName
},
signerRole = iterateThroughAllMyFields.signerRole.also { signerRole ->
// do something with signerRole
}
)

Programmatically execute Gatling tests

I want to use something like Cucumber JVM to drive performance tests written for Gatling.
Ideally the Cucumber features would somehow build a scenario dynamically - probably reusing predefined chain objects similar to the method described in the "Advanced Tutorial", e.g.
val scn = scenario("Scenario Name").exec(Search.search("foo"), Browse.browse, Edit.edit("foo", "bar")
I've looked at how the Maven plugin executes the scripts, and I've also seen mention of using an App trait but I can't find any documentation for the later and it strikes me that somebody else will have wanted to do this before...
Can anybody point (a Gatling noob) in the direction of some documentation or example code of how to achieve this?
EDIT 20150515
So to explain a little more:
I have created a trait which is intended to build up a sequence of, I think, ChainBuilders that are triggered by Cucumber steps:
trait GatlingDsl extends ScalaDsl with EN {
private val gatlingActions = new ArrayBuffer[GatlingBehaviour]
def withGatling(action: GatlingBehaviour): Unit = {
gatlingActions += action
}
}
A GatlingBehaviour would look something like:
object Google {
class Home extends GatlingBehaviour {
def execute: ChainBuilder =
exec(http("Google Home")
.get("/")
)
}
class Search extends GatlingBehaviour {...}
class FindResult extends GatlingBehaviour {...}
}
And inside the StepDef class:
class GoogleStepDefinitions extends GatlingDsl {
Given( """^the Google search page is displayed$""") { () =>
println("Loading www.google.com")
withGatling(Home())
}
When( """^I search for the term "(.*)"$""") { (searchTerm: String) =>
println("Searching for '" + searchTerm + "'...")
withGatling(Search(searchTerm))
}
Then( """^"(.*)" appears in the search results$""") { (expectedResult: String) =>
println("Found " + expectedResult)
withGatling(FindResult(expectedResult))
}
}
The idea being that I can then execute the whole sequence of actions via something like:
val scn = Scenario(cucumberScenario).exec(gatlingActions)
setup(scn.inject(atOnceUsers(1)).protocols(httpConf))
and then check the reports or catch an exception if the test fails, e.g. response time too long.
It seems that no matter how I use the 'exec' method it tries to instantly execute it there and then, not waiting for the scenario.
Also I don't know if this is the best approach to take, we'd like to build some reusable blocks for our Gatling tests that can be constructed via Cucumber's Given/When/Then style. Is there a better or already existing approach?
Sadly, it's not currently feasible to have Gatling directly start a Simulation instance.
Not that's it's not technically feasible, but you're just the first person to try to do this.
Currently, Gatling is usually in charge of compiling and can only be passed the name of the class to load, not an instance itself.
You can maybe start by forking io.gatling.app.Gatling and io.gatling.core.runner.Runner, and then provide a PR to support this new behavior. The former is the main entry point, and the latter the one can instanciate and run the simulation.
I recently ran into a similar situation, and did not want to fork gatling. And while this solved my immediate problem, it only partially solves what you are trying to do, but hopefully someone else will find this useful.
There is an alternative. Gatling is written in Java and Scala so you can call Gatling.main directly and pass it the arguments you need to run the Gatling Simulation you want. The problem is, the main explicitly calls System.exit so you have to also use a custom security manager to prevent it from actually exiting.
You need to know two things:
the class (with the full package) of the Simulation you want to run
example: com.package.your.Simulation1
the path where the binaries are compiled.
The code to run a Simulation:
protected void fire(String gatlingGun, String binaries){
SecurityManager sm = System.getSecurityManager();
System.setSecurityManager(new GatlingSecurityManager());
String[] args = {"--simulation", gatlingGun,
"--results-folder", "gatling-results",
"--binaries-folder", binaries};
try {
io.gatling.app.Gatling.main(args);
}catch(SecurityException se){
LOG.debug("gatling test finished.");
}
System.setSecurityManager(sm);
}
The simple security manager i used:
public class GatlingSecurityManager extends SecurityManager {
#Override
public void checkExit(int status){
throw new SecurityException("Tried to exit.");
}
#Override
public void checkPermission(Permission perm) {
return;
}
}
The problem is then getting the information you want out of the simulation after it has been run.