Closed bookmoons closed 5 years ago
@bookmoons Fantastic! I'll start reviewing this. Will begin by trying to convert a bunch of JMeter tests.
Ok, I've played around with the converter a bit. This first round of feedback is just based on what I experienced while using the tool, I haven't looked much at the code yet. Great job overall from what I've seen so far! 🎉
examples/
should be .jmx
rather than .xml
const vus = ...
let url, opts, auth, r, regex, match, matches, extract, output
let csvPage = {}, csvColumns = {}
const constants = {}
const vars = {}
const files = {}
import {group, sleep} from "k6"
rather than doing import k6 from "k6"
and using k6.group
and k6.sleep
throughout the generated code?'
, "
and template literal strings. Could we always use "
when not necessary to use template literals (ie. when there aren't any variable substitutions that need to happen)? Maybe this could be done as part of a code formatting/prettification step as mentioned above?/Users/<snip>/jmeter-to-k6/src/expand.js:3
for (const item of array) {
^
TypeError: array is not iterable
at expand (/Users/<snip>/jmeter-to-k6/src/expand.js:3:22)
at countVus (/Users/<snip>/jmeter-to-k6/src/render.js:35:10)
at render (/Users/<snip>/jmeter-to-k6/src/render.js:13:15)
at convert (/Users/<snip>/jmeter-to-k6/src/convert.js:5:10)
at Object.<anonymous> (/Users/<snip>/jmeter-to-k6/bin/jmeter-to-k6.js:18:16)
at Module._compile (internal/modules/cjs/loader.js:722:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:733:10)
at Module.load (internal/modules/cjs/loader.js:620:32)
at tryModuleLoad (internal/modules/cjs/loader.js:560:12)
at Function.Module._load (internal/modules/cjs/loader.js:552:3)
jsonpath
doesn't seem to be included in the generated imports when using JSONExtractor (see JMX and generated JS: https://gist.github.com/robingustafsson/f847c27bee10d8c8c487bee00f0a8c14)
/Users/<snip>/jmeter-to-k6/src/element.js:48
if (!route[name]) throw new Error('Unrecognized element: ' + name)
^
Error: Unrecognized element: JSONPathAssertion
at element (/Users/
## Random Controller
* Only seems to generate 1 case (`case 0: ...`), and throws `Error: Unexpected random index: 1` for all other possible options (see JMX and generated JS: https://gist.github.com/robingustafsson/7eae7a30a438f074de8a1c36cdbaa99b)
## Thread group
* Could the `if (__VU >= max_vus && __VU <= max_vus) { ... } else throw new Error('Unexpected VU: ' + __VU)` code be removed if only 1 thread group is used? (see JMX and generated JS: https://gist.github.com/robingustafsson/16979a21fcedbff9766cee7a48dd9d4a)
* Also, I think we can skip the `else throw new Error('Unexpected VU: ' + __VU)` in all cases
* If `forever` is not selected and instead an iteration count (`loops`) has been specified there should be a statement like this in the generated script:
```js
export default function() {
if (__ITER < whatever_iteration_count_specified) {
// All thread group code
}
}
target
values in stages
) is wrong and so is the duration
values (see JMX and generated JS: https://gist.github.com/robingustafsson/1e15ca6f6fd2a79612ea8c485be9eb3e):
target
entry should increase by the specified stepping (in this case by 10 VUs every step)duration
values are all in "ms" where they should be "s"[{"target":10,"duration":"30ms"},{"target":10,"duration":"30ms"},{"target":10,"duration":"30ms"}]
for a 30 VU max with 10 VU stepping every 30s with 5s ramp-up and a 60s hold at the top with a ramp down to 0 VUs with 5 VUs per second, it should generate [{"target":10,"duration":"5s"},{"target":10,"duration":"30s"},{"target":20,"duration":"5s"},{"target":20,"duration":"30s"},{"target":30,"duration":"5s"},{"target":30,"duration":"30s"},{"target":30,"duration":"60s"},{"target":0,"duration":"6s"}]
Really nice. Updating all of this.
These were really good bug reports. Everything but the SteppingThreadGroup
is fixed/updated. Will be getting into that stepping logic tomorrow.
The SteppingThreadGroup
is converting as specified. Also added all the test files to examples.
Good stuff! I'll do a second review tomorrow.
Sorry for the delay. Have now done a second pass after your fixes, found some more issues:
prettier
. I know it's a subjective matter, but we've used semicolons all over the docs and examples for k6 so would like to keep it consistent 🙂"dependencies": {
"@babel/runtime": "^7.0.0-beta.55"
}
as I got: Error: Cannot find module '@babel/runtime/helpers/builtin/interopRequireDefault'
(any idea why?)
${someVariable}=="Some string"
in JMeter it converts to something like this in k6 JS (see JMX and generated JS: https://gist.github.com/robingustafsson/56cd5aef0e69ad25afbaf0bde1b572d7):
if (`${vars[`someVariable`]}=="Some string"` === "true") {
...
}
This is not going to execute correctly, it would have to be something like this to work:
if (eval(`"${vars[`someVariable`]}"=="Some string"`) === true) {
...
}
JSONPostProcessor
) that is defined above the if-controller is also included inside the if-statement in the generated JS.{target: 0, duration:"20s"}
(20s = 100 VUs / 5 VUs per second) rather than:
{ target: 95, duration: "0s" },
{ target: 95, duration: "1s" },
{ target: 90, duration: "0s" },
{ target: 90, duration: "1s" },
{ target: 85, duration: "0s" },
{ target: 85, duration: "1s" },
{ target: 80, duration: "0s" },
{ target: 80, duration: "1s" },
{ target: 75, duration: "0s" },
{ target: 75, duration: "1s" },
{ target: 70, duration: "0s" },
{ target: 70, duration: "1s" },
{ target: 65, duration: "0s" },
{ target: 65, duration: "1s" },
{ target: 60, duration: "0s" },
{ target: 60, duration: "1s" },
{ target: 55, duration: "0s" },
{ target: 55, duration: "1s" },
{ target: 50, duration: "0s" },
{ target: 50, duration: "1s" },
{ target: 45, duration: "0s" },
{ target: 45, duration: "1s" },
{ target: 40, duration: "0s" },
{ target: 40, duration: "1s" },
{ target: 35, duration: "0s" },
{ target: 35, duration: "1s" },
{ target: 30, duration: "0s" },
{ target: 30, duration: "1s" },
{ target: 25, duration: "0s" },
{ target: 25, duration: "1s" },
{ target: 20, duration: "0s" },
{ target: 20, duration: "1s" },
{ target: 15, duration: "0s" },
{ target: 15, duration: "1s" },
{ target: 10, duration: "0s" },
{ target: 10, duration: "1s" },
{ target: 5, duration: "0s" },
{ target: 5, duration: "1s" },
{ target: 0, duration: "0s" }
Thanks for that. Have updated all of it.
Not sure what caused Babel requirement, but I added it as a dependency. The only package I've added to bundle
was papaparse
for the CSV update.
When I have a statement like ${someVariable}=="Some string" in JMeter it converts to something like this in k6 JS
I think this piece is following JMeter. The IfController
has a special flag for the condition, it seemed to be the only one that has it.
Interpret Condition as Variable Expression?
If this is selected, then the condition must be an expression that evaluates to "true" (case is ignored).
(Expression here meaning a JMeter expression). If you uncheck that flag then it's meant to be JavaScript, and it should render with an eval.
When running this the JSON extraction block (JSONPostProcessor) that is defined above the if-controller is also included inside the if-statement in the generated JS.
I think this one is correct. Processors and assertions are supposed to apply to all descendants of the parent element. So it caches them up in the context and duplicates for all samplers in that section of the tree.
It looks like that Babel requirement comes from yaml
, only necessary when bundling. So I think it's OK:
It runs on Node.js 6 and later with no external dependencies, and in browsers from IE 11 upwards using @babel/runtime (Note: not included in dependencies, install separately).
That package is used by JSONPathAssertion
JSONPathExtractor
to support YAML data in a response body.
Ah, you're right about the IFController
and var expression vs JS code. I'd missed unchecking that flag.
Regarding the post processors and assertions you're also correct that they apply to all samplers in the same scope. Think I found another issue here though:
It seems that even when I add the post-processor to be a child of the sampler, the post-processor is still applied to children of the grandparent which seems to go against the docs (see JMX and generated JS: https://gist.github.com/robingustafsson/82041fb0eb790bce3ddddedc79570332):
...to ensure that a post-processor is applied only to a particular sampler, add it as a child of the sampler.
It seems that even when I add the post-processor to be a child of the sampler, the post-processor is still applied to children of the grandparent
Think I know just where I did this. Looking into it.
Not what I thought it was, but I've just pushed a fix.
Ok @bookmoons. I've looked through the code as well and I think it looks good; structure, style and tests. Well done, thanks for your work on this 👏Now, go claim the bounty!
PS. I sent you a message on k6 slack if you can have a look when you get a chance.
Very nice, thank you very much. Submitted a claim and will open slack.
Adds v1 implementation of the converter. Converts all elements listed in #1.
Closes #1.