diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd9241..61b4bf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ Dropping a requirement of a major version of a dependency is a new contract. ## [Unreleased] [Unreleased]: https://github.com/atlassian/virtual-users/compare/release-3.10.0...master +### Added +- Support HttpClient based scenarios. + ## [3.10.0] - 2019-08-02 [3.10.0]: https://github.com/atlassian/virtual-users/compare/release-3.9.1...release-3.10.0 diff --git a/src/main/java/com/atlassian/performance/tools/virtualusers/lib/api/Scenario.java b/src/main/java/com/atlassian/performance/tools/virtualusers/lib/api/Scenario.java new file mode 100644 index 0000000..6aa11a5 --- /dev/null +++ b/src/main/java/com/atlassian/performance/tools/virtualusers/lib/api/Scenario.java @@ -0,0 +1,42 @@ +package com.atlassian.performance.tools.virtualusers.lib.api; + +import com.atlassian.performance.tools.jiraactions.api.action.Action; +import com.atlassian.performance.tools.jiraactions.api.measure.ActionMeter; +import com.atlassian.performance.tools.virtualusers.api.config.VirtualUserTarget; + +import java.util.List; + +/** + * IT doesn't need to be thread safe. Each VU will have own copy of the scenario and own Thread + */ +public abstract class Scenario { + + /** + * vu will use this constructor to create the scenario + * + * @param virtualUserTarget + * @param meter + */ + protected Scenario(VirtualUserTarget virtualUserTarget, ActionMeter meter) { + } + + /** + * The method will be called before VU starts executing actions + */ + public void before() { + + } + + /** + * The method will be called Once to setUp product instance + */ + public void setup() { + + } + + public abstract List getActions(); + + public void cleanUp() { + + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/LoadTest.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/LoadTest.kt index c5654b9..480f9ef 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/LoadTest.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/LoadTest.kt @@ -178,24 +178,23 @@ internal class LoadTest( userMemory: UserMemory, diagnostics: Diagnostics ): ExploratoryVirtualUser { - val scenarioAdapter = ScenarioAdapter(scenario) val maxOverallLoad = load.maxOverallLoad return ExploratoryVirtualUser( node = WebJiraNode(jira), nodeCounter = nodeCounter, - actions = scenarioAdapter.getActions( - jira = jira, - seededRandom = SeededRandom(random.random.nextLong()), - meter = meter + actions = scenario.getActions( + jira, + SeededRandom(random.random.nextLong()), + meter ), - setUpAction = scenarioAdapter.getSetupAction( - jira = jira, - meter = meter + setUpAction = scenario.getSetupAction( + jira, + meter ), - logInAction = scenarioAdapter.getLogInAction( - jira = jira, - meter = meter, - userMemory = userMemory + logInAction = scenario.getLogInAction( + jira, + meter, + userMemory ), maxLoad = maxOverallLoad / load.virtualUsers, diagnostics = diagnostics diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewExploratoryVirtualUser.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewExploratoryVirtualUser.kt new file mode 100644 index 0000000..b7dd080 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewExploratoryVirtualUser.kt @@ -0,0 +1,58 @@ +package com.atlassian.performance.tools.virtualusers + +import com.atlassian.performance.tools.jiraactions.api.action.Action +import com.atlassian.performance.tools.virtualusers.api.TemporalRate +import com.atlassian.performance.tools.virtualusers.collections.CircularIterator +import com.atlassian.performance.tools.virtualusers.measure.ApplicationNode +import com.atlassian.performance.tools.virtualusers.measure.JiraNodeCounter +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import java.time.Duration +import java.time.Instant.now +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Applies load on a Jira via page objects. Explores the instance to learn about data and choose pages to visit. + * Wanders preset Jira pages with different proportions of each page. Their order is random. + */ +internal class NewExploratoryVirtualUser( + private val node: ApplicationNode, + private val nodeCounter: JiraNodeCounter, + private val actions: Iterable, + private val maxLoad: TemporalRate +) { + private val logger: Logger = LogManager.getLogger(this::class.java) + + /** + * Repeats [actions] until [done] is `true`. + */ + fun applyLoad( + done: AtomicBoolean + ) { + logger.info("Applying load...") + nodeCounter.count(node) + val actionNames = actions.map { it.javaClass.simpleName } + logger.debug("Circling through $actionNames") // TODO Circling through [DiagnosableAction] :( + var actionsPerformed = 0.0 + val start = now() + for (action in CircularIterator(actions)) { + if (done.get()) { + logger.info("Done applying load") + break + } + try { + action.run() + actionsPerformed++ + val expectedTimeSoFar = maxLoad.scaleChange(actionsPerformed).time + val actualTimeSoFar = Duration.between(start, now()) + val extraTime = expectedTimeSoFar - actualTimeSoFar + if (extraTime > Duration.ZERO) { + Thread.sleep(extraTime.toMillis()) + } + } catch (e: Exception) { + logger.error("Failed to run $action, but we keep running", e) + } + } + } + +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadSegment.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadSegment.kt new file mode 100644 index 0000000..2040b19 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadSegment.kt @@ -0,0 +1,41 @@ +package com.atlassian.performance.tools.virtualusers + +import com.atlassian.performance.tools.virtualusers.lib.api.Scenario +import org.apache.logging.log4j.LogManager +import java.io.BufferedWriter +import java.time.Duration +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +internal class NewLoadSegment( + val scenario: Scenario, + val output: BufferedWriter, + val done: AtomicBoolean, + val id: UUID, + val index: Int +) : AutoCloseable { + + override fun close() { + done.set(true) + output.close() + val executor = Executors.newSingleThreadExecutor { + Thread(it) + .apply { name = "close-driver" } + .apply { isDaemon = true } + } + try { + executor + .submit { scenario.cleanUp() } + .get(DRIVER_CLOSE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS) + } catch (e: Exception) { + LOGGER.warn("Failed to close WebDriver", e) + } + } + + internal companion object { + private val LOGGER = LogManager.getLogger(this::class.java) + internal val DRIVER_CLOSE_TIMEOUT = Duration.ofSeconds(30) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadTest.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadTest.kt new file mode 100644 index 0000000..cbf2707 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/NewLoadTest.kt @@ -0,0 +1,186 @@ +package com.atlassian.performance.tools.virtualusers + +import com.atlassian.performance.tools.io.api.ensureDirectory +import com.atlassian.performance.tools.jiraactions.api.measure.ActionMeter +import com.atlassian.performance.tools.jiraactions.api.measure.output.AppendableActionMetricOutput +import com.atlassian.performance.tools.virtualusers.api.VirtualUserOptions +import com.atlassian.performance.tools.virtualusers.api.config.VirtualUserTarget +import com.atlassian.performance.tools.virtualusers.api.diagnostics.* +import com.atlassian.performance.tools.virtualusers.lib.api.Scenario +import com.atlassian.performance.tools.virtualusers.measure.ApplicationNode +import com.atlassian.performance.tools.virtualusers.measure.JiraNodeCounter +import com.google.common.util.concurrent.ThreadFactoryBuilder +import org.apache.logging.log4j.CloseableThreadContext +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import org.openqa.selenium.WebDriver +import org.openqa.selenium.remote.RemoteWebDriver +import java.io.BufferedWriter +import java.nio.file.Paths +import java.time.Duration +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A [load test](https://en.wikipedia.org/wiki/Load_testing). + */ +internal class NewLoadTest( + private val options: VirtualUserOptions +) { + private val logger: Logger = LogManager.getLogger(this::class.java) + private val behavior = options.behavior + private val target = options.target + private val workspace = Paths.get("test-results") + private val nodeCounter = JiraNodeCounter() + private val diagnosisPatience = DiagnosisPatience(Duration.ofSeconds(5)) + private val diagnosisLimit = DiagnosisLimit(behavior.diagnosticsLimit) + + + private fun createScenario(virtualUserTarget: VirtualUserTarget, actionMeter: ActionMeter): Scenario { + return behavior + .scenario + .getConstructor(VirtualUserTarget::class.java, ActionMeter::class.java) + .newInstance( + virtualUserTarget, + actionMeter + ) as Scenario + } + + private val load = behavior.load + + fun run() { + logger.info("Holding for ${load.hold}.") + Thread.sleep(load.hold.toMillis()) + workspace.toFile().ensureDirectory() + setUpJira() + applyLoad() + val nodesDump = workspace.resolve("nodes.csv") + nodesDump.toFile().bufferedWriter().use { + nodeCounter.dump(it) + } + logger.debug("Dumped node's counts to $nodesDump") + } + + private fun setUpJira() { + CloseableThreadContext.push("setup").use { + val createScenario = createScenario( + virtualUserTarget = target, + actionMeter = ActionMeter(virtualUser = UUID.randomUUID()) + ) + createScenario.setup() + createScenario.cleanUp() + } + } + + private fun applyLoad() { + val userCount = load.virtualUsers + val finish = load.ramp + load.flat + val loadPool = ThreadPoolExecutor( + userCount, + userCount, + 0L, + TimeUnit.MILLISECONDS, + LinkedBlockingQueue(), + ThreadFactoryBuilder().setNameFormat("virtual-user-%d").setDaemon(true).build() + ) + logger.info("Segmenting load across $userCount VUs") + val segments = (0..userCount).map { index -> + segmentLoad(index + 1) + } + logger.info("Load segmented") + segments.forEach { loadPool.submit { applyLoad(it) } } + Thread.sleep(finish.toMillis()) + close(segments) + } + + private fun segmentLoad( + index: Int + ): NewLoadSegment { + val uuid = UUID.randomUUID() + val output = output(uuid) + val scenario = createScenario(options.target, + ActionMeter( + virtualUser = uuid, + output = AppendableActionMetricOutput(output) + ) + ) + + return NewLoadSegment( + scenario = scenario, + output = output, + done = AtomicBoolean(false), + id = uuid, + index = index + ) + } + + private fun output(uuid: UUID): BufferedWriter { + return workspace + .resolve(uuid.toString()) + .toFile() + .ensureDirectory() + .resolve("action-metrics.jpt") + .bufferedWriter() + } + + private fun applyLoad( + segment: NewLoadSegment + ) { + CloseableThreadContext.push("applying load #${segment.id}").use { + val rampUpWait = load.rampInterval.multipliedBy(segment.index.toLong()) + logger.info("Waiting for $rampUpWait") + Thread.sleep(rampUpWait.toMillis()) + val virtualUser = createVirtualUser(segment) + segment.scenario.before() + virtualUser.applyLoad(segment.done) + } + } + + private fun createVirtualUser( + segment: NewLoadSegment + ): NewExploratoryVirtualUser { + val maxOverallLoad = load.maxOverallLoad + return NewExploratoryVirtualUser( + node = object : ApplicationNode { + override fun identify(): String = "todo??" + }, + nodeCounter = nodeCounter, + actions = segment.scenario.actions, + maxLoad = maxOverallLoad / load.virtualUsers + ) + } + + private fun close( + segments: List + ) { + logger.info("Closing segments") + val closePool = Executors.newCachedThreadPool { Thread(it, "close-segment") } + segments + .map { closePool.submit { it.close() } } + .forEach { it.get() } + logger.info("Segments closed") + closePool.shutdown() + } + + private fun RemoteWebDriver.toDiagnosableDriver(): DiagnosableDriver { + return DiagnosableDriver( + this, + LimitedDiagnostics( + ImpatientDiagnostics( + WebDriverDiagnostics(this), + diagnosisPatience + ), + diagnosisLimit + ) + ) + } + + internal data class DiagnosableDriver( + val driver: WebDriver, + val diagnostics: Diagnostics + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapter.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapter.kt deleted file mode 100644 index 5f00389..0000000 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapter.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.atlassian.performance.tools.virtualusers - -import com.atlassian.performance.tools.jiraactions.api.SeededRandom -import com.atlassian.performance.tools.jiraactions.api.WebJira -import com.atlassian.performance.tools.jiraactions.api.action.Action -import com.atlassian.performance.tools.jiraactions.api.action.LogInAction -import com.atlassian.performance.tools.jiraactions.api.action.SetUpAction -import com.atlassian.performance.tools.jiraactions.api.measure.ActionMeter -import com.atlassian.performance.tools.jiraactions.api.memories.UserMemory -import com.atlassian.performance.tools.jiraactions.api.scenario.Scenario - -/** - * Compatibility layer to avoid MAJOR version bump, because of a transitive dependency bump. - * Adapter avoids to require `jira-actions:3.x` at compile time. We can remove the class, as soon we stop - * to support `jira-actions:2.x`. - */ -internal class ScenarioAdapter( - private val scenario: Scenario -) { - fun getActions(jira: WebJira, seededRandom: SeededRandom, meter: ActionMeter): List { - return scenario.getActions(jira, seededRandom, meter) - } - - fun getLogInAction(jira: WebJira, meter: ActionMeter, userMemory: UserMemory): Action { - return if (isScenario3Compatible()) { - val getLogInActionMethod = scenario::class.java.getMethod("getLogInAction", WebJira::class.java, ActionMeter::class.java, UserMemory::class.java) - getLogInActionMethod(scenario, jira, meter, userMemory) as Action - } else { - LogInAction( - jira = jira, - meter = meter, - userMemory = userMemory - ) - } - } - - fun getSetupAction(jira: WebJira, meter: ActionMeter): Action { - return if (isScenario3Compatible()) { - val getSetupActionMethod = scenario::class.java.getMethod("getSetupAction", WebJira::class.java, ActionMeter::class.java) - getSetupActionMethod(scenario, jira, meter) as Action - } else { - SetUpAction( - jira = jira, - meter = meter - ) - } - } - - private fun isScenario3Compatible(): Boolean { - val methods = scenario::class - .java - .methods - .map { it.name } - - return methods.contains("getSetupAction") && methods.contains("getLogInAction") - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPoint.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPoint.kt index 276ee7b..593f5c4 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPoint.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPoint.kt @@ -1,6 +1,7 @@ package com.atlassian.performance.tools.virtualusers.api import com.atlassian.performance.tools.virtualusers.LoadTest +import com.atlassian.performance.tools.virtualusers.NewLoadTest import com.atlassian.performance.tools.virtualusers.logs.LogConfiguration import com.atlassian.performance.tools.virtualusers.logs.LogConfigurationFactory import org.apache.logging.log4j.LogManager @@ -41,7 +42,14 @@ class Application { if (options.help) { options.printHelp() } else { - LoadTest(options).run() + val scenarioPackage = options.behavior.scenario.packageName + if (scenarioPackage == "com.atlassian.performance.tools.virtualusers.lib.api") { + NewLoadTest(options).run() + } else if (scenarioPackage == "com.atlassian.performance.tools.jiraactions.api.scenario") { + LoadTest(options).run() + } else { + throw Exception("not implemented") + } } } diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/VirtualUserOptions.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/VirtualUserOptions.kt index 0d88c24..6f36b2d 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/VirtualUserOptions.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/VirtualUserOptions.kt @@ -49,7 +49,7 @@ class VirtualUserOptions( get() = behavior.load @Deprecated(deprecatedGetterMessage) - val scenario: Class + val scenario: Class<*> get() = behavior.scenario @Deprecated(deprecatedGetterMessage) @@ -78,7 +78,7 @@ class VirtualUserOptions( adminLogin: String = "admin", adminPassword: String = "admin", virtualUserLoad: VirtualUserLoad = VirtualUserLoad(), - scenario: Class = JiraSoftwareScenario::class.java, + scenario: Class<*> = JiraSoftwareScenario::class.java, seed: Long = Random().nextLong(), diagnosticsLimit: Int = 64, allowInsecureConnections: Boolean = false @@ -106,7 +106,7 @@ class VirtualUserOptions( adminLogin: String, adminPassword: String, virtualUserLoad: VirtualUserLoad, - scenario: Class, + scenario: Class<*>, seed: Long, diagnosticsLimit: Int, browser: Class @@ -416,11 +416,9 @@ class VirtualUserOptions( ) } - private fun getScenario(commandLine: CommandLine): Class { + private fun getScenario(commandLine: CommandLine): Class<*> { val scenario = commandLine.getOptionValue(scenarioParameter) - val scenarioClass = Class.forName(scenario) - val scenarioConstructor = scenarioClass.getConstructor() - return (scenarioConstructor.newInstance() as Scenario)::class.java + return Class.forName(scenario) } private fun getBrowser(commandLine: CommandLine): Class { diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserBehavior.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserBehavior.kt index ff2ba2f..c5d9d74 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserBehavior.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserBehavior.kt @@ -19,7 +19,7 @@ class VirtualUserBehavior private constructor( message = "There should be no need to display help from Java API. Read the Javadoc or sources instead." ) internal val help: Boolean, - internal val scenario: Class, + internal val scenario: Class<*>, val load: VirtualUserLoad, val maxOverhead: Duration, internal val seed: Long, @@ -35,7 +35,7 @@ class VirtualUserBehavior private constructor( ) constructor( help: Boolean, - scenario: Class, + scenario: Class<*>, load: VirtualUserLoad, seed: Long, diagnosticsLimit: Int, @@ -60,7 +60,7 @@ class VirtualUserBehavior private constructor( @Suppress("DEPRECATION") constructor( help: Boolean, - scenario: Class, + scenario: Class<*>, load: VirtualUserLoad, seed: Long, diagnosticsLimit: Int, @@ -80,7 +80,7 @@ class VirtualUserBehavior private constructor( message = "Use the VirtualUserBehavior.Builder instead" ) constructor( - scenario: Class, + scenario: Class<*>, load: VirtualUserLoad, seed: Long, diagnosticsLimit: Int, @@ -105,8 +105,10 @@ class VirtualUserBehavior private constructor( load: VirtualUserLoad ): VirtualUserBehavior = Builder(this).load(load).build() + @Deprecated("TODO - can we provide more type safe builder?") + // TODO detect when the new scenario is used with parameters that are no longer supported in new Scenario class Builder( - private var scenario: Class + private var scenario: Class<*> ) { private var load: VirtualUserLoad = VirtualUserLoad.Builder().build() private var maxOverhead: Duration = Duration.ofMinutes(5) @@ -122,6 +124,7 @@ class VirtualUserBehavior private constructor( fun maxOverhead(maxOverhead: Duration) = apply { this.maxOverhead = maxOverhead } fun seed(seed: Long) = apply { this.seed = seed } fun diagnosticsLimit(diagnosticsLimit: Int) = apply { this.diagnosticsLimit = diagnosticsLimit } + @Deprecated("TODO -> new API doesn't use it") fun browser(browser: Class) = apply { this.browser = browser } fun logging(logging: Class) = apply { this.logging = logging } fun skipSetup(skipSetup: Boolean) = apply { this.skipSetup = skipSetup } diff --git a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserTarget.kt b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserTarget.kt index 8f28b27..b59b2b3 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserTarget.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/virtualusers/api/config/VirtualUserTarget.kt @@ -3,7 +3,7 @@ package com.atlassian.performance.tools.virtualusers.api.config import java.net.URI class VirtualUserTarget( - internal val webApplication: URI, - internal val userName: String, - internal val password: String -) \ No newline at end of file + val webApplication: URI, + val userName: String, + val password: String +) diff --git a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapterTest.kt b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapterTest.kt deleted file mode 100644 index ac6397d..0000000 --- a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/ScenarioAdapterTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.atlassian.performance.tools.virtualusers - -import com.atlassian.performance.tools.jiraactions.api.SeededRandom -import com.atlassian.performance.tools.jiraactions.api.WebJira -import com.atlassian.performance.tools.jiraactions.api.measure.ActionMeter -import com.atlassian.performance.tools.jiraactions.api.memories.adaptive.AdaptiveUserMemory -import com.atlassian.performance.tools.jirasoftwareactions.api.JiraSoftwareScenario -import com.atlassian.performance.tools.virtualusers.api.diagnostics.DriverMock -import org.junit.Assert -import org.junit.Test -import java.net.URI -import java.util.* - -class ScenarioAdapterTest { - private val scenarioAdapter = ScenarioAdapter(JiraSoftwareScenario()) - private val webJira = WebJira( - driver = DriverMock(), - base = URI("http://localhost"), - adminPassword = "admin" - ) - private val meter = ActionMeter( - virtualUser = UUID.randomUUID() - ) - private val userMemory = AdaptiveUserMemory(SeededRandom()) - - @Test - fun shouldReturnLogInAction() { - val logInAction = scenarioAdapter.getLogInAction(webJira, meter, userMemory) - - Assert.assertNotEquals(logInAction, null) - } - - @Test - fun shouldReturnSetupAction() { - val setupAction = scenarioAdapter.getSetupAction(webJira, meter) - - Assert.assertNotEquals(setupAction, null) - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/SimpleWebdriverScenario.kt b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/SimpleWebdriverScenario.kt new file mode 100644 index 0000000..02c47a6 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/SimpleWebdriverScenario.kt @@ -0,0 +1,84 @@ +package com.atlassian.performance.tools.virtualusers + +import com.atlassian.performance.tools.jiraactions.api.ActionType +import com.atlassian.performance.tools.jiraactions.api.WebJira +import com.atlassian.performance.tools.jiraactions.api.action.Action +import com.atlassian.performance.tools.jiraactions.api.measure.ActionMeter +import com.atlassian.performance.tools.jiraactions.api.memories.User +import com.atlassian.performance.tools.virtualusers.api.browsers.CloseableRemoteWebDriver +import com.atlassian.performance.tools.virtualusers.api.browsers.HeadlessChromeBrowser +import com.atlassian.performance.tools.virtualusers.api.config.VirtualUserTarget +import com.atlassian.performance.tools.virtualusers.api.diagnostics.WebDriverDiagnostics +import com.atlassian.performance.tools.virtualusers.lib.api.Scenario +import java.util.concurrent.atomic.AtomicInteger + +// TODO show that RTE can be disabled via setup +class SimpleWebdriverScenario( + virtualUserTarget: VirtualUserTarget, + private val meter: ActionMeter +) : Scenario( + virtualUserTarget, meter +) { + private val driver: CloseableRemoteWebDriver = HeadlessChromeBrowser().start() + private val jira: WebJira = WebJira( + driver = driver.getDriver(), + base = virtualUserTarget.webApplication, + adminPassword = "admin" + ) + + override fun before() { //todo don't use hardcoded credentials + jira.goToLogin().logIn(User( + "admin", + "admin" + )) + } + + override fun cleanUp() { + driver.close() + } + + override fun getActions(): List { + val actions = listOf(HardcodedViewIssueAction(meter, jira)) + val diagnostics = WebDriverDiagnostics(driver.getDriver()) + return actions.map { action -> DiagnosableAction(action, diagnostics, 10) } + } + + + class HardcodedViewIssueAction( + private val meter: ActionMeter, + private val jira: WebJira + ) : Action { + private val viewIssueAction = ActionType("View Issue") { Unit } + + override fun run() { + meter.measure(viewIssueAction) { + jira.goToIssue("DSEI-1").waitForSummary() + } + } + } + + class DiagnosableAction( // should be available in API? + private val action: Action, + private val diagnostics: WebDriverDiagnostics, + limit: Int = Int.MAX_VALUE + ) : Action { + companion object { + private val limitCounter = AtomicInteger(Int.MAX_VALUE) + } + + init { + limitCounter.compareAndSet(Int.MAX_VALUE, limit) + } + + override fun run() { + try { + action.run() + } catch (e: Exception) { + if (limitCounter.getAndDecrement() > 0) { + diagnostics.diagnose(e) + } + } + } + } + +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPointIT.kt b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPointIT.kt index 29e489f..2e3c06b 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPointIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/EntryPointIT.kt @@ -31,4 +31,4 @@ class EntryPointIT { )) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/NewScenarioApiSupportIT.kt b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/NewScenarioApiSupportIT.kt new file mode 100644 index 0000000..45a5547 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/virtualusers/api/NewScenarioApiSupportIT.kt @@ -0,0 +1,37 @@ +package com.atlassian.performance.tools.virtualusers.api + +import com.atlassian.performance.tools.virtualusers.NewLoadTest +import com.atlassian.performance.tools.virtualusers.api.config.VirtualUserBehavior +import com.atlassian.performance.tools.virtualusers.api.config.VirtualUserTarget +import com.atlassian.performance.tools.virtualusers.SimpleWebdriverScenario +import org.junit.Test +import java.net.URI +import java.time.Duration + +class NewScenarioApiSupportIT { + + @Test + fun shouldConsumeHttpClientBasedScenario() { + val loadTest = NewLoadTest( + VirtualUserOptions( + target = VirtualUserTarget( + webApplication = URI("http://localhost:8090/jira/"), + userName = "admin", + password = "admin" + ), + behavior = VirtualUserBehavior.Builder( + SimpleWebdriverScenario::class.java + ).load( + VirtualUserLoad.Builder() + .virtualUsers(1) + .ramp(Duration.ZERO) + .flat(Duration.ofSeconds(120)) + .build() + ).build() + ) + ) + + loadTest.run() + } + +}