Reputation: 324
During the upgrade of an automation project to the latest Karate version, we noticed a different behaviour of karate.callSingle()
in the Karate version 1.2.0 compared to the old Karate version 0.9.6.
According to the documentation, karate.callSingle()
ensures that a routine "runs only once". However, there is no clear statement about the scope in which the statement "runs only once" is true, so I don't really know if the old behaviour (in Karate 0.9.6) just worked by accident - or if there is a bug in the Karate 1.2.0.
This is our scenario:
We run our tests as JUnit 5 Parallel Execution and use the Cucumber-Reporting test report, so our test class looks very similar to https://github.com/karatelabs/karate/blob/master/karate-demo/src/test/java/demo/DemoTestParallel.java . There is one exception: When creating the Cucumber-Reporting ReportBuilder
object, we add so called classifications
(which are key-value pairs) to the Configuration
object, because we want to display some information (artefact name, version number) about the software under test in the report. To read out the version number, we're calling a feature file, that returns the version.
As a result, the unit tests contains two Runner
executions: Runner.path().parallel()
to execute all tests - and Runner.runFeature()
to execute only the feature that returns the version information, so that it can be passed to the Cucumber-Report:
class DemoTestParallel {
@Test
public void testParallel() {
Results results = Runner.path("classpath:demo")
.outputCucumberJson(true)
.karateEnv("demo")
.parallel(5);
generateReport(results.getReportDir());
assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
}
public static void generateReport(String karateOutputPath) {
Collection<File> jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] {"json"}, true);
List<String> jsonPaths = new ArrayList<>(jsonFiles.size());
jsonFiles.forEach(file -> jsonPaths.add(file.getAbsolutePath()));
final Map<String, Object> versionResult = Runner.runFeature("classpath:demo/versionInformation.feature", null, true);
Configuration config = new Configuration(new File("target"), "demo");
config.addClassifications("demoArtefact", versionResult.get("version"));
ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);
reportBuilder.generateReports();
}
}
In karate-config.js, we execute a feature with karate.callSingle()
, as we want to run it only once:
var order = karate.callSingle('classpath:demo/reusableOrder.feature@createSuccessfulOrder', config);
config.orderId = order.id;
In Karate version 0.9.6, karate.callSingle()
was also ensured during multiple Runner
executions. So in the scenario above, reusableOrder.feature@createSuccessfulOrder
was executed only once.
In Karate versions 1.2.0, karate.callSingle()
is not ensured during multiple Runner
executions. So in the scenario above, reusableOrder.feature@createSuccessfulOrder
gets executed twice (once during Runner.path().parallel()
and another time during Runner.runFeature()
).
Is it a bug or a feature?
As I mentioned, I don't know if this is a bug or a feature :)
In case it's a feature, I would be happy about suggestions how to avoid to have reusableOrder.feature@createSuccessfulOrder
executed twice.
What I had in mind but didn't work in our case:
Runner.runFeature()
statement to pass evalKarateConfig = false
, to avoid executing karate-config.js . However, we need karate-config.js execution, as it defines some environment specific variables, that are needed for all feature files.callSingleCache
, to read the result of reusableOrder.feature@createSuccessfulOrder
a cache. However, defining callSingleCache
broke other tests (don't know why, didn't investigate further).I am happy about any help!
Upvotes: 1
Views: 694
Reputation: 58058
There's a lot to unpack in your question, but just skimming - yes the "scoping" of callSingle()
to a Runner
instance is intentional. The reason is it should be possible for teams to run 2 Runner
instances in parallel without blocking.
So my suggestion is please stuff everything you have into a single Runner
. If it means refactoring your tests, I think that's the right approach. For example, what I would do is have reusableOrder.feature
write some file to the file-system, which you can then read in your reporting routine.
No other suggestions come to mind other than the extreme option of writing your own Java utility that wraps reusableOrder.feature
- but ensures it returns data only once, so build your own cache within the active JVM.
Upvotes: 1