com.indeed.proctor.consumer.gen.TestGroupsGenerator Maven / Gradle / Ivy
package com.indeed.proctor.consumer.gen;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.indeed.proctor.common.PayloadType;
import com.indeed.proctor.common.ProctorSpecification;
import com.indeed.proctor.common.Serializers;
import com.indeed.proctor.common.TestSpecification;
import com.indeed.proctor.common.dynamic.DynamicFilters;
import com.indeed.proctor.common.model.NameObfuscator;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Handles combining multiple proctor specs into one and building a root map for generating a
* Proctor test groups file.
*
* Note: heavily based off of the original TestGroupsJavaGenerator. populateRootMap may still
* have some code only needed for Java.
*
* @author andrewk
*/
public abstract class TestGroupsGenerator extends FreeMarkerCodeGenerator {
private static final ObjectMapper OBJECT_MAPPER = Serializers.lenient();
private static final NameObfuscator TEST_NAME_OBFUSCATOR = new NameObfuscator();
public static final String PROVIDED_CONTEXT_FILENAME = "providedcontext.json";
public static final String DYNAMIC_FILTERS_FILENAME = "dynamicfilters.json";
/**
* If a folder of split jsons defining a proctor specification is provided, this method iterates
* over the folder contents, using the individual TestDefinition jsons and a
* providedcontext.json and dynamicfilters.json to create one large temporary
* ProctorSpecification json to be used for code generation
*/
public static ProctorSpecification makeTotalSpecification(
final File inputDir, final String targetDir) throws CodeGenException {
// If no name is provided use the name of the containing folder
return makeTotalSpecification(
inputDir,
targetDir,
inputDir.getPath().substring(inputDir.getPath().lastIndexOf(File.separator) + 1)
+ "Groups.json");
}
public static ProctorSpecification makeTotalSpecification(
final File inputDir, final String targetDir, final String outputFileName)
throws CodeGenException {
final File[] dirFiles = inputDir.listFiles();
return makeTotalSpecification(Arrays.asList(dirFiles), targetDir, outputFileName);
}
/**
* Combines all input files into a total proctor specification and writes it to a file of the
* path `targetDir`/`outputFileName`.
*
* @return combined total proctor specification
*/
public static ProctorSpecification makeTotalSpecification(
final List inputFiles, final String targetDir, final String outputFileName)
throws CodeGenException {
final List providedContextFiles = new ArrayList<>();
final List dynamicFiltersFiles = new ArrayList<>();
for (final File file : inputFiles) {
if (PROVIDED_CONTEXT_FILENAME.equals(file.getName())) {
providedContextFiles.add(file);
} else if (DYNAMIC_FILTERS_FILENAME.equals(file.getName())) {
dynamicFiltersFiles.add(file);
}
}
if (providedContextFiles.size() != 1) {
throw new CodeGenException(
"Incorrect amount of "
+ PROVIDED_CONTEXT_FILENAME
+ " in specified input folder."
+ " expected 1 but "
+ providedContextFiles.size()
+ ": "
+ providedContextFiles);
}
if (dynamicFiltersFiles.size() > 1) {
throw new CodeGenException(
"Incorrect amount of "
+ DYNAMIC_FILTERS_FILENAME
+ " in specified input folder."
+ " expected 0 or 1 but "
+ dynamicFiltersFiles.size()
+ ": "
+ dynamicFiltersFiles);
}
final Map testSpec = new LinkedHashMap<>();
Map providedContext = null;
DynamicFilters dynamicFilters = null;
for (final File file : inputFiles) {
final String fileName = file.getName();
if (fileName.equals(PROVIDED_CONTEXT_FILENAME)) {
try {
providedContext = OBJECT_MAPPER.readValue(file, Map.class);
} catch (final IOException e) {
throw new CodeGenException(
"Could not read json correctly "
+ file.getAbsolutePath()
+ " for provided context",
e);
}
} else if (fileName.equals(DYNAMIC_FILTERS_FILENAME)) {
try {
dynamicFilters = OBJECT_MAPPER.readValue(file, DynamicFilters.class);
} catch (final IOException e) {
throw new CodeGenException(
"Could not read json correctly "
+ file.getAbsolutePath()
+ " for dynamic filters",
e);
}
} else if (fileName.endsWith(".json")) {
final TestSpecification spec;
try {
spec = OBJECT_MAPPER.readValue(file, TestSpecification.class);
} catch (final IOException e) {
throw new CodeGenException(
"Could not read json correctly "
+ file.getAbsolutePath()
+ " for a test specification",
e);
}
final String specName = fileName.substring(0, fileName.indexOf(".json"));
if (testSpec.containsKey(specName)) {
throw new CodeGenException(
"Multiple "
+ fileName
+ " found, each test should only have 1 spec file");
}
testSpec.put(specName, spec);
}
}
final ProctorSpecification proctorSpecification =
new ProctorSpecification(
ObjectUtils.defaultIfNull(providedContext, new LinkedHashMap<>()),
testSpec,
ObjectUtils.defaultIfNull(dynamicFilters, new DynamicFilters()));
validateProctorSpecification(proctorSpecification);
final File output = new File(targetDir, outputFileName);
try {
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(output, proctorSpecification);
} catch (final IOException e) {
throw new CodeGenException(
"Could not write to temp file " + output.getAbsolutePath(), e);
}
return proctorSpecification;
}
/**
* Validate proctor specification at build time. Throws {@link CodeGenException} if an error is
* found.
*/
private static void validateProctorSpecification(final ProctorSpecification spec)
throws CodeGenException {
for (final Map.Entry entry : spec.getTests().entrySet()) {
final String testName = entry.getKey();
final TestSpecification testSpecification = entry.getValue();
validateTestSpecification(testName, testSpecification);
}
}
@VisibleForTesting
static void validateTestSpecification(final String testName, final TestSpecification testSpec)
throws CodeGenException {
final Set bucketValueSet = new HashSet<>();
for (final Integer bucketValue : testSpec.getBuckets().values()) {
if (bucketValue == null) {
throw new CodeGenException(
"specification of " + testName + " has null bucket value");
}
if (!bucketValueSet.add(bucketValue)) {
throw new CodeGenException(
"specification of "
+ testName
+ " has duplicated bucket value "
+ bucketValue);
}
}
}
@VisibleForTesting
@Override
Map populateRootMap(
final ProctorSpecification spec,
final Map baseContext,
final String packageName,
final String className) {
final Map rootMap = new HashMap<>(baseContext);
final Map tests = spec.getTests();
final List