All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.chutneytesting.execution.domain.compiler.ComposedTestCaseDatatableIterationsPreProcessor Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package com.chutneytesting.execution.domain.compiler;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Optional.empty;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import com.chutneytesting.design.domain.dataset.DataSet;
import com.chutneytesting.design.domain.dataset.DataSetRepository;
import com.chutneytesting.design.domain.scenario.compose.Strategy;
import com.chutneytesting.engine.domain.execution.strategies.DataSetIterationsStrategy;
import com.chutneytesting.execution.domain.ExecutionRequest;
import com.chutneytesting.execution.domain.scenario.composed.ExecutableComposedScenario;
import com.chutneytesting.execution.domain.scenario.composed.ExecutableComposedStep;
import com.chutneytesting.execution.domain.scenario.composed.ExecutableComposedTestCase;
import com.chutneytesting.execution.domain.scenario.composed.StepImplementation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.text.StringEscapeUtils;

public class ComposedTestCaseDatatableIterationsPreProcessor implements TestCasePreProcessor {

    private final DataSetRepository dataSetRepository;

    ComposedTestCaseDatatableIterationsPreProcessor(DataSetRepository dataSetRepository) {
        this.dataSetRepository = dataSetRepository;
    }

    @Override
    public ExecutableComposedTestCase apply(ExecutionRequest executionRequest) {
        ExecutableComposedTestCase testCase = (ExecutableComposedTestCase) executionRequest.testCase;
        return apply(testCase);
    }

    ExecutableComposedTestCase apply(ExecutableComposedTestCase testCase) {
        Optional oDataset = testCase.metadata.datasetId().map(dataSetRepository::findById);
        if (oDataset.isEmpty()) {
            return testCase;
        }

        DataSet dataset = oDataset.get();
        Map> matchedHeaders = findDatableHeadersMatchingExecutionParameters(testCase, dataset.datatable);

        return new ExecutableComposedTestCase(
            testCase.metadata,
            applyToScenario(testCase.composedScenario, matchedHeaders, dataset),
            applyToExecutionParameters(testCase.executionParameters, matchedHeaders.get(Boolean.TRUE), dataset));
    }

    private Map> findDatableHeadersMatchingExecutionParameters(ExecutableComposedTestCase testCase, List> datatable) {
        Map> matchedHeaders = new HashMap<>();
        if (!datatable.isEmpty()) {
            Set headers = datatable.get(0).keySet();
            matchedHeaders = testCase.executionParameters.keySet().stream()
                .collect(groupingBy(headers::contains));
        }
        matchedHeaders.putIfAbsent(Boolean.TRUE, emptyList());
        matchedHeaders.putIfAbsent(Boolean.FALSE, emptyList());
        return matchedHeaders;
    }

    private Map applyToExecutionParameters(Map executionParameters, List matchedHeaders, DataSet dataset) {
        HashMap parameters = new HashMap<>(executionParameters);

        Map constants = dataset.constants;
        executionParameters.keySet().stream()
            .filter(constants::containsKey)
            .forEach(key -> parameters.put(key, constants.get(key)));

        executionParameters.keySet().stream()
            .filter(matchedHeaders::contains)
            .forEach(parameters::remove);

        return parameters;
    }

    private ExecutableComposedScenario applyToScenario(ExecutableComposedScenario composedScenario, Map> matchedHeaders, DataSet dataset) {
        Map iterationOutputs = new HashMap<>();
        return ExecutableComposedScenario.builder()
            .withComposedSteps(
                composedScenario.composedSteps.stream()
                    .map(cs -> applyToStep(cs, matchedHeaders, dataset, iterationOutputs))
                    .collect(toList())
            )
            .withParameters(composedScenario.parameters)
            .build();
    }

    private ExecutableComposedStep applyToStep(ExecutableComposedStep composedStep, Map> matchedHeaders, DataSet dataset, Map iterationOutputs) {
        // ex. parameter : { "**header**" : "" }
        Set executionParametersReferencingDatableHeadersInKey = findHeadersReferencesWithinEmptyParametersKey(composedStep.executionParameters, matchedHeaders.get(Boolean.TRUE));

        // ex: parameter : { "paramName" : "**header1** + **header2**" }
        Map> executionParametersReferencingDatableHeadersInValue = findHeadersReferencesWithinParameterValues(composedStep.executionParameters, matchedHeaders.get(Boolean.TRUE));
        Map usedIndexedOutput = findUsageOfPreviousOutputInImplementation(composedStep, iterationOutputs);
        Map usedIndexedOutputInDataset = findUsageOfPreviousOutputInExecutionParameters(composedStep, iterationOutputs);

        if (executionParametersReferencingDatableHeadersInKey.isEmpty() && executionParametersReferencingDatableHeadersInValue.isEmpty() && usedIndexedOutput.isEmpty() && usedIndexedOutputInDataset.isEmpty()) {
            removeObsoleteIndexedOutputs(iterationOutputs, composedStep);
            return composedStep;
        }

        // ex. parameter : { "paramName" : "" }
        Map emptyExecutionParameters = composedStep.executionParameters.entrySet().stream()
            .filter(e -> e.getValue().isEmpty())
            .filter(e -> !executionParametersReferencingDatableHeadersInKey.contains(e.getKey()))
            .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

        matchedHeaders.get(Boolean.FALSE).forEach(s -> emptyExecutionParameters.put(s, ""));

        return ExecutableComposedStep.builder()
            .from(composedStep)
            .withImplementation(empty())
            .withStrategy(new Strategy(DataSetIterationsStrategy.TYPE, emptyMap()))
            .withSteps(buildStepIterations(composedStep, dataset.datatable, executionParametersReferencingDatableHeadersInKey, executionParametersReferencingDatableHeadersInValue, iterationOutputs))
            .withExecutionParameters(buildExecutionParametersWithAliases(emptyExecutionParameters))
            .build();
    }

    private Map findUsageOfPreviousOutputInExecutionParameters(ExecutableComposedStep composedStep, Map iterationOutputs) {
        return iterationOutputs.entrySet().stream()
            .filter(previousOutput ->
                composedStep.executionParameters.entrySet().stream()
                    .anyMatch(input -> input.getKey().contains("#" + previousOutput.getKey())
                        || usePreviousIterationOutput(previousOutput.getKey(), input.getValue())
                    )
            )
            .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    private Map findUsageOfPreviousOutputInImplementation(ExecutableComposedStep composedStep, Map iterationOutputs) {
        Map map = new HashMap<>();
        composedStep.stepImplementation.ifPresent(si ->
            map.putAll(iterationOutputs.entrySet().stream()
                .filter(previousOutput ->
                    contains(previousOutput.getKey(), si.inputs) || contains(previousOutput.getKey(), si.outputs)
                )
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))
            )
        );

        return map;
    }

    private boolean contains(String previousOutput, Map map) {
        return map.entrySet().stream()
            .anyMatch(entry -> entry.getKey().contains("#" + previousOutput)
                || usePreviousIterationOutput(previousOutput, entry.getValue())
            );
    }

    private boolean usePreviousIterationOutput(String previousOutput, Object value) {
        if (value instanceof String) {
            return usePreviousIterationOutput(previousOutput, (String) value);
        } else if (value instanceof Map) {
            return usePreviousIterationOutput(previousOutput, (Map) value);
        }
        return false;
    }

    private boolean usePreviousIterationOutput(String previousOutput, String value) {
        return value.contains("#" + previousOutput);
    }

    private boolean usePreviousIterationOutput(String previousOutput, Map value) {
        return value.entrySet().stream().anyMatch(e -> e.getKey().contains("#" + previousOutput) || usePreviousIterationOutput(previousOutput, e.getValue()));
    }

    private Set findHeadersReferencesWithinEmptyParametersKey(Map executionParameters, List matchedHeaders) {
        return executionParameters.entrySet().stream()
            .filter(e -> e.getValue().isEmpty() && matchedHeaders.contains(e.getKey()))
            .map(Map.Entry::getKey)
            .collect(toSet());
    }

    private Map> findHeadersReferencesWithinParameterValues(Map executionParameters, List matchedHeaders) {
        // Key is an execution parameter name, value is a set of headers name
        HashMap> executionParametersReferencingDatableHeader = new HashMap<>();
        // TODO - change double for loop + condition by first listing step parameters matching a header, then only proceed matched ones
        for (Map.Entry parameter : executionParameters.entrySet()) {
            String value = parameter.getValue();
            for (String matchedHeader : matchedHeaders) {
                if (value.contains("**" + matchedHeader + "**")) {
                    executionParametersReferencingDatableHeader.putIfAbsent(parameter.getKey(), new HashSet<>());
                    executionParametersReferencingDatableHeader.get(parameter.getKey()).add(matchedHeader);
                }
            }
        }
        return executionParametersReferencingDatableHeader;
    }

    private List buildStepIterations(ExecutableComposedStep composedStep,
                                                             List> datatable,
                                                             Set csNovaluedEntries,
                                                             Map> executionParametersReferencingDatableHeader,
                                                             Map iterationOutputs) {
        List iterations;
        AtomicInteger index = new AtomicInteger(0);

        iterations = generateIterationsForDatatable(composedStep, datatable, csNovaluedEntries, executionParametersReferencingDatableHeader, iterationOutputs, index);

        if (iterations.isEmpty()) {
            iterations = generateIterationsForPreviousIterationOutputs(composedStep, iterationOutputs, index);
        }

        if (iterations.isEmpty()) {
            iterations = generateIterationsForPreviousIterationOutputsInDataset(composedStep, iterationOutputs, index);
        }

        rememberIterationsCountForEachOutput(iterationOutputs, index);
        updateIndexedOutputsUsingExecutionParameterValues(iterationOutputs, composedStep);
        return iterations;
    }

    private List generateIterationsForDatatable(ExecutableComposedStep composedStep, List> datatable, Set csNovaluedEntries, Map> executionParametersReferencingDatableHeader, Map iterationOutputs, AtomicInteger index) {
        List> iterationData = findUsageOfDatasetDatatable(csNovaluedEntries, executionParametersReferencingDatableHeader, datatable);
        return iterationData.stream()
            .map(mv -> {
                index.getAndIncrement();

                return ExecutableComposedStep.builder()
                    .from(composedStep)
                    .withImplementation(composedStep.stepImplementation.flatMap(si -> Optional.of(indexIterationIO(si, index, iterationOutputs))))
                    .withName(composedStep.name + " - datatable iteration " + index)
                    .withExecutionParameters(applyIndexedOutputs(updatedExecutionParametersUsingCurrentValue(composedStep.executionParameters, csNovaluedEntries, executionParametersReferencingDatableHeader, mv), index, iterationOutputs))
                    .withSteps(composedStep.steps.stream()
                        .map(s ->
                            ExecutableComposedStep.builder()
                                .from(s)
                                .withImplementation(s.stepImplementation.flatMap(si -> Optional.of(indexIterationIO(si, index, iterationOutputs))))
                                .withExecutionParameters(applyIndexedOutputs(s.executionParameters, index, iterationOutputs))
                                .build())
                        .collect(toList())
                    )
                    .build();
            })
            .collect(toList());
    }

    private List> findUsageOfDatasetDatatable(Set csNovaluedEntries, Map> csValuedEntriesWithRef, List> datatable) {
        Set dataSetEntriesReferenced = csValuedEntriesWithRef.values().stream().flatMap(Collection::stream).collect(toSet());
        return datatable.stream()
            .map(mv ->
                mv.entrySet().stream()
                    .filter(e -> csNovaluedEntries.contains(e.getKey()) || dataSetEntriesReferenced.contains(e.getKey()))
                    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))
            )
            .distinct()
            .filter(mv -> !mv.isEmpty())
            .collect(toList());
    }

    private List generateIterationsForPreviousIterationOutputs(ExecutableComposedStep composedStep, Map iterationOutputs, AtomicInteger index) {
        Map previousOutputs = findUsageOfPreviousOutputInImplementation(composedStep, iterationOutputs);
        int iterationsCount = previousOutputs.values().stream().findFirst().orElse(0);

        List generatedIterations = new ArrayList<>();
        for (int i = 0; i < iterationsCount; i++) {
            index.getAndIncrement();
            generatedIterations.add(
                ExecutableComposedStep.builder()
                    .from(composedStep)
                    .withImplementation(composedStep.stepImplementation.flatMap(si -> Optional.of(indexIterationIO(si, index, iterationOutputs))))
                    .withName(composedStep.name + " - datatable iteration " + index)
                    .withExecutionParameters(composedStep.executionParameters)
                    .build()
            );
        }
        return generatedIterations;
    }

    private List generateIterationsForPreviousIterationOutputsInDataset(ExecutableComposedStep composedStep, Map iterationOutputs, AtomicInteger index) {
        Map previousOutputs = findUsageOfPreviousOutputInExecutionParameters(composedStep, iterationOutputs);
        int iterationsCount = previousOutputs.values().stream().findFirst().orElse(0);

        List generatedIterations = new ArrayList<>();
        for (int i = 0; i < iterationsCount; i++) {
            index.getAndIncrement();
            generatedIterations.add(
                ExecutableComposedStep.builder()
                    .from(composedStep)
                    .withImplementation(composedStep.stepImplementation.flatMap(si -> Optional.of(indexIterationIO(si, index, iterationOutputs))))
                    .withName(composedStep.name + " - datatable iteration " + index)
                    .withExecutionParameters(applyIndexedOutputs(composedStep.executionParameters, index, iterationOutputs))
                    .build()
            );
        }
        return generatedIterations;
    }

    private Map updatedExecutionParametersUsingCurrentValue(Map executionParameters, Set csNovaluedEntries, Map> executionParametersReferencingDatableHeader, Map mv) {
        Map newExecutionParameters = new HashMap<>(executionParameters);
        executionParameters.forEach((k, v) -> {
            if (csNovaluedEntries.contains(k)) {
                newExecutionParameters.put(k, mv.get(k));
            } else if (executionParametersReferencingDatableHeader.containsKey(k)) {
                newExecutionParameters.put(k, replaceParams(v, emptyMap(), mv));
            }
        });
        return newExecutionParameters;
    }

    private StepImplementation indexIterationIO(StepImplementation si, AtomicInteger index, Map iterationOutputs) {
        rememberIndexedOutput(iterationOutputs, si.outputs);
        return new StepImplementation(
            si.type,
            si.target,
            indexInputs(si.inputs, index, iterationOutputs),
            indexOutputs(si.outputs, index, iterationOutputs),
            si.validations
        );
    }

    private void rememberIndexedOutput(Map iterationOutputs, Map outputs) {
        Map collect = outputs.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> 0));
        iterationOutputs.putAll(collect);
    }

    private void rememberIterationsCountForEachOutput(Map iterationOutputs, AtomicInteger index) {
        iterationOutputs.replaceAll((k, v) -> v = index.get());
    }

    private void updateIndexedOutputsUsingExecutionParameterValues(Map iterationOutputs, ExecutableComposedStep composedStep) {
        List steps = new ArrayList<>(composedStep.steps);
        Collections.reverse(steps);
        steps.forEach(executableComposedStep ->
            updateIndexedOutputsUsingExecutionParameterValues(iterationOutputs, executableComposedStep)
        );

        composedStep.executionParameters.entrySet().stream()
            .filter(entry -> iterationOutputs.containsKey("**" + entry.getKey() + "**"))
            .forEach(entry -> {
                Integer iterationsCount = iterationOutputs.get("**" + entry.getKey() + "**");
                iterationOutputs.remove("**" + entry.getKey() + "**");
                iterationOutputs.put(composedStep.executionParameters.get(entry.getKey()), iterationsCount);
            });
    }

    private void removeObsoleteIndexedOutputs(Map iterationOutputs, ExecutableComposedStep composedStep) {
        List list = new ArrayList<>();
        composedStep.stepImplementation.ifPresent(si -> list.addAll(si.outputs.keySet()));
        if (list.isEmpty()) {
            composedStep.steps
                .forEach(executableComposedStep -> removeObsoleteIndexedOutputs(iterationOutputs, executableComposedStep));
        } else {
            composedStep.executionParameters.entrySet().stream()
                .filter(entry -> list.contains("**" + entry.getKey() + "**"))
                .forEach(entry -> {
                    list.remove("**" + entry.getKey() + "**");
                    list.add(entry.getValue());
                });
            list.stream()
                .filter(iterationOutputs::containsKey)
                .forEach(iterationOutputs::remove);
        }
    }

    private Map indexInputs(Map inputs, AtomicInteger index, Map iterationOutputs) {
        return inputs.entrySet().stream().collect(HashMap::new, (m, e) -> m.put(
            applyIndexedOutputsOnStringValue(e.getKey(), index, iterationOutputs),
            applyIndexedOutputs(e.getValue(), index, iterationOutputs)
            ), HashMap::putAll
        );
    }

    private Map indexOutputs(Map outputs, AtomicInteger index, Map iterationOutputs) {
        return outputs.entrySet().stream().collect(HashMap::new, (m, e) -> m.put(
            e.getKey() + "_" + index,
            applyIndexedOutputs(e.getValue(), index, iterationOutputs)
            ), HashMap::putAll
        );
    }

    private Object applyIndexedOutputs(Object value, AtomicInteger index, Map indexedOutput) {
        if (value instanceof String) {
            return applyIndexedOutputsOnStringValue((String) value, index, indexedOutput);
        } else if (value instanceof Map) {
            return applyIndexedOutputs((Map) value, index, indexedOutput);
        } else return value;
    }

    private Map applyIndexedOutputs(Map value, AtomicInteger index, Map indexedOutput) {
        return value.entrySet().parallelStream().collect(toMap(
            e -> applyIndexedOutputsOnStringValue(e.getKey(), index, indexedOutput),
            e -> applyIndexedOutputsOnStringValue(e.getValue(), index, indexedOutput)
        ));
    }

    private String applyIndexedOutputsOnStringValue(String value, AtomicInteger index, Map indexedOutput) {
        String tmp = value;
        for (String output : indexedOutput.keySet()) {
            Pattern pattern = Pattern.compile("#" + Pattern.quote(output) + "\\b");
            Matcher matcher = pattern.matcher(tmp);
            if (matcher.find()) {
                tmp = matcher.replaceAll("#" + StringEscapeUtils.escapeJson(output + "_" + index));
            }
        }
        return tmp;
    }

    private Map buildExecutionParametersWithAliases(Map executionParameters) {
        Map aliases = executionParameters.entrySet().stream()
            .filter(e -> isAlias(e.getValue()))
            .collect(toMap(a -> a.getValue().substring(2, a.getValue().length() - 2), o -> ""));

        aliases.putAll(executionParameters); // TODO - need to check why we filter and remove aliases and then add them all again ?

        return unmodifiableMap(aliases);
    }

    Pattern aliasPattern = Pattern.compile("^\\*\\*(.+)\\*\\*$");
    private boolean isAlias(String paramValue) {
        return aliasPattern.matcher(paramValue).matches();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy