Skip to content

DSL for programmatic test plan generation#678

Merged
vlsi merged 24 commits into
apache:masterfrom
vlsi:jmeter-dsl
Jun 15, 2023
Merged

DSL for programmatic test plan generation#678
vlsi merged 24 commits into
apache:masterfrom
vlsi:jmeter-dsl

Conversation

@vlsi

@vlsi vlsi commented Nov 23, 2021

Copy link
Copy Markdown
Collaborator

Description

See https://lists.apache.org/thread/52o722d7npsjj99m4k2zdz44mstl02lg

TODO

  • Should there be an intermediate test plan representation or should the current ListedHashTree and other classes represent the tree? I incline we should create a separate set of classes to avoid leaking unwanted methods to the end-user. For instance, methods like #sample(), #interrupt() do not seem to make sense during test plan generation.
  • Should DSL functions return objects or should they be no-result functions?
  • How should $ be escaped? (see https://youtrack.jetbrains.com/issue/KT-2425, https://lists.apache.org/thread/717sm4k8kx6ojmkq0j1w0fhkv41bkm8d)
  • Should the DSL incline to name-less elements or name-full elements? JMeter GUI requires names for all elements, including if and pauses, however, regular programming languages and DSLs do not require names for things like if, for, etc. Of course, the transaction controller should have a name, but is the name required for httpSampler?
  • Should there be .children() or something like that API to access all the children elements of the currently built node?

Motivation and Context

(in alphabetical order)
https://aliesbelik.github.io/awesome-jmeter/#dsl
https://github.com/abstracta/jmeter-java-dsl
https://github.com/anasoid/jmeter-as-code
https://github.com/smicyk/groovy-jmeter

anasoid/jmeter-as-code#133

https://github.com/Kotlin/kotlinx.html/wiki/Micro-templating-and-DSL-customizing

Screenshots (if appropriate):

Copy Code action in JMeter UI
org.apache.jmeter.threads.ThreadGroup::class {
    props {
        it[guiClass] = "org.apache.jmeter.threads.gui.ThreadGroupGui"
        it[name] = "Thread Group"
        it[mainController] = org.apache.jmeter.control.LoopController().apply {
            props {
                it[name] = "Loop Controller"
                it[guiClass] = "org.apache.jmeter.control.gui.LoopControlPanel"
                it[testClass] = "org.apache.jmeter.control.LoopController"
                it[loops] = "1"
            }
        }
        it[numThreads] = "1"
        it[rampTime] = "1"
    }

    org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy::class {
        props {
            it[arguments] = org.apache.jmeter.config.Arguments().apply {
                props {
                    it[arguments] = listOf(
                        org.apache.jmeter.protocol.http.util.HTTPArgument().apply {
                            props {
                                it[value] = "World"
                                it[metadata] = "="
                                it[useEquals] = true
                                it[argumentName] = "user"
                            }
                        },
                        org.apache.jmeter.protocol.http.util.HTTPArgument().apply {
                            props {
                                it[alwaysEncode] = true
                                it[value] = "asdfa"
                                it[metadata] = "="
                                it[useEquals] = true
                                it[argumentName] = "test"
                            }
                        },
                    )
                    it[name] = "User Defined Variables"
                    it[guiClass] = "org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel"
                    it[testClass] = "org.apache.jmeter.config.Arguments"
                }
            }
            it[domain] = "example.com"
            it[path] = "/api/v1/hello"
            it[method] = "GET"
            it[followRedirects] = true
            it[useKeepalive] = true
            it[proxy.scheme] = "https"
            it[proxy.host] = "localhost"
            it[proxy.port] = "8080"
            it[proxy.username] = "secret"
            it[proxy.password] = "password1"
            it[name] = "HTTP Request"
            it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui"
        }

        org.apache.jmeter.extractor.RegexExtractor::class {
            props {
                it[guiClass] = "org.apache.jmeter.extractor.gui.RegexExtractorGui"
                it[name] = "Regular Expression Extractor"
                it[referenceName] = "regexVar"
                it[regularExpression] = "hello\\s+?world"
                it[template] = "\$1\$"
            }
        }

        org.apache.jmeter.protocol.http.control.HeaderManager::class {
            props {
                it[headers] = listOf(
                    org.apache.jmeter.protocol.http.control.Header().apply {
                        props {
                            it[headerName] = "Accept"
                            it[value] = "text/plain"
                        }
                    },
                    org.apache.jmeter.protocol.http.control.Header().apply {
                        props {
                            it[headerName] = "User-Agent"
                            it[value] = "JMeter"
                        }
                    },
                    org.apache.jmeter.protocol.http.control.Header().apply {
                        props {
                            it[headerName] = "X-JMeter-Thread"
                            it[value] = "Thread \${__threadNum}"
                        }
                    },
                )
                it[guiClass] = "org.apache.jmeter.protocol.http.gui.HeaderPanel"
                it[name] = "HTTP Header Manager"
            }
        }
    }

    org.apache.jmeter.control.LoopController::class {
        props {
            it[guiClass] = "org.apache.jmeter.control.gui.LoopControlPanel"
            it[name] = "Loop Controller"
            it[loops] = "1"
        }

        org.apache.jmeter.sampler.DebugSampler::class {
            props {
                it[guiClass] = "org.apache.jmeter.testbeans.gui.TestBeanGUI"
                it[name] = "Debug Sampler"
            }
        }
    }
}

@kirillyu

Copy link
Copy Markdown

Initially - thanks for the idea, this is really what the community needs.

My opinion on current issues:

  1. I am inclined more to the idea that an intermediate presentation is needed than not.
  2. Can you give details on this? I don't see any clear advantage in using no-result functions.
  3. This is an interesting point. In my opinion there is no need to escape this character. It should also be used as part of the string, and replace will happen in the jmeter engine. In other cases, the variables must be taken from the context in the vars.get style.
  4. name-less with a description is better, it is faster and takes up less space.
  5. Yes, using .children () is more convenient for implementing custom builders.

@vlsi vlsi force-pushed the jmeter-dsl branch 2 times, most recently from 8e3ca1c to ad6ade1 Compare June 6, 2023 20:09
@vlsi vlsi marked this pull request as ready for review June 6, 2023 20:23
@vlsi vlsi added this to the 5.6 milestone Jun 6, 2023
@vlsi vlsi force-pushed the jmeter-dsl branch 3 times, most recently from a73589a to 0257ffa Compare June 7, 2023 16:40
@vlsi

vlsi commented Jun 7, 2023

Copy link
Copy Markdown
Collaborator Author

I've added documentation, and a few more samples

@vlsi vlsi force-pushed the jmeter-dsl branch 6 times, most recently from 0853eae to b9095f2 Compare June 9, 2023 21:03
@vlsi

vlsi commented Jun 9, 2023

Copy link
Copy Markdown
Collaborator Author

I've added Copy Code action so users could copy a code draft (see the first comment for sample UI and the generated code)

@vlsi vlsi force-pushed the jmeter-dsl branch 9 times, most recently from 34e609f to a97510c Compare June 13, 2023 14:25
…n interface

Java's interfaces are bad since they have no declaration side type variance.
Kotlin's ... -> Unit is hard to use in Java as it would require "return Unit.INSTANCE"

Regular Kotlin's functions A->B are fine though.
@vlsi vlsi merged commit 31d598f into apache:master Jun 15, 2023
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
… the most often used scenario

This is a follow-up to apache#678
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
… the most often used scenario

This is a follow-up to apache#678
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
…ckward compatibility

Previously, TestElement.NAME and similar constants were coded as literals,
and it was possible to use those references in annotation values.

If the replace literals with method calls, then it would be impossible to use
the fields in annotation values.

So the static fields are reverted to literals.

This partially reverts commits like apache#678,
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
…ckward compatibility

Previously, TestElement.NAME and similar constants were coded as literals,
and it was possible to use those references in annotation values.

If the replace literals with method calls, then it would be impossible to use
the fields in annotation values.

So the static fields are reverted to literals.

This partially reverts commits like apache#678,
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
…ckward compatibility

Previously, TestElement.NAME and similar constants were coded as literals,
and it was possible to use those references in annotation values.

If the replace literals with method calls, then it would be impossible to use
the fields in annotation values.

So the static fields are reverted to literals.

This partially reverts commits like apache#678,
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 15, 2023
…ckward compatibility

Previously, TestElement.NAME and similar constants were coded as literals,
and it was possible to use those references in annotation values.

If the replace literals with method calls, then it would be impossible to use
the fields in annotation values.

So the static fields are reverted to literals.

This partially reverts commits like apache#678,
vlsi added a commit that referenced this pull request Jun 15, 2023
…ckward compatibility

Previously, TestElement.NAME and similar constants were coded as literals,
and it was possible to use those references in annotation values.

If the replace literals with method calls, then it would be impossible to use
the fields in annotation values.

So the static fields are reverted to literals.

This partially reverts commits like #678,
vlsi added a commit to vlsi/jmeter that referenced this pull request Jun 16, 2023
… the most often used scenario

This is a follow-up to apache#678
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants