Skip to content

Commit

Permalink
chore: Rework Simulations scan
Browse files Browse the repository at this point in the history
Motivation:

The existing file based filter is bad, in particular as it requires naming the Simulation classes as XXXSimulation.

Modification:

use standard scan mechanism
don't use filters for the user defined simulation class name (gatlingRun-FQCN)
replace file path filters matching source files with ant based filters on class names
  • Loading branch information
slandelle committed Feb 15, 2024
1 parent e2053be commit f9cd77f
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 115 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ repositories {

dependencies {
implementation "io.gatling:gatling-enterprise-plugin-commons:1.9.0-M8"
implementation "org.apache.ant:ant:1.10.11"
implementation "org.codehaus.plexus:plexus-utils:4.0.0"
constraints {
implementation('com.fasterxml.jackson.core:jackson-databind') {
version {
Expand Down
8 changes: 1 addition & 7 deletions src/main/groovy/io/gatling/gradle/GatlingPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,7 @@ final class GatlingPlugin implements Plugin<Project> {
group = "Gatling"

if (simulationFQN) {
simulations = {
include(
"${simulationFQN.replace('.', '/')}.java",
"${simulationFQN.replace('.', '/')}.scala",
"${simulationFQN.replace('.', '/')}.kt"
)
}
simulationClass = simulationFQN
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class GatlingPluginExtension {
private String systemPropsString
private Map<String, String> environmentVariables
private String environmentVariablesString
private URL url = new URL("https://cloud.gatling.io")
private URL url = URI.create("https://cloud.gatling.io").toURL()
private String simulationClass
private boolean batchMode
private boolean waitForRunEnd
Expand All @@ -54,7 +54,7 @@ class GatlingPluginExtension {
}

def setUrl(String url) {
this.url = new URL(url)
this.url = URI.create(url).toURL()
}

def url(String url) {
Expand Down Expand Up @@ -303,8 +303,6 @@ class GatlingPluginExtension {

static final String SCALA_VERSION = '2.13.12'

static final Closure DEFAULT_SIMULATIONS = { include("**/*Simulation*.java", "**/*Simulation*.kt", "**/*Simulation*.scala") }

static final String DEFAULT_LOG_LEVEL = "WARN"
static final LogHttp DEFAULT_LOG_HTTP = LogHttp.NONE

Expand All @@ -327,7 +325,8 @@ class GatlingPluginExtension {
gatlingVersion = toolVersion
}

Closure simulations = DEFAULT_SIMULATIONS
List<String> includes = List.of()
List<String> excludes = List.of()

Boolean includeMainOutput = true
Boolean includeTestOutput = true
Expand Down
12 changes: 8 additions & 4 deletions src/main/groovy/io/gatling/gradle/GatlingRunTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class GatlingRunTask extends DefaultTask {
Map environment = [:]

@Internal
Closure simulations
String simulationClass = null

@OutputDirectory
File gatlingReportDir = project.file("${project.reportsDir}/gatling")
Expand All @@ -52,7 +52,7 @@ class GatlingRunTask extends DefaultTask {
void gatlingRun() {
def gatlingExt = project.extensions.getByType(GatlingPluginExtension)

Map<String, ExecResult> results = simulationFilesToFQN().collectEntries { String simulationClass ->
Map<String, ExecResult> results = simulationClasses().collectEntries { String simulationClass ->
[(simulationClass): project.javaexec({ JavaExecSpec exec ->
exec.mainClass.set(GatlingPluginExtension.GATLING_MAIN_CLASS)
exec.classpath = project.configurations.gatlingRuntimeClasspath
Expand Down Expand Up @@ -84,7 +84,11 @@ class GatlingRunTask extends DefaultTask {
}
}

Iterable<String> simulationFilesToFQN() {
return SimulationFilesUtils.resolveSimulations(project, this.simulations)
List<String> simulationClasses() {
def classpath = project.configurations.gatlingRuntimeClasspath.getFiles()
def includes = this.simulationClass ? List.of(simulationClass) : project.gatling.includes
def excludes = this.simulationClass ? List.of() : project.gatling.excludes

return SimulationFilesUtils.resolveSimulations(classpath, includes, excludes)
}
}
71 changes: 23 additions & 48 deletions src/main/groovy/io/gatling/gradle/SimulationFilesUtils.groovy
Original file line number Diff line number Diff line change
@@ -1,64 +1,39 @@
package io.gatling.gradle

import org.gradle.api.Project
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.InputFiles
import io.gatling.scanner.SimulationScanner
import org.codehaus.plexus.util.SelectorUtils

import java.nio.file.Path
import java.nio.file.Paths
import java.util.stream.Collectors

final class SimulationFilesUtils {

static Iterable<String> resolveSimulations(Project project, Closure simulations) {
def javaSrcDirs = project.sourceSets.gatling.java.srcDirs.collect { Paths.get(it.absolutePath) }
def javaFiles = getJavaSimulationSources(project, simulations).collect { Paths.get(it.absolutePath) }
static List<String> resolveSimulations(Collection<File> classpath, List<String> includes, List<String> excludes) {
def dependencies = new ArrayList<File>()
def classDirectories = new ArrayList<File>()

def javaFQNs = javaFiles.collect { Path srcFile ->
javaSrcDirs.find { srcFile.startsWith(it) }.relativize(srcFile).join(".") - ".java"
}

List<String> kotlinFQNs
if (project.sourceSets.gatling.hasProperty("kotlin")) {
def kotlinSrcDirs = project.sourceSets.gatling.kotlin.srcDirs.collect { Paths.get(it.absolutePath) }
def kotlinFiles = getKotlinSimulationSources(project, simulations).collect { Paths.get(it.absolutePath) }

kotlinFQNs = kotlinFiles.collect { Path srcFile ->
kotlinSrcDirs.find { srcFile.startsWith(it) }.relativize(srcFile).join(".") - ".kt"
classpath.forEach {file ->
if (file.isDirectory()) {
classDirectories.add(file)
} else if (file.isFile()) {
dependencies.add(file)
}
} else {
kotlinFQNs = []
}

def scalaSrcDirs = project.sourceSets.gatling.scala.srcDirs.collect { Paths.get(it.absolutePath) }
def scalaFiles = getScalaSimulationSources(project, simulations).collect { Paths.get(it.absolutePath) }

def scalaFQNs = scalaFiles.collect { Path srcFile ->
scalaSrcDirs.find { srcFile.startsWith(it) }.relativize(srcFile).join(".") - ".scala"
}

return javaFQNs + kotlinFQNs + scalaFQNs
}
def allSimulationClasses = SimulationScanner.scan(dependencies, classDirectories).simulationClasses

@InputFiles
static FileTree getJavaSimulationSources(Project project, Closure simulations) {
def simulationFilter = simulations ?: project.gatling.simulations
return project.sourceSets.gatling.java.matching(simulationFilter)
}
List<String> filteredSimulationClasses = allSimulationClasses.stream()
.filter(
className -> {
boolean isIncluded = includes.isEmpty() || match(includes, className)
boolean isExcluded = !excludes.isEmpty() && match(excludes, className)
return isIncluded && !isExcluded
})
.collect(Collectors.toList())

@InputFiles
static FileTree getKotlinSimulationSources(Project project, Closure simulations) {
if (project.sourceSets.gatling.hasProperty("kotlin")) {
def simulationFilter = simulations ?: project.gatling.simulations
return project.sourceSets.gatling.kotlin.matching(simulationFilter)
} else {
return project.files().asFileTree
}
return filteredSimulationClasses
}

@InputFiles
static FileTree getScalaSimulationSources(Project project, Closure simulations) {
def simulationFilter = simulations ?: project.gatling.simulations
return project.sourceSets.gatling.scala.matching(simulationFilter)
private static boolean match(List<String> patterns, String string) {
return patterns.any {it != null && SelectorUtils.match(it, string) }
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package unit
package func

import helper.GatlingFuncSpec
import io.gatling.gradle.GatlingPluginExtension
import helper.GatlingUnitSpec
import io.gatling.gradle.GatlingRunTask
import org.codehaus.groovy.runtime.typehandling.GroovyCastException
import spock.lang.Ignore
import spock.lang.Unroll

import static io.gatling.gradle.GatlingPlugin.GATLING_RUN_TASK_NAME
import static io.gatling.gradle.GatlingPluginExtension.SCALA_SIMULATIONS_DIR
import static org.apache.commons.io.FileUtils.copyFileToDirectory
import static org.apache.commons.io.FileUtils.moveFileToDirectory

class GatlingRunTaskTest extends GatlingUnitSpec {
@Ignore
class GatlingRunTaskTest extends GatlingFuncSpec {

GatlingRunTask theTask

Expand All @@ -21,7 +22,7 @@ class GatlingRunTaskTest extends GatlingUnitSpec {

def "should resolve simulations using default filter"() {
when:
def gatlingRunSimulations = theTask.simulationFilesToFQN()
def gatlingRunSimulations = theTask.simulationClasses()
then:
gatlingRunSimulations.size() == 2
and:
Expand All @@ -32,9 +33,9 @@ class GatlingRunTaskTest extends GatlingUnitSpec {

def "should resolve simulations using custom filter"() {
given:
project.gatling.simulations = { include "**/*AdvancedSimulation*" }
project.gatling.includes = [ "**.*AdvancedSimulation*" ]
when:
def gatlingRunSimulations = theTask.simulationFilesToFQN()
def gatlingRunSimulations = theTask.simulationClasses()
then:
gatlingRunSimulations == ["computerdatabase.advanced.AdvancedSimulationStep03"]
}
Expand All @@ -43,17 +44,17 @@ class GatlingRunTaskTest extends GatlingUnitSpec {
given:
project.gatling.simulations = GatlingPluginExtension.DEFAULT_SIMULATIONS
and:
project.gatlingRun.simulations = { include "**/*AdvancedSimulation*" }
project.gatlingRun.includes = [ "**.*AdvancedSimulation*" ]
when:
def gatlingRunSimulations = theTask.simulationFilesToFQN()
def gatlingRunSimulations = theTask.simulationClasses()
then:
gatlingRunSimulations == ["computerdatabase.advanced.AdvancedSimulationStep03"]
}

@Unroll
def "should fail if extension filter is not a closure but #val"() {
def "should fail if extension filter is not a list but #val"() {
when:
project.gatling.simulations = val
project.gatling.includes = val
then:
def ex = thrown(GroovyCastException)
ex.message.contains(val.toString())
Expand All @@ -62,9 +63,9 @@ class GatlingRunTaskTest extends GatlingUnitSpec {
}

@Unroll
def "should fail if gatlingRun filter not a closure but #val"() {
def "should fail if gatlingRun filter not a list but #val"() {
when:
project.gatlingRun.simulations = val
project.gatlingRun.includes = val
then:
def ex = thrown(GroovyCastException)
ex.message.contains(val.toString())
Expand All @@ -81,13 +82,13 @@ class GatlingRunTaskTest extends GatlingUnitSpec {
gatling.scala.srcDirs = [overridenSrc]
}
then:
theTask.simulationFilesToFQN().size() == 0
theTask.simulationClasses().isEmpty()

when: 'put simulations into overridden source dir'
copyFileToDirectory(new File(projectDir.root, "${SCALA_SIMULATIONS_DIR}/computerdatabase/BasicSimulation.scala"),
copyFileToDirectory(new File(projectDir.root, "${GatlingPluginExtension.SCALA_SIMULATIONS_DIR}/computerdatabase/BasicSimulation.scala"),
new File(projectDir.root, "$overridenSrc/computerdatabase"))
then:
theTask.simulationFilesToFQN() == ["computerdatabase.BasicSimulation"]
theTask.simulationClasses() == ["computerdatabase.BasicSimulation"]
}

def "should extend simulations dirs via sourceSet"() {
Expand All @@ -99,53 +100,45 @@ class GatlingRunTaskTest extends GatlingUnitSpec {
gatling.scala.srcDir overridenSrc
}
then:
theTask.simulationFilesToFQN().size() == 2
theTask.simulationClasses().size() == 2

when: "hide one simulation"
moveFileToDirectory(new File(projectDir.root, "${SCALA_SIMULATIONS_DIR}/computerdatabase/BasicSimulation.scala"),
moveFileToDirectory(new File(projectDir.root, "${GatlingPluginExtension.SCALA_SIMULATIONS_DIR}/computerdatabase/BasicSimulation.scala"),
projectDir.root, true)
then:
theTask.simulationFilesToFQN() == ["computerdatabase.advanced.AdvancedSimulationStep03"]
theTask.simulationClasses() == ["computerdatabase.advanced.AdvancedSimulationStep03"]

when: 'move simulation back to overridden source dir'
moveFileToDirectory(new File(projectDir.root, "BasicSimulation.scala"), new File(projectDir.root, "$overridenSrc/computerdatabase"), true)
then:
theTask.simulationFilesToFQN().size() == 2
theTask.simulationClasses().size() == 2
}

def "should not find missing simulations via gatling extension"() {
when: 'default src layout'
project.gatling.simulations = {
include "computerdatabase/BasicSimulation.scala"
include "some.missing.file"
}
project.gatling.includes = [ "computerdatabase.BasicSimulation", "some.missing.file" ]
then:
theTask.simulationFilesToFQN() == ["computerdatabase.BasicSimulation"]
theTask.simulationClasses() == ["computerdatabase.BasicSimulation"]

when: 'custom src layout'
project.sourceSets {
gatling.scala.srcDirs = ["missing/gatling"]
}
then:
theTask.simulationFilesToFQN().size() == 0
theTask.simulationClasses().isEmpty()
}

def "should not find missing simulations via gatlingRun"() {
when: 'default src layout'
project.gatlingRun.simulations = {
include "computerdatabase/BasicSimulation.scala"
include "some.missing.file"
}
project.gatlingRun.includes = [ "computerdatabase.BasicSimulation", "some.missing.file" ]
then:
theTask.simulationFilesToFQN() == ["computerdatabase.BasicSimulation"]
theTask.simulationClasses() == ["computerdatabase.BasicSimulation"]

when: 'custom src layout'
project.sourceSets {
gatling.scala.srcDirs = ["missing/gatling"]
}
then:
theTask.simulationFilesToFQN().size() == 0
theTask.simulationClasses().isEmpty()
}


}
15 changes: 3 additions & 12 deletions src/test/groovy/func/WhenRunFailingSimulationSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ class WhenRunFailingSimulationSpec extends GatlingFuncSpec {
given:
buildFile << """
gatling {
simulations = {
include 'computerdatabase/AFailedSimulation.scala'
include 'computerdatabase/BasicSimulation.scala'
}
includes = ['computerdatabase.AFailedSimulation', 'computerdatabase.BasicSimulation']
}
"""
and: "add incorrect simulation"
Expand Down Expand Up @@ -58,10 +55,7 @@ class AFailedSimulation extends Simulation {
given:
buildFile << """
gatling {
simulations = {
include 'computerdatabase/AFailedSimulation.scala'
include 'computerdatabase/BasicSimulation.scala'
}
includes = ['computerdatabase.AFailedSimulation', 'computerdatabase.BasicSimulation']
}
"""
and: "add incorrect simulation"
Expand Down Expand Up @@ -92,10 +86,7 @@ class AFailedSimulation extends Simulation {
given:
buildFile << """
gatling {
simulations = {
include 'computerdatabase/BasicSimulation.scala'
include 'computerdatabase/AFailedSimulation.scala'
}
includes = ['computerdatabase.BasicSimulation', 'computerdatabase.AFailedSimulation']
}
"""
and: "add incorrect simulation"
Expand Down
2 changes: 1 addition & 1 deletion src/test/groovy/func/WhenRunSimulationSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ gatling.charting.noReports = true
given:
prepareTest()
buildFile << """
gatling { simulations = { include 'computerdatabase/BasicSimulation.scala' } }
gatling { includes = ['computerdatabase.BasicSimulation'] }
"""
when: '1st time'
BuildResult result = executeGradle("$GATLING_RUN_TASK_NAME")
Expand Down
1 change: 0 additions & 1 deletion src/test/groovy/helper/GatlingUnitSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ abstract class GatlingUnitSpec extends GatlingSpec {

gatlingExt = project.extensions.getByType(GatlingPluginExtension)
}

}
Loading

0 comments on commit f9cd77f

Please sign in to comment.