Dynamically exporting dynamically generated VU code functions

Hi, im trying to put together a generic test code which:

  • reads in scenario configurations from a config file
  • generates scenario configuration
  • generates VU code functions
  • and exports them

I succeeded to create such a script, but with a limitation. I can only export a static list of functions and the optimal would be to dynamically export all the generated functions.

This is how a config.json looks like:

{
    "scenarios": [
        {
            "hostname": "interesting-hostname.com",
            "schema": "https",
            "path": "/team/links",
            "exec": "team_links",
            "executor": "constant-arrival-rate",
            "duration": "30s",
            "rate": 40,
            "timeUnit": "1s",
            "preAllocatedVUs": 2,
            "maxVUs": 60
        },
        {
            "hostname": "interesting-hostname.com",
            "schema": "https",
            "path": "/status/elastic",
            "exec": "status_elastic",
            "executor": "constant-arrival-rate",
            "duration": "30s",
            "rate": 30,
            "timeUnit": "1s",
            "preAllocatedVUs": 2,
            "maxVUs": 60
        }
    ]
}

And this is the script code, with commented out attempts on the bottom section to dynamically export the functions:

import http from 'k6/http';
import { group } from 'k6';
import { SharedArray } from 'k6/data';


// All heavy work (opening and processing big files for example) should be done inside here.
// This way it will happen only once and the result will be shared between all VUs, saving time and memory.
const scenariosConfig = new SharedArray('scenario configurations', function () {
    const configContent = JSON.parse(open('/test_folder/config.json'));
    console.log(JSON.stringify(configContent));
    const extractedConfigData = configContent.scenarios; // Change 'specific_key' to the key you want to extract

    return extractedConfigData; // extractedData must be an array
});


function generateScenarios(pScenariosConfig) {
    const scenariosOptions = {}

    pScenariosConfig.forEach(scenario => {
        scenariosOptions[scenario.exec] = {
            executor: scenario.executor,
            exec: scenario.exec,
            duration: scenario.duration,
            rate: scenario.rate,
            timeUnit: scenario.timeUnit,
            preAllocatedVUs: scenario.preAllocatedVUs,
            maxVUs: scenario.maxVUs
        };
    });

    return scenariosOptions;
}

export const options = {
    discardResponseBodies: true,
    scenarios: generateScenarios(scenariosConfig),
};

function defineApiCall(scenario) {
    return function () {
        group(scenario.exec, () => {
          http.get(scenario.schema + '://' + scenario.hostname + scenario.path, {
            tags: { my_custom_tag: scenario.exec },
          });
        });
    };
  }

// Define functions dynamically based on config
const apiCalls = {};
scenariosConfig.forEach(scenario => {
    const apiCallName = scenario.exec; // Get function name from "exec" property
    apiCalls[apiCallName] = defineApiCall(scenario);
});

// >>> This fails with: 'import' and 'export' may only appear at the top level
//// Dynamically export the generated function individually
//const apiCallNames = Object.keys(apiCalls); // Get function names as an array
//
//for (const name of apiCallNames) {
//  export function [name]() {
//    return apiCalls[name];
//  }
//}

// >>> This fails with: could not load JS test 'file:///test_folder/test_multiple_scenarios_groups.js': no exported functions in script"
//// Dynamically export the generated functions as an object
//const apiCallNames = Object.keys(apiCalls); // Get function names as an array
//
//const exportedApiCalls = {};
//for (const name of apiCallNames) {
//  exportedApiCalls[name] = apiCalls[name];
//}
//export { exportedApiCalls };

// >>> This is not dynamic, but works...
export const { team_links, status_elastic } = apiCalls;

Im not a JS guru and looks like my problem is not directly k6 related(well, as i see there is a k6 limitation to use the export only on top level, or it expects individual function exports), but if anyone has relevant JS expertise or did a similar script, please help me figure out how to dinamically export the generated functions so k6 can use them and the only file which has to be changed is the configuration.

Hi @bgyomorei,

As you’ve noticed export and import statements can only be at the top level and can’t have what will be considered dynamic parts.

That is consious decision by the designers of the ECMAScript specification (the thing defining what JavaScript is). While I can go into a bunch of reasons why this is so I propose you go look at the proposal for dynamic modules.

There apart from the explanation what the underlying mechanism is and why it works the way it, also are explanations (in the linked slides) on why this was turned down.

Looking at the example you have I would argue that you don’t need to create functions dynamically to begin with. You can have a single default function that splits based on the scenario info which you can get based on the scenario name using k6/execution.

This should work without any problems and looking at the code you have I can’t really see it being a problem.

Hope this helps you!

p.s. My only other comment is around you using a single group in the whole scenario. (as well as using only 1 request within a group). If you are going to have only 1 group that is the whole scenario - you can just use the scenario tag for a grouping. And grouping only 1 request is not really … grouping