de.gematik.combine.CombineMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cucumber-test-combinations-maven-plugin Show documentation
Show all versions of cucumber-test-combinations-maven-plugin Show documentation
Fill cucumber scenarios examples tables with generated combinations of
specified values.
/*
* Copyright 2023 gematik GmbH
*
* 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 de.gematik.combine;
import static de.gematik.combine.CombineMojo.ErrorType.MINIMAL_TABLE;
import static de.gematik.combine.CombineMojo.ErrorType.PROPERTY;
import static de.gematik.combine.CombineMojo.ErrorType.SIZE;
import static de.gematik.combine.CombineMojo.ErrorType.WARNING;
import static de.gematik.combine.tags.parser.AllowDoubleLineupTagParser.ALLOW_DOUBLE_LINEUP_TAG;
import static de.gematik.combine.tags.parser.AllowSelfCombineTagParser.ALLOW_SELF_COMBINE_TAG;
import static de.gematik.combine.tags.parser.MinimalTableTagParser.MINIMAL_TABLE_TAG;
import static de.gematik.utils.Utils.getItemsToCombine;
import static de.gematik.utils.Utils.writeErrors;
import static java.lang.String.format;
import static java.util.Objects.nonNull;
import static org.apache.commons.io.FileUtils.copyDirectory;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.apache.commons.io.filefilter.DirectoryFileFilter.DIRECTORY;
import static org.apache.commons.io.filefilter.FileFilterUtils.suffixFileFilter;
import de.gematik.BaseMojo;
import de.gematik.combine.count.ExecutionCounter;
import de.gematik.combine.execution.FileProcessor;
import de.gematik.combine.model.CombineItem;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/** Plugin for filling empty gherkin tables with generated combinations */
@Mojo(name = "prepare-combine", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES)
@Setter
@RequiredArgsConstructor(onConstructor_ = @Inject)
public class CombineMojo extends BaseMojo {
public static final String EMPTY_EXAMPLES_TABLE_TAG = "@EMPTY_EXAMPLES_TABLE";
public static final String WIP_TAG = "@WIP";
public static final String TEST_RESOURCES_DIR = "./src/test/resources/";
public static final String WARN_MESSAGE =
"=== Caution!!! The feature file which is prepared have some issues and may not contain the expected value ===";
public static final String MINIMAL_TABLE_ERROR_HEADER =
"Minimal table should be created and failed. For following apis a valid row could not be generated:\n\t";
@Getter @Setter private static CombineMojo instance;
@Getter private static List tableSizeErrorLog = new ArrayList<>();
@Getter private static List minimalTableErrorLog = new ArrayList<>();
@Getter private static List propertyErrorLog = new ArrayList<>();
@Getter private static List warningsLog = new ArrayList<>();
private final FileProcessor replacer;
/** Path to the directory where the rendered templates got stored */
@Parameter(property = "outputDir", defaultValue = TEST_RESOURCES_DIR + "features")
String outputDir;
/** Path to the directory of the templates */
@Parameter(property = "templateDir", defaultValue = TEST_RESOURCES_DIR + "templates")
String templateDir;
/** The specific ending of the templates */
@Parameter(property = "ending", defaultValue = ".cute")
String ending;
/**
* The plugin will add these tags to all examples tables where it was not able to add at least one
* row
*/
@Parameter(property = "emptyExamplesTags")
List emptyExamplesTags;
/** The plugin will throw exception it was not able to add at least one row */
@Parameter(property = "breakIfTableToSmall", defaultValue = "true")
boolean breakIfTableToSmall;
/** The plugin will throw exception it was not able to add at least one row */
@Parameter(property = "minTableSize", defaultValue = "1")
int minTableSize;
/**
* The plugin will throw exception if minimal table could not generate valid row for at least one
* value
*/
@Parameter(property = "breakIfMinimalTableError", defaultValue = "false")
boolean breakIfMinimalTableError;
/**
* Prefix that is added to all plugin specific tags (except version filters!) in the feature file
* to categorize them under in the report
*/
@Getter
@Parameter(property = "pluginTagCategory", defaultValue = "Plugin")
String pluginTagCategory;
/**
* Prefix that is added to all version filter tags in the feature file to categorize them under in
* the report
*/
@Getter
@Parameter(property = "versionFilterTagCategory", defaultValue = "VersionFilter")
String versionFilterTagCategory;
/**
* The plugin will look for this property to determine the version of an item, may be set to an
* arbitrary string, default is "version"
*/
@Getter
@Parameter(property = "versionProperty", defaultValue = "version")
String versionProperty;
/** List of tags that are added to each processed examples table */
@Parameter(property = "defaultExamplesTags")
List defaultExamplesTags;
/** List of tags that are skipped by this plugin */
@Parameter(property = "skipTags")
List skipTags;
/** Filter Configuration */
@Parameter(property = "filterConfiguration")
FilterConfiguration filterConfiguration;
/** Project Filters */
@Parameter(property = "projectFilters")
ProjectFilters projectFilters;
/** Parameter to decide if combine-execution should be run */
@Parameter(name = "skipComb", defaultValue = "false")
boolean skipComb;
/** Parameter to decide if SoftFiler should be not considered if minimal table size not reached */
@Parameter(name = "softFilterToHardFilter", defaultValue = "false")
boolean softFilterToHardFilter;
/** Parameter to decide if executions should be counted and exported to file */
@Parameter(name = "countExecutions", defaultValue = "true")
boolean countExecutions;
/** In what formats should the counted executions be exported */
@Parameter(name = "countExecutionsFormat", defaultValue = "json")
List countExecutionsFormat;
@SneakyThrows
public void execute() {
if (this.isSkip() || skipComb) {
getLog().warn("Combine items and generate feature files got skipped due configuration");
return;
}
setInstance(this);
doChecks();
deleteDirectory(new File(outputDir));
execute(getConfiguration());
}
public void execute(CombineConfiguration config) throws MojoExecutionException {
String outDir = config.getOutputDir();
String fileEnding = config.getTemplateFileEnding();
List itemsToCombine =
getItemsToCombine(new File(config.getCombineItemFile()), this, true);
copyFiles(config.getTemplateDir(), outDir, fileEnding);
Collection files = allFiles(outDir, fileEnding);
if (files.isEmpty()) {
getPluginLog().warn("There are no files to process in " + outDir);
}
files.stream()
.map(file -> stripEnding(file, fileEnding))
.forEach(file -> replacer.process(file, config, itemsToCombine));
new ExecutionCounter().count(config);
writeErrors(
getClass().getSimpleName(),
Stream.of(minimalTableErrorLog, tableSizeErrorLog, propertyErrorLog, warningsLog)
.flatMap(Collection::stream)
.collect(Collectors.toList()),
WARN_MESSAGE,
true);
if (config.isBreakIfTableToSmall() && !tableSizeErrorLog.isEmpty()) {
throw new MojoExecutionException(
"Scenarios with insufficient examples found -> \n"
+ String.join("\n", tableSizeErrorLog));
}
if (config.isBreakIfMinimalTableError() && !minimalTableErrorLog.isEmpty()) {
throw new MojoExecutionException(
MINIMAL_TABLE_ERROR_HEADER + String.join("\n\t", minimalTableErrorLog));
}
}
@SneakyThrows
private void doChecks() {
File templateDirFile = new File(templateDir);
if (!templateDirFile.exists()) {
throw new MojoExecutionException(
"Template directory does not exist: " + templateDirFile.getAbsolutePath());
}
File file = new File(getCombineItemsFile());
if (!file.exists() || !file.isFile()) {
throw new MojoExecutionException("Combine items file not found: " + file.getAbsolutePath());
}
defaultExamplesTags.forEach(this::checkDefaultTag);
}
@SneakyThrows
private void checkDefaultTag(String tag) {
if (!tag.startsWith("@") || tag.lastIndexOf("@") > 0 || tag.trim().contains(" ")) {
throw new MojoExecutionException(tag + " is not a valid default tag");
}
List forbiddenTags =
List.of(ALLOW_DOUBLE_LINEUP_TAG, ALLOW_SELF_COMBINE_TAG, MINIMAL_TABLE_TAG);
if (forbiddenTags.stream().anyMatch(tag::contains)) {
throw new MojoExecutionException(
format(
"Default tag '%s' is not allowed to contain configuration tags! %s",
tag, forbiddenTags));
}
}
private CombineConfiguration getConfiguration() {
if (emptyExamplesTags.isEmpty()) {
emptyExamplesTags = List.of(EMPTY_EXAMPLES_TABLE_TAG, WIP_TAG);
}
if (skipTags.isEmpty()) {
skipTags = List.of(WIP_TAG);
}
if (pluginTagCategory == null) {
pluginTagCategory = "Plugin";
}
if (versionFilterTagCategory == null) {
versionFilterTagCategory = "VersionFilter";
}
if (versionProperty == null) {
versionProperty = "version";
}
if (nonNull(projectFilters)) {
projectFilters.parseProjectFilters();
}
if (!ending.startsWith(".")) {
ending = format(".%s", ending);
}
return CombineConfiguration.builder()
.templateDir(templateDir)
.templateFileEnding(ending)
.outputDir(outputDir)
.combineItemFile(getCombineItemsFile())
.pluginTagCategory(pluginTagCategory)
.versionFilterTagCategory(versionFilterTagCategory)
.emptyExamplesTags(emptyExamplesTags)
.defaultExamplesTags(defaultExamplesTags)
.skipTags(skipTags.stream().map(String::toLowerCase).collect(Collectors.toList()))
.filterConfiguration(filterConfiguration)
.projectFilters(projectFilters)
.breakIfTableToSmall(breakIfTableToSmall)
.minTableSize(minTableSize)
.breakIfMinimalTableError(breakIfMinimalTableError)
.softFilterToHardFilter(softFilterToHardFilter)
.countExecutions(countExecutions)
.countExecutionsFormat(countExecutionsFormat)
.build();
}
@SneakyThrows
public static void copyFiles(String from, String to, String ending) {
File sourceDirectory = new File(from);
File destinationDirectory = new File(to);
IOFileFilter fileFilter = suffixFileFilter(ending).or(DIRECTORY);
try {
copyDirectory(sourceDirectory, destinationDirectory, fileFilter);
} catch (FileNotFoundException e) {
getPluginLog().error(e);
throw new MojoExecutionException(e);
}
}
public static File stripEnding(File file, String ending) {
File dest = new File(file.getAbsolutePath().replace(ending, ""));
boolean success = file.renameTo(dest);
if (!success) {
getPluginLog()
.error("could not rename " + file.getAbsolutePath() + " to " + dest.getAbsolutePath());
}
return dest;
}
@SneakyThrows
public static Collection allFiles(String dir, String ending) {
File inputDir = new File(dir);
return listFiles(inputDir, new String[] {ending.replace(".", "")}, true);
}
public static Log getPluginLog() {
return getInstance().getLog();
}
public static void appendError(String error, ErrorType type) {
if (type == SIZE) {
tableSizeErrorLog.add(error);
} else if (type == MINIMAL_TABLE) {
minimalTableErrorLog.add(error);
} else if (type == PROPERTY) {
propertyErrorLog.add(error);
} else if (type == WARNING) {
warningsLog.add("WARNING: " + error);
}
}
public static void resetError() {
tableSizeErrorLog = new ArrayList<>();
minimalTableErrorLog = new ArrayList<>();
propertyErrorLog = new ArrayList<>();
warningsLog = new ArrayList<>();
}
public enum ErrorType {
SIZE,
MINIMAL_TABLE,
PROPERTY,
WARNING
}
}