fix param mutability bug, defer dryrun till full activity rendering

This commit is contained in:
Jonathan Shook committed 2026-02-09 22:59:00 +00:00
1 parent 95e119d759
commit f95fbfc049
6 files changed
+133 -4

No files matched your search

@@ -76,14 +76,17 @@ public class OpsLoader {
if (srcuri != null) {
logger.info("workload URI: '" + srcuri + "'");
}
Map<String, ?> expressionParams = params == null ? Map.of() : Map.copyOf(params);
// Defensive copy so template processing never mutates caller-owned params
Map<String, Object> mutableParams =
(params == null) ? new java.util.LinkedHashMap<>() : new java.util.LinkedHashMap<>(params);
Map<String, ?> expressionParams = Map.copyOf(mutableParams);
// Use TemplateContext to automatically manage template state lifecycle
try (TemplateContext ctx = TemplateContext.enter()) {
// PHASE 1: Rewrite TEMPLATE syntax to expr function calls
// This converts TEMPLATE(k,v) and ${key:value} to expr paramOr() calls
String templateRewritten = switch (fmt) {
case jsonnet -> evaluateJsonnet(srcuri, params); // Jsonnet doesn't need template rewriting
case jsonnet -> evaluateJsonnet(srcuri, mutableParams); // Jsonnet doesn't need template rewriting
case yaml, json, inline, stmt -> TemplateRewriter.rewrite(sourceData);
};
@@ -103,7 +106,6 @@ public class OpsLoader {
Map<String, String> templateAccesses = ctx.getAccesses();
templateAccesses.forEach((k, v) -> {
layered.addTemplateVariable(k, v);
params.remove(k);
});
return layered;
@@ -149,4 +149,25 @@ public class OpsLoaderTest {
assertThat(result.getTemplateVariables()).containsKey("username");
}
@Test
public void testTemplateProcessingDoesNotMutateParams() {
String yaml = """
ops:
- stmt: "value is TEMPLATE(cmd_line_param,theDefault)"
""";
Map<String, String> params = new HashMap<>();
params.put("cmd_line_param", "TheProvidedParameter");
OpsDocList first = OpsLoader.loadString(yaml, OpTemplateFormat.yaml, params, null);
assertThat(params).containsEntry("cmd_line_param", "TheProvidedParameter");
assertThat(first.getStmtDocs().get(0).getOpTemplates().get(0).getStmt())
.hasValue("value is TheProvidedParameter");
OpsDocList second = OpsLoader.loadString(yaml, OpTemplateFormat.yaml, params, null);
assertThat(params).containsEntry("cmd_line_param", "TheProvidedParameter");
assertThat(second.getStmtDocs().get(0).getOpTemplates().get(0).getStmt())
.hasValue("value is TheProvidedParameter");
}
}
@@ -128,7 +128,10 @@ public class NBCLIScenarioPreprocessor {
.first().orElseThrow();
// TODO: The yaml needs to be parsed with arguments from each command independently to support template vars
OpsDocList scenariosYaml = OpsLoader.loadContent(yamlWithNamedScenarios, new LinkedHashMap<>(userProvidedParams));
// Avoid triggering dryrun modes during scenario parsing; these should apply to the final activity load
LinkedHashMap<String, String> paramsForScenarioParsing = new LinkedHashMap<>(userProvidedParams);
paramsForScenarioParsing.remove("dryrun");
OpsDocList scenariosYaml = OpsLoader.loadContent(yamlWithNamedScenarios, paramsForScenarioParsing);
Scenarios scenarios = scenariosYaml.getDocScenarios();
String[] nameparts = scenarioName.split("\\.",2);
@@ -0,0 +1,38 @@
/*
* Copyright (c) nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.engine.api.scenarios;
import java.util.LinkedList;
import java.util.List;
/**
* Probe used by NBCLIScenarioPreprocessorTest to ensure dryrun does not
* trigger System.exit during scenario parsing.
*/
public class NBCLIScenarioPreprocessorDryrunProbe {
public static void main(String[] args) {
LinkedList<String> argv = new LinkedList<>();
argv.add("cmdline_template");
argv.add("the_scenario");
argv.add("dryrun=exprs");
NBCLIScenarioPreprocessor.rewriteScenarioCommands(argv, List.of("src/test/resources"));
System.out.println("PROBE_OK");
System.out.println("ARGS=" + String.join(" ", argv));
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (c) nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.engine.api.scenarios;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("unit")
public class NBCLIScenarioPreprocessorTest {
@Test
public void rewriteScenarioCommands_shouldNotTriggerDryrunDuringScenarioParsing() throws Exception {
String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");
ProcessBuilder pb = new ProcessBuilder(
javaBin,
"-cp",
classpath,
"io.nosqlbench.engine.api.scenarios.NBCLIScenarioPreprocessorDryrunProbe"
);
pb.directory(new File(System.getProperty("user.dir")));
pb.redirectErrorStream(true);
Process p = pb.start();
String output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
int exit = p.waitFor();
assertThat(exit).isEqualTo(0);
assertThat(output).contains("PROBE_OK");
assertThat(output).contains("cmd_line_param=firstone");
assertThat(output).contains("dryrun=exprs");
assertThat(output).contains("run");
}
}
@@ -0,0 +1,11 @@
scenarios:
the_scenario:
the_activity: run driver=stdout tags==block:the_block threads==1 cycles==1 cmd_line_param=TEMPLATE(cmd_line_param, firstone)
blocks:
the_block:
ops:
the_only_op:
stmt: |
================================================
The command-line-parameter is: "TEMPLATE(cmd_line_param, theDefault)".
================================================