diff --git a/common/src/main/java/com/scottlogic/deg/common/date/TemporalAdjusterGenerator.java b/common/src/main/java/com/scottlogic/deg/common/date/TemporalAdjusterGenerator.java index 8f3346ad4..6c31b62dd 100644 --- a/common/src/main/java/com/scottlogic/deg/common/date/TemporalAdjusterGenerator.java +++ b/common/src/main/java/com/scottlogic/deg/common/date/TemporalAdjusterGenerator.java @@ -44,7 +44,7 @@ public TemporalAdjusterGenerator(ChronoUnit chronoUnit, boolean workingDay) { } public TemporalAdjuster adjuster(int value) { - int adjustedValue = negated ? value : -value; + int adjustedValue = negated ? -value : value; return workingDay ? getWorkingDayAdjusterFunction(adjustedValue) : getAdjusterFunction(chronoUnit, adjustedValue); } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpec.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpec.java index 663f09ec9..013545695 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpec.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpec.java @@ -37,18 +37,16 @@ public static FieldSpec fromList(DistributedList whitelist) { public static FieldSpec fromRestriction(TypedRestrictions restrictions) { return new FieldSpec(null, restrictions, true, Collections.emptySet()); } - public static FieldSpec empty() { return new FieldSpec(null, null, true, Collections.emptySet()); } - public static FieldSpec nullOnly() { return new FieldSpec(NO_VALUES, null, true, Collections.emptySet()); } + private final boolean nullable; private final DistributedList whitelist; private final Set blacklist; - private final TypedRestrictions restrictions; private FieldSpec( diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/RowSpec.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/RowSpec.java index 36ce17a5d..a1858be20 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/RowSpec.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/RowSpec.java @@ -29,9 +29,10 @@ */ public class RowSpec { private final ProfileFields fields; + private final Map fieldToFieldSpec; - private final List relations; + private final List relations; public RowSpec(ProfileFields fields, Map fieldToFieldSpec, List relations) { @@ -59,6 +60,10 @@ public List getRelations() { return relations; } + public Map getFieldToFieldSpec() { + return fieldToFieldSpec; + } + @Override public String toString() { return Objects.toString(fieldToFieldSpec); diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AbstractDateInequalityRelation.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AbstractDateInequalityRelation.java deleted file mode 100644 index 4d450deaf..000000000 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AbstractDateInequalityRelation.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019 Scott Logic Ltd - * - * 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 com.scottlogic.deg.generator.fieldspecs.relations; - -import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.generator.fieldspecs.FieldSpec; -import com.scottlogic.deg.generator.restrictions.linear.Limit; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; - -import java.time.OffsetDateTime; - -abstract class AbstractDateInequalityRelation implements FieldSpecRelations { - private final Field main; - private final Field other; - - public AbstractDateInequalityRelation(Field main, Field other) { - this.main = main; - this.other = other; - } - - protected abstract OffsetDateTime dateTimeLimitExtractingFunction(LinearRestrictions restrictions); - - protected abstract LinearRestrictions appendValueToRestrictions(OffsetDateTime value); - - @Override - public FieldSpec reduceToRelatedFieldSpec(FieldSpec otherValue) { - OffsetDateTime value = dateTimeLimitExtractingFunction((LinearRestrictions) otherValue.getRestrictions()); - - if (value != null) { - LinearRestrictions restrictions = appendValueToRestrictions(value); - return FieldSpec.fromRestriction(restrictions); - } else { - return FieldSpec.empty(); - } - } - - @Override - public Field main() { - return main; - } - - @Override - public Field other() { - return other; - } -} diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelation.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelation.java index bfa75940c..9ad58be96 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelation.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelation.java @@ -17,35 +17,42 @@ package com.scottlogic.deg.generator.fieldspecs.relations; import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.fieldspecs.FieldSpec; import com.scottlogic.deg.generator.restrictions.linear.Limit; import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; import java.time.OffsetDateTime; +import static com.scottlogic.deg.common.util.Defaults.ISO_MAX_DATE; +import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MAX_LIMIT; import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MIN_LIMIT; -public class AfterDateRelation extends AbstractDateInequalityRelation { +public class AfterDateRelation implements FieldSpecRelations { + private final Field main; + private final Field other; private final boolean inclusive; public AfterDateRelation(Field main, Field other, boolean inclusive) { - super(main, other); + this.main = main; + this.other = other; this.inclusive = inclusive; } @Override - protected OffsetDateTime dateTimeLimitExtractingFunction(LinearRestrictions restrictions) { - if (restrictions != null) { - return restrictions.getMax(); - } else { - return null; + public FieldSpec reduceToRelatedFieldSpec(FieldSpec otherValue) { + LinearRestrictions lr = (LinearRestrictions) otherValue.getRestrictions(); + if (lr == null){ + return FieldSpec.empty(); } - } - @Override - protected LinearRestrictions appendValueToRestrictions(OffsetDateTime value) { - return LinearRestrictionsFactory.createDateTimeRestrictions(DATETIME_MIN_LIMIT, new Limit<>(value, inclusive)); + OffsetDateTime min = lr.getMin(); + if (!inclusive){ + min = lr.getGranularity().getNext(min); + } + + return FieldSpec.fromRestriction(new LinearRestrictions<>(min, ISO_MAX_DATE, lr.getGranularity())); } @Override @@ -53,4 +60,18 @@ public FieldSpecRelations inverse() { return new BeforeDateRelation(other(), main(), inclusive); } + @Override + public Field main() { + return main; + } + + @Override + public Field other() { + return other; + } + + @Override + public String toString() { + return String.format("%s is after %s%s", main(), inclusive ? "or equal to " : "", other()); + } } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelation.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelation.java index 0fff91085..98aa96f9f 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelation.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelation.java @@ -17,38 +17,62 @@ package com.scottlogic.deg.generator.fieldspecs.relations; import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.fieldspecs.FieldSpec; import com.scottlogic.deg.generator.restrictions.linear.Limit; import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; import java.time.OffsetDateTime; +import static com.scottlogic.deg.common.util.Defaults.ISO_MAX_DATE; +import static com.scottlogic.deg.common.util.Defaults.ISO_MIN_DATE; import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MAX_LIMIT; +import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MIN_LIMIT; -public class BeforeDateRelation extends AbstractDateInequalityRelation { +public class BeforeDateRelation implements FieldSpecRelations { + private final Field main; + private final Field other; private final boolean inclusive; public BeforeDateRelation(Field main, Field other, boolean inclusive) { - super(main, other); + this.main = main; + this.other = other; this.inclusive = inclusive; } @Override - public OffsetDateTime dateTimeLimitExtractingFunction(LinearRestrictions restrictions) { - if (restrictions != null) { - return restrictions.getMin(); - } else { - return null; + public FieldSpec reduceToRelatedFieldSpec(FieldSpec otherValue) { + LinearRestrictions lr = (LinearRestrictions) otherValue.getRestrictions(); + if (lr == null){ + return FieldSpec.empty(); } + + OffsetDateTime max = lr.getMax(); + if (!inclusive){ + max = lr.getGranularity().getPrevious(max); + } + + return FieldSpec.fromRestriction(new LinearRestrictions<>(ISO_MIN_DATE, max, lr.getGranularity())); + } + + + @Override + public Field main() { + return main; } @Override - protected LinearRestrictions appendValueToRestrictions(OffsetDateTime value) { - return LinearRestrictionsFactory.createDateTimeRestrictions(new Limit<>(value, inclusive), DATETIME_MAX_LIMIT); + public Field other() { + return other; } @Override public FieldSpecRelations inverse() { return new AfterDateRelation(other(), main(), inclusive); } + + @Override + public String toString() { + return String.format("%s is before %s%s", main(), inclusive ? "or equal to " : "", other()); + } } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/DataBagGroupWrapper.java b/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/DataBagGroupWrapper.java deleted file mode 100644 index 8509899b3..000000000 --- a/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/DataBagGroupWrapper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 Scott Logic Ltd - * - * 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 com.scottlogic.deg.generator.generation.databags; - -import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; -import com.scottlogic.deg.generator.generation.FieldSpecValueGenerator; - -public final class DataBagGroupWrapper { - - private final DataBag dataBag; - private final FieldSpecGroup group; - private final FieldSpecValueGenerator generator; - - public DataBagGroupWrapper(DataBag databag, - FieldSpecGroup group, - FieldSpecValueGenerator generator) { - this.dataBag = databag; - this.group = group; - this.generator = generator; - } - - public DataBag dataBag() { - return dataBag; - } - - public FieldSpecValueGenerator generator() { - return generator; - } - - public FieldSpecGroup group() { - return group; - } - -} diff --git a/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGenerator.java b/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGenerator.java index ba3680851..573560e3a 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGenerator.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGenerator.java @@ -17,55 +17,32 @@ package com.scottlogic.deg.generator.generation.databags; import com.google.inject.Inject; -import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.generator.fieldspecs.FieldSpec; -import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; import com.scottlogic.deg.generator.fieldspecs.RowSpec; -import com.scottlogic.deg.generator.fieldspecs.relations.FieldSpecRelations; -import com.scottlogic.deg.generator.generation.grouped.FieldGroup; +import com.scottlogic.deg.generator.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.deg.generator.generation.grouped.FieldSpecGroupValueGenerator; -import com.scottlogic.deg.generator.generation.FieldSpecValueGenerator; import com.scottlogic.deg.generator.generation.grouped.RowSpecGrouper; -import com.scottlogic.deg.generator.generation.combinationstrategies.CombinationStrategy; -import java.util.Map; -import java.util.*; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; public class RowSpecDataBagGenerator { - private final FieldSpecValueGenerator generator; + private final FieldSpecGroupValueGenerator generator; private final CombinationStrategy combinationStrategy; @Inject public RowSpecDataBagGenerator( - FieldSpecValueGenerator generator, + FieldSpecGroupValueGenerator generator, CombinationStrategy combinationStrategy) { this.generator = generator; this.combinationStrategy = combinationStrategy; } public Stream createDataBags(RowSpec rowSpec) { - Stream>> dataBagsForGroups = RowSpecGrouper.createGroups(rowSpec).stream() - .map(group -> () -> generateDataForGroup(rowSpec, group)); + Stream>> dataBagsForGroups = + RowSpecGrouper.createGroups(rowSpec).stream() + .map(group -> () -> generator.generate(group)); return combinationStrategy.permute(dataBagsForGroups); } - private Stream generateDataForGroup(RowSpec rowSpec, FieldGroup group) { - List fields = group.fields(); - List relations = rowSpec.getRelations().stream() - .filter(relation -> fields.contains(relation.main()) || fields.contains(relation.other())) - .collect(Collectors.toList()); - - Map fieldSpecMap = fields.stream() - .collect(Collectors.toMap(field -> field, rowSpec::getSpecForField)); - - FieldSpecGroup specGroup = new FieldSpecGroup(fieldSpecMap, relations); - - FieldSpecGroupValueGenerator groupGenerator = new FieldSpecGroupValueGenerator(generator); - - return groupGenerator.generate(specGroup); - } } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupDateHelper.java b/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupDateHelper.java deleted file mode 100644 index c7fec12ec..000000000 --- a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupDateHelper.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.scottlogic.deg.generator.generation.grouped; - -import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.generator.fieldspecs.FieldSpec; -import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; -import com.scottlogic.deg.generator.fieldspecs.FieldSpecMerger; -import com.scottlogic.deg.generator.fieldspecs.FieldWithFieldSpec; -import com.scottlogic.deg.generator.fieldspecs.relations.FieldSpecRelations; -import com.scottlogic.deg.generator.restrictions.linear.Limit; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; - -import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class FieldSpecGroupDateHelper { - - public static FieldSpecGroup adjustBoundsOfDate(Field field, OffsetDateTime value, FieldSpecGroup group) { - - Limit limit = new Limit<>(value, true); - LinearRestrictions restrictions = LinearRestrictionsFactory.createDateTimeRestrictions(limit,limit); - FieldSpec newSpec = FieldSpec.fromRestriction(restrictions).withNotNull(); - - return adjustBoundsOfDateFromFieldSpec(field, newSpec, group); - } - - private static FieldSpecGroup adjustBoundsOfDateFromFieldSpec(Field field, FieldSpec newSpec, FieldSpecGroup group) { - - Map specs = new HashMap<>(group.fieldSpecs()); - specs.replace(field, newSpec); - - Set relations = group.relations().stream() - .filter(relation -> relation.main().equals(field) || relation.other().equals(field)) - .collect(Collectors.toSet()); - Stream relationsOrdered = relations.stream() - .map(relation -> relation.main().equals(field) ? relation : relation.inverse()) - .map(relation -> new FieldWithFieldSpec(relation.other(), relation.reduceToRelatedFieldSpec(newSpec))); - - relationsOrdered.forEach( - wrapper -> applyToFieldSpecMap( - specs, - specs.get(wrapper.field()), - wrapper.fieldSpec(), - wrapper.field())); - return new FieldSpecGroup(specs, relations); - } - - private static void applyToFieldSpecMap(Map map, FieldSpec left, FieldSpec right, Field field) { - FieldSpecMerger merger = new FieldSpecMerger(); - - FieldSpec newSpec = merger.merge(left, right) - .orElseThrow(() -> new IllegalArgumentException("Failed to create field spec from value")); - map.put(field, newSpec); - } -} diff --git a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGenerator.java b/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGenerator.java index 6b08c094d..eeeb8a25c 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGenerator.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGenerator.java @@ -17,172 +17,177 @@ package com.scottlogic.deg.generator.generation.grouped; +import com.google.inject.Inject; import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.common.util.FlatMappingSpliterator; +import com.scottlogic.deg.generator.config.detail.CombinationStrategyType; import com.scottlogic.deg.generator.fieldspecs.FieldSpec; import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; import com.scottlogic.deg.generator.fieldspecs.FieldSpecMerger; import com.scottlogic.deg.generator.fieldspecs.relations.FieldSpecRelations; -import com.scottlogic.deg.generator.generation.FieldPair; import com.scottlogic.deg.generator.generation.FieldSpecValueGenerator; import com.scottlogic.deg.generator.generation.databags.*; +import com.scottlogic.deg.generator.restrictions.linear.Limit; +import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; +import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; import com.scottlogic.deg.generator.utils.SetUtils; import java.time.OffsetDateTime; import java.util.*; +import java.util.function.BinaryOperator; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.scottlogic.deg.generator.generation.grouped.FieldSpecGroupDateHelper.adjustBoundsOfDate; +import static com.scottlogic.deg.common.util.FlatMappingSpliterator.flatMap; public class FieldSpecGroupValueGenerator { + private final CombinationStrategyType combinationStrategy; private final FieldSpecValueGenerator underlyingGenerator; + private final FieldSpecMerger fieldSpecMerger = new FieldSpecMerger(); - public FieldSpecGroupValueGenerator(FieldSpecValueGenerator underlyingGenerator) { + @Inject + public FieldSpecGroupValueGenerator(FieldSpecValueGenerator underlyingGenerator, CombinationStrategyType combinationStrategy) { this.underlyingGenerator = underlyingGenerator; + this.combinationStrategy = combinationStrategy; } public Stream generate(FieldSpecGroup group) { - Field first = SetUtils.firstIteratorElement(group.fieldSpecs().keySet()); + Field first = getFirst(group); - FieldSpecGroup groupRespectingFirstField = initialAdjustments(first, group); - FieldSpec firstSpec = groupRespectingFirstField.fieldSpecs().get(first); + if (group.fieldSpecs().size() == 1){ + return underlyingGenerator.generate(first, group.fieldSpecs().get(first)) + .map(val -> toDataBag(first, val)); + } + + FieldSpec firstSpec = updateFirstSpecFromRelations(first, group); Stream firstDataBagValues = underlyingGenerator.generate(first, firstSpec) .map(value -> toDataBag(first, value)); - return createRemainingDataBags(firstDataBagValues, first, groupRespectingFirstField); + return flatMap( + firstDataBagValues, + dataBag -> generateRemainingData(first, dataBag, removeSpecFromGroup(first, group))); } - private static DataBag toDataBag(Field field, DataBagValue value) { - Map map = new HashMap<>(); - map.put(field, value); - return new DataBag(map); + private Field getFirst(FieldSpecGroup keySet) { + Map otherCount = keySet.relations().stream().collect(Collectors.toMap( + FieldSpecRelations::other, + r -> 1, Integer::sum)); + return otherCount.keySet().stream().reduce((l,r)->otherCount.get(l)>otherCount.get(r)?l:r) + .orElse(keySet.fieldSpecs().keySet().iterator().next()); } - private static FieldSpecGroup initialAdjustments(Field first, FieldSpecGroup group) { - checkOnlyPairwiseRelationsExist(group.relations()); - - Map mutatingSpecs = new HashMap<>(group.fieldSpecs()); - - for (FieldSpecRelations relation : group.relations()) { - FieldSpec merged = createMergedSpecFromRelation(first, relation, group) - .orElseThrow(() -> new IllegalStateException("Failed to merge field specs in related fields")); - mutatingSpecs.replace(first, merged); - } + private FieldSpec updateFirstSpecFromRelations(Field first, FieldSpecGroup group) { + FieldSpec firstFieldSpec = group.fieldSpecs().get(first); - return new FieldSpecGroup(mutatingSpecs, group.relations()); + return group.relations().stream() + .filter(relation -> isRelatedToField(first, relation)) + .map(relation -> createMergedSpecFromRelation(first, relation, group)) + .map(Optional::of) + .reduce(Optional.of(firstFieldSpec), this::mergeOptionalFieldspecs) + .orElseThrow(() -> new IllegalStateException("Failed to merge field specs in related fields")); } - private static Optional createMergedSpecFromRelation(Field first, - FieldSpecRelations relation, - FieldSpecGroup group) { - Field other = relation.main().equals(first) ? relation.other() : relation.main(); - - FieldSpecMerger merger = new FieldSpecMerger(); - - FieldSpec reduced = relation.inverse().reduceToRelatedFieldSpec(group.fieldSpecs().get(other)); - return merger.merge(reduced, group.fieldSpecs().get(first)); + private Optional mergeOptionalFieldspecs(Optional left, Optional right) { + return left.flatMap( + (leftFieldSpec) -> fieldSpecMerger.merge(leftFieldSpec, right.get())); } - private static void checkOnlyPairwiseRelationsExist(Collection relations) { - Set pairs = new HashSet<>(); - Set usedFields = new HashSet<>(); - for (FieldSpecRelations relation : relations) { - FieldPair pair = new FieldPair(relation.main(), relation.other()); - if (!pairs.contains(pair) && - (usedFields.contains(relation.main()) || usedFields.contains(relation.other()))) { - throw new UnsupportedOperationException("Using more than two fields in a related dependency" - + " is currently unsupported."); - } - pairs.add(pair); - usedFields.add(relation.main()); - usedFields.add(relation.other()); - } + private boolean isRelatedToField(Field field, FieldSpecRelations relation) { + return relation.main().equals(field) || relation.other().equals(field); } - private static FieldSpecGroup adjustBounds(Field field, DataBagValue value, FieldSpecGroup group) { - Object object = value.getValue(); - - if (object instanceof OffsetDateTime) { - return adjustBoundsOfDate(field, (OffsetDateTime) object, group); + private FieldSpec createMergedSpecFromRelation(Field first, + FieldSpecRelations relation, + FieldSpecGroup group) { + if (relation.main().equals(first)){ + FieldSpec otherFieldSpec = group.fieldSpecs().get(relation.other()); + return relation.reduceToRelatedFieldSpec(otherFieldSpec); + } + else { + FieldSpec otherFieldSpec = group.fieldSpecs().get(relation.main()); + return relation.inverse().reduceToRelatedFieldSpec(otherFieldSpec); } - - return group; } + private Stream generateRemainingData(Field generatedField, DataBag dataBag, FieldSpecGroup group) { + FieldSpecGroup newGroup = adjustBounds(generatedField, dataBag.getDataBagValue(generatedField), group); - private Stream createRemainingDataBags(Stream stream, Field field, FieldSpecGroup group) { - Stream initial = stream - .map(dataBag -> new DataBagGroupWrapper(dataBag, group, underlyingGenerator)) - .map(wrapper -> adjustWrapperBounds(wrapper, field)); - Set toProcess = filterFromSet(group.fieldSpecs().keySet(), field); - - Stream wrappedStream = recursiveMap(initial, toProcess); + Stream dataBagStream = generate(newGroup) + .map(otherData -> DataBag.merge(dataBag, otherData)); - return wrappedStream.map(DataBagGroupWrapper::dataBag); + return applyCombinationStrategy(dataBagStream); } - private static DataBagGroupWrapper adjustWrapperBounds(DataBagGroupWrapper wrapper, Field field) { - DataBagValue value = wrapper.dataBag().getDataBagValue(field); - FieldSpecGroup newGroup = adjustBounds(field, value, wrapper.group()); - return new DataBagGroupWrapper(wrapper.dataBag(), newGroup, wrapper.generator()); + private FieldSpecGroup adjustBounds(Field generatedField, DataBagValue generatedValue, FieldSpecGroup group) { + if (generatedValue.getValue() instanceof OffsetDateTime) { - } + Limit limit = new Limit<>((OffsetDateTime)generatedValue.getValue(), true); + LinearRestrictions restrictions = LinearRestrictionsFactory.createDateTimeRestrictions(limit,limit); + FieldSpec newSpec = FieldSpec.fromRestriction(restrictions).withNotNull(); - private static Stream recursiveMap(Stream wrapperStream, - Set fieldsToProcess) { - if (fieldsToProcess.isEmpty()) { - return wrapperStream; + return adjustBoundsOfDate(generatedField, newSpec, group); } - - Field field = SetUtils.firstIteratorElement(fieldsToProcess); - - Stream mappedStream = - FlatMappingSpliterator.flatMap(wrapperStream, wrapper -> acceptNextValue(wrapper, field)); - - Set remainingFields = filterFromSet(fieldsToProcess, field); - - return recursiveMap(mappedStream, remainingFields); + return group; } - private static Set filterFromSet(Set original, T element) { - return original.stream() - .filter(f -> !f.equals(element)) + private FieldSpecGroup adjustBoundsOfDate(Field generatedField, FieldSpec generatedValue, FieldSpecGroup group) { + Set nonUpdatedRelations = group.relations().stream() + .filter(relation -> !isRelatedToField(generatedField, relation)) .collect(Collectors.toSet()); - } - private static Stream acceptNextValue(DataBagGroupWrapper wrapper, Field field) { - if (wrapper.generator().isRandom()) { - return Stream.of(acceptNextRandomValue(wrapper, field)); - } else { - return acceptNextNonRandomValue(wrapper, field); - } - } + List updatableRelations = group.relations().stream() + .filter(relation -> isRelatedToField(generatedField, relation)) + .map(relations -> relations.other().equals(generatedField) ? relations : relations.inverse()) + .collect(Collectors.toList()); - private static DataBagGroupWrapper acceptNextRandomValue(DataBagGroupWrapper wrapper, Field field) { - FieldSpecGroup group = wrapper.group(); + Map fieldUpdates = updatableRelations.stream() + .collect(Collectors.toMap( + relation -> relation.main(), + relation -> relation.reduceToRelatedFieldSpec(generatedValue), + (l, r) -> fieldSpecMerger.merge(l, r) + .orElseThrow(() -> new IllegalStateException("Failed to merge field specs in related fields")))); - DataBagValue nextValue = wrapper.generator().generate(field, group.fieldSpecs().get(field)).findFirst().get(); + Map newFieldSpecs = group.fieldSpecs().entrySet().stream() + .collect(Collectors.toMap( + e -> e.getKey(), + e -> updateSpec(e.getKey(), e.getValue(), fieldUpdates))); - DataBag combined = DataBag.merge(toDataBag(field, nextValue), wrapper.dataBag()); + return new FieldSpecGroup(newFieldSpecs, nonUpdatedRelations); + } - FieldSpecGroup newGroup = adjustBounds(field, nextValue, group); + private FieldSpec updateSpec(Field key, FieldSpec previous, Map fieldUpdates){ + if (!fieldUpdates.containsKey(key)){ + return previous; + } - return new DataBagGroupWrapper(combined, newGroup, wrapper.generator()); + return fieldSpecMerger.merge(previous, fieldUpdates.get(key)) + .orElseThrow(() -> + new IllegalStateException("Failed to merge field specs in related fields")); } - private static Stream acceptNextNonRandomValue(DataBagGroupWrapper wrapper, Field field) { - return wrapper.generator() - .generate(field, wrapper.group().fieldSpecs().get(field)) - .map(value -> getWrappedDataBag(wrapper, field, value)); + private Stream applyCombinationStrategy(Stream dataBagStream) { + switch (combinationStrategy) { + case EXHAUSTIVE: + return dataBagStream; + case MINIMAL: + case PINNING: + return dataBagStream.limit(1); + default: + throw new UnsupportedOperationException("no combination strategy provided"); + } } - private static DataBagGroupWrapper getWrappedDataBag(DataBagGroupWrapper wrapper, Field field, DataBagValue value) { - DataBag dataBag = toDataBag(field, value); - DataBag merged = DataBag.merge(dataBag, wrapper.dataBag()); + private DataBag toDataBag(Field field, DataBagValue value) { + Map map = new HashMap<>(); + map.put(field, value); + return new DataBag(map); + } - return new DataBagGroupWrapper(merged, adjustBounds(field, value, wrapper.group()), wrapper.generator()); + private FieldSpecGroup removeSpecFromGroup(Field first, FieldSpecGroup group) { + HashMap newFieldSpecs = new HashMap<>(group.fieldSpecs()); + newFieldSpecs.remove(first); + return new FieldSpecGroup(newFieldSpecs, group.relations()); } } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouper.java b/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouper.java index e82af3179..140166027 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouper.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouper.java @@ -17,7 +17,10 @@ package com.scottlogic.deg.generator.generation.grouped; import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.fieldspecs.FieldSpec; +import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; import com.scottlogic.deg.generator.fieldspecs.RowSpec; +import com.scottlogic.deg.generator.fieldspecs.relations.FieldSpecRelations; import com.scottlogic.deg.generator.generation.FieldPair; import com.scottlogic.deg.generator.generation.grouped.FieldGroup; import com.scottlogic.deg.generator.utils.SetUtils; @@ -26,13 +29,14 @@ import java.util.stream.Collectors; public class RowSpecGrouper { - - public static Set createGroups(RowSpec rowSpec) { + public static Set createGroups(RowSpec rowSpec) { List pairs = rowSpec.getRelations().stream() .map(relation -> new FieldPair(relation.main(), relation.other())) .collect(Collectors.toList()); - return findGroups(rowSpec.getFields().asList(), pairs); + return findGroups(rowSpec.getFields().asList(), pairs) + .stream().map(fs->createFieldSpecGroups(fs.fields(), rowSpec)) + .collect(Collectors.toSet()); } private static Set findGroups(List fields, List pairs) { @@ -52,6 +56,7 @@ private static Set findGroups(List fields, List pa } // This method is recursive + private static Set findGroupsFromMap(Map> map) { if (map.isEmpty()) { return new HashSet<>(); @@ -101,6 +106,17 @@ private static Set findGroupRecursive(Deque fieldsToSearch, return findGroupRecursive(toProcessCopy, newFound, map); } + private static FieldSpecGroup createFieldSpecGroups(List fields, RowSpec rowSpec){ + List relations = rowSpec.getRelations().stream() + .filter(relation -> fields.contains(relation.main()) || fields.contains(relation.other())) + .collect(Collectors.toList()); + + Map fieldSpecMap = fields.stream() + .collect(Collectors.toMap(field -> field, rowSpec::getSpecForField)); + + return new FieldSpecGroup(fieldSpecMap, relations); + } + private static void addToBoth(T element, Collection first, Collection second) { first.add(element); second.add(element); diff --git a/generator/src/main/java/com/scottlogic/deg/generator/guice/GeneratorModule.java b/generator/src/main/java/com/scottlogic/deg/generator/guice/GeneratorModule.java index 0684ae947..4e0245030 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/guice/GeneratorModule.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/guice/GeneratorModule.java @@ -19,6 +19,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.name.Names; +import com.scottlogic.deg.generator.config.detail.CombinationStrategyType; import com.scottlogic.deg.generator.config.detail.DataGenerationType; import com.scottlogic.deg.generator.generation.*; import com.scottlogic.deg.generator.generation.combinationstrategies.CombinationStrategy; @@ -54,6 +55,7 @@ protected void configure() { // bind config directly bind(DataGenerationType.class).toInstance(generationConfigSource.getGenerationType()); + bind(CombinationStrategyType.class).toInstance(generationConfigSource.getCombinationStrategyType()); bind(long.class) .annotatedWith(Names.named("config:maxRows")) diff --git a/generator/src/test/java/com/scottlogic/deg/generator/builders/DataBagBuilder.java b/generator/src/test/java/com/scottlogic/deg/generator/builders/DataBagBuilder.java index 3f2488b41..08f1d1adc 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/builders/DataBagBuilder.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/builders/DataBagBuilder.java @@ -46,4 +46,8 @@ public DataBagBuilder set(Field field, Object value) { public DataBag build() { return new DataBag(fieldToValue); } + + public static DataBag of(Field field, Object value){ + return new DataBagBuilder().set(field, value).build(); + } } diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java deleted file mode 100644 index 1ac38a816..000000000 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019 Scott Logic Ltd - * - * 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 com.scottlogic.deg.generator.fieldspecs.relations; - -import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.generator.fieldspecs.FieldSpec; -import com.scottlogic.deg.generator.restrictions.linear.Limit; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; -import org.junit.jupiter.api.Test; - -import java.time.OffsetDateTime; -import java.time.ZoneOffset; - -import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MIN_LIMIT; -import static org.junit.jupiter.api.Assertions.*; -import static com.scottlogic.deg.common.profile.FieldBuilder.createField; - -class AfterDateRelationTest { - - @Test - public void reduceToRelatedFieldSpec_comparingTwoFields_givesALaterFieldSpec() { - Field first = createField("first"); - Field second = createField("second"); - - FieldSpecRelations relation = new AfterDateRelation(first, second, true); - - Limit lower = new Limit<>( - OffsetDateTime.of( - 2005, - 6, - 3, - 0, - 0, - 0, - 0, - ZoneOffset.UTC), - true); - - Limit upper = new Limit<>( - OffsetDateTime.of( - 2006, - 6, - 3, - 0, - 0, - 0, - 0, - ZoneOffset.UTC), - true); - - LinearRestrictions inRestrictions = LinearRestrictionsFactory.createDateTimeRestrictions(lower, upper); - - FieldSpec inSpec = FieldSpec.fromRestriction(inRestrictions); - - FieldSpec reducedSpec = relation.reduceToRelatedFieldSpec(inSpec); - - LinearRestrictions expectedRestrictions = LinearRestrictionsFactory.createDateTimeRestrictions(DATETIME_MIN_LIMIT, upper); - FieldSpec expectedSpec = FieldSpec.fromRestriction(expectedRestrictions); - - assertEquals(expectedSpec, reducedSpec); - } - -} \ No newline at end of file diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java deleted file mode 100644 index 87bc25616..000000000 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2019 Scott Logic Ltd - * - * 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 com.scottlogic.deg.generator.fieldspecs.relations; - - import com.scottlogic.deg.common.profile.Field; - import com.scottlogic.deg.generator.fieldspecs.FieldSpec; - import com.scottlogic.deg.generator.restrictions.linear.Limit; - import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; - import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; - import org.junit.jupiter.api.Test; - - import java.time.OffsetDateTime; - import java.time.ZoneOffset; - - import static com.scottlogic.deg.generator.utils.Defaults.DATETIME_MAX_LIMIT; - import static org.junit.jupiter.api.Assertions.*; - import static com.scottlogic.deg.common.profile.FieldBuilder.createField; - -class BeforeDateRelationTest { - - @Test - public void reduceToRelatedFieldSpec_comparingTwoFields_givesAnEarlierFieldSpec() { - Field first = createField("first"); - Field second = createField("second"); - - FieldSpecRelations relation = new BeforeDateRelation(first, second, true); - - Limit lower = new Limit<>( - OffsetDateTime.of( - 2005, - 6, - 3, - 0, - 0, - 0, - 0, - ZoneOffset.UTC), - true); - - Limit upper = new Limit<>( - OffsetDateTime.of( - 2006, - 6, - 3 - ,0, - 0, - 0, - 0, - ZoneOffset.UTC), - true); - - LinearRestrictions inRestrictions = LinearRestrictionsFactory.createDateTimeRestrictions(lower, upper); - - FieldSpec inSpec = FieldSpec.fromRestriction(inRestrictions); - - FieldSpec reducedSpec = relation.reduceToRelatedFieldSpec(inSpec); - - LinearRestrictions expectedRestrictions = LinearRestrictionsFactory.createDateTimeRestrictions(lower, DATETIME_MAX_LIMIT); - FieldSpec expectedSpec = FieldSpec.fromRestriction(expectedRestrictions); - - assertEquals(expectedSpec, reducedSpec); - } - -} diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToOffsetDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToOffsetDateRelationTest.java deleted file mode 100644 index 4a418211d..000000000 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToOffsetDateRelationTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.scottlogic.deg.generator.fieldspecs.relations; - -import com.scottlogic.deg.common.date.TemporalAdjusterGenerator; -import com.scottlogic.deg.common.profile.Field; -import com.scottlogic.deg.generator.fieldspecs.FieldSpec; -import com.scottlogic.deg.generator.restrictions.linear.Limit; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; -import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; -import org.junit.jupiter.api.Test; - -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; - -import static org.junit.jupiter.api.Assertions.*; -import static com.scottlogic.deg.common.profile.FieldBuilder.createField; -import static com.shazam.shazamcrest.MatcherAssert.assertThat; -import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs; - -class EqualToOffsetDateRelationTest { - - @Test - public void reduceToRelatedFieldSpec_comparingTwoFields_givesEquivalentFieldSpec() { - Field first = createField("first"); - Field second = createField("second"); - - TemporalAdjusterGenerator wrapper = new TemporalAdjusterGenerator(ChronoUnit.DAYS, false); - int days = 3; - - FieldSpecRelations relation = new EqualToOffsetDateRelation(first, second, wrapper, 3); - - OffsetDateTime exactTime = OffsetDateTime.of( - 2005, - 3, - 4, - 5, - 6, - 7, - 0, - ZoneOffset.UTC); - - FieldSpec initialSpec = specEqualToTime(exactTime); - - FieldSpec expectedSpec = specEqualToTime(exactTime.minusDays(days)); - - FieldSpec newSpec = relation.reduceToRelatedFieldSpec(initialSpec); - - assertThat(expectedSpec, sameBeanAs(newSpec)); - } - - - - @Test - void reduceToRelatedFieldSpec_comparingTwoFieldsNegativeCase_givesEquivalentFieldSpec() { - Field first = createField("first"); - Field second = createField("second"); - - int days = -3; - - TemporalAdjusterGenerator wrapper = new TemporalAdjusterGenerator(ChronoUnit.DAYS, false); - - FieldSpecRelations relation = new EqualToOffsetDateRelation(first, second, wrapper, days); - - OffsetDateTime exactTime = OffsetDateTime.of( - 2005, - 3, - 5, - 5, - 6, - 7, - 0, - ZoneOffset.UTC); - - FieldSpec initialSpec = specEqualToTime(exactTime); - - FieldSpec expectedSpec = specEqualToTime(exactTime.plusDays(Math.abs(days))); - - FieldSpec newSpec = relation.reduceToRelatedFieldSpec(initialSpec); - - assertEquals(expectedSpec, newSpec); - } - - private static FieldSpec specEqualToTime(OffsetDateTime time) { - Limit limit = new Limit<>(time, true); - - LinearRestrictions restrictions = LinearRestrictionsFactory.createDateTimeRestrictions(limit, limit); - return FieldSpec.fromRestriction(restrictions); - } -} \ No newline at end of file diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/FieldSpecRelationsTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/FieldSpecRelationsTest.java new file mode 100644 index 000000000..c21464c47 --- /dev/null +++ b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/FieldSpecRelationsTest.java @@ -0,0 +1,133 @@ +package com.scottlogic.deg.generator.fieldspecs.relations; + +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.common.profile.Types; +import com.scottlogic.deg.common.profile.constraintdetail.Timescale; +import com.scottlogic.deg.generator.fieldspecs.FieldSpec; +import com.scottlogic.deg.generator.restrictions.linear.Limit; +import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictions; +import com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import static com.scottlogic.deg.common.profile.FieldBuilder.createField; +import static com.scottlogic.deg.common.util.Defaults.ISO_MAX_DATE; +import static com.scottlogic.deg.common.util.Defaults.ISO_MIN_DATE; +import static com.scottlogic.deg.generator.restrictions.linear.LinearRestrictionsFactory.createDateTimeRestrictions; +import static com.shazam.shazamcrest.MatcherAssert.assertThat; +import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs; + +class FieldSpecRelationsTest { + private Field main = createField("main", Types.DATETIME); + private Field other = createField("other", Types.DATETIME); + + @Test + public void equalTo_exactValue_returnsSame(){ + FieldSpec fieldSpec = forYears(2018, 2018); + EqualToDateRelation relation = new EqualToDateRelation(main, other); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fieldSpec; + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void equalTo_range_returnsSame(){ + FieldSpec fieldSpec = forYears(2018, 2020); + EqualToDateRelation relation = new EqualToDateRelation(main, other); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fieldSpec; + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void afterOrAt_exactValue_returnsBetween(){ + FieldSpec fieldSpec = forYears(2018, 2018); + AfterDateRelation relation = new AfterDateRelation(main, other, true); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMin(2018); + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void afterOrAt_range_returnsFromMin(){ + FieldSpec fieldSpec = forYears(2018, 2020); + AfterDateRelation relation = new AfterDateRelation(main, other, true); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMin(2018); + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void after_range_returnsFromMin(){ + FieldSpec fieldSpec = forYears(2018, 2021); + AfterDateRelation relation = new AfterDateRelation(main, other, false); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMin(2019); + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void beforeOrAt_exactValue_returnsBetween(){ + FieldSpec fieldSpec = forYears(2018, 2018); + BeforeDateRelation relation = new BeforeDateRelation(main, other, true); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMax(2018); + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void beforeOrAt_range_returnsFromMin(){ + FieldSpec fieldSpec = forYears(2018, 2020); + BeforeDateRelation relation = new BeforeDateRelation(main, other, true); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMax(2020); + + assertThat(actual, sameBeanAs(expected)); + } + + @Test + public void before_range_returnsFromMin(){ + FieldSpec fieldSpec = forYears(2017, 2020); + BeforeDateRelation relation = new BeforeDateRelation(main, other, false); + + FieldSpec actual = relation.reduceToRelatedFieldSpec(fieldSpec); + FieldSpec expected = fromMax(2019); + + assertThat(actual, sameBeanAs(expected)); + } + + private FieldSpec fromMin(int year) { + OffsetDateTime min = OffsetDateTime.of(year, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + LinearRestrictions restrictions = new LinearRestrictions(min, ISO_MAX_DATE, Timescale.MILLIS); + return FieldSpec.fromRestriction(restrictions); + } + + private FieldSpec fromMax(int year) { + OffsetDateTime max = OffsetDateTime.of(year, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + LinearRestrictions restrictions = new LinearRestrictions(ISO_MIN_DATE, max, Timescale.MILLIS); + return FieldSpec.fromRestriction(restrictions); + } + + private FieldSpec forYears(int minYear, int maxYear) { + OffsetDateTime min = OffsetDateTime.of(minYear, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + OffsetDateTime max = OffsetDateTime.of(maxYear, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + LinearRestrictions restrictions = new LinearRestrictions(min, max, Timescale.YEARS); + return FieldSpec.fromRestriction(restrictions).withNotNull(); + } +} \ No newline at end of file diff --git a/generator/src/test/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGeneratorTests.java b/generator/src/test/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGeneratorTests.java index 27e5a2a70..0cfaf6bdd 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGeneratorTests.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/generation/databags/RowSpecDataBagGeneratorTests.java @@ -24,6 +24,7 @@ import com.scottlogic.deg.generator.generation.FieldSpecValueGenerator; import com.scottlogic.deg.generator.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.deg.generator.generation.combinationstrategies.ExhaustiveCombinationStrategy; +import com.scottlogic.deg.generator.generation.grouped.FieldSpecGroupValueGenerator; import org.junit.jupiter.api.Test; import java.util.*; @@ -38,7 +39,7 @@ class RowSpecDataBagGeneratorTests { private CombinationStrategy exhaustiveCombinationStrategy = new ExhaustiveCombinationStrategy(); - private FieldSpecValueGenerator mockGeneratorFactory = mock(FieldSpecValueGenerator.class); + private FieldSpecGroupValueGenerator mockGeneratorFactory = mock(FieldSpecGroupValueGenerator.class); private CombinationStrategy mockCombinationStrategy = mock(CombinationStrategy.class); private Field field = createField("Field1"); @@ -49,9 +50,9 @@ class RowSpecDataBagGeneratorTests { private FieldSpec fieldSpec2 = mock(FieldSpec.class); private FieldSpec fieldSpec3 = mock(FieldSpec.class); - DataBagValue dataBagValue = new DataBagValue(field); - DataBagValue dataBagValue1 = new DataBagValue(field2); - DataBagValue dataBagValue2 = new DataBagValue(field3); + DataBag dataBagValue = DataBagBuilder.of(field, field); + DataBag dataBagValue1 = DataBagBuilder.of(field2, field2); + DataBag dataBagValue2 = DataBagBuilder.of(field3, field3); @Test void shouldCreateValuesForEachFieldSpecInRowSpec() { @@ -60,14 +61,14 @@ void shouldCreateValuesForEachFieldSpecInRowSpec() { Map map = new HashMap() {{ put(field, fieldSpec); }}; RowSpec rowSpec = new RowSpec(fields, map, Collections.emptyList()); - when(mockGeneratorFactory.generate(any(Field.class), any(FieldSpec.class))).thenReturn(Stream.of(dataBagValue)); + when(mockGeneratorFactory.generate(any())).thenReturn(Stream.of(dataBagValue)); List actual = factory.createDataBags(rowSpec) .collect(Collectors.toList()); - verify(mockGeneratorFactory, times(1)).generate(any(Field.class), eq(fieldSpec)); + verify(mockGeneratorFactory, times(1)).generate(any()); - List expected = Arrays.asList(new DataBagBuilder().set(field, dataBagValue).build()); + List expected = Arrays.asList(dataBagValue); assertThat(actual, sameBeanAs(expected)); } @@ -85,15 +86,13 @@ void factoryIsCalledForEachField() { map, Collections.emptyList()); - when(mockGeneratorFactory.generate(any(Field.class), any(FieldSpec.class))) + when(mockGeneratorFactory.generate(any())) .thenReturn(Stream.of(dataBagValue), Stream.of(dataBagValue1), Stream.of(dataBagValue2)); factory.createDataBags(rowSpec) .collect(Collectors.toList()); - verify(mockGeneratorFactory, times(1)).generate(any(Field.class), eq(fieldSpec)); - verify(mockGeneratorFactory, times(1)).generate(any(Field.class), eq(fieldSpec)); - verify(mockGeneratorFactory, times(1)).generate(any(Field.class), eq(fieldSpec)); + verify(mockGeneratorFactory, times(3)).generate(any()); } @Test diff --git a/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGeneratorTest.java b/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGeneratorTest.java index d9a96a870..900f9bbaa 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGeneratorTest.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/FieldSpecGroupValueGeneratorTest.java @@ -1,6 +1,7 @@ package com.scottlogic.deg.generator.generation.grouped; import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.config.detail.CombinationStrategyType; import com.scottlogic.deg.generator.fieldspecs.FieldSpec; import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; import com.scottlogic.deg.generator.generation.FieldSpecValueGenerator; @@ -35,7 +36,7 @@ public void generate_withGroupOfSingleField_returnsCorrectStream() { DataBagValue firstValue = new DataBagValue(result); when(underlyingGenerator.generate(any(Field.class), eq(firstSpec))).thenReturn(Stream.of(firstValue)); - FieldSpecGroupValueGenerator generator = new FieldSpecGroupValueGenerator(underlyingGenerator); + FieldSpecGroupValueGenerator generator = new FieldSpecGroupValueGenerator(underlyingGenerator, CombinationStrategyType.MINIMAL); FieldSpecGroup group = new FieldSpecGroup(specMap, Collections.emptyList()); diff --git a/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouperTest.java b/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouperTest.java index 021e8e9a9..d11970e78 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouperTest.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/generation/grouped/RowSpecGrouperTest.java @@ -20,6 +20,7 @@ import com.scottlogic.deg.common.profile.Field; import com.scottlogic.deg.common.profile.ProfileFields; import com.scottlogic.deg.generator.fieldspecs.FieldSpec; +import com.scottlogic.deg.generator.fieldspecs.FieldSpecGroup; import com.scottlogic.deg.generator.fieldspecs.RowSpec; import com.scottlogic.deg.generator.fieldspecs.relations.FieldSpecRelations; import org.junit.jupiter.api.Test; @@ -48,7 +49,7 @@ void createGroups_withTwoRelatedFields_givesOneGroupOfSizeOne() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(1, groups.size()); } @@ -67,7 +68,7 @@ void createGroups_withTwoAndOneFields_givesTwoGroups() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(2, groups.size()); } @@ -85,7 +86,7 @@ void createGroups_withThreeIndependentFields_givesThreeGroups() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(3, groups.size()); } @@ -103,7 +104,7 @@ void createGroups_withThreeCodependentFields_givesOneGroup() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(1, groups.size()); } @@ -124,7 +125,7 @@ void createGroups_withThreeRelatedFieldsWithACircularLink_givesOneGroup() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(1, groups.size()); } @@ -148,7 +149,7 @@ void createGroups_withFiveFields_correctlyGroups() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(2, groups.size()); } @@ -168,7 +169,7 @@ void createGroups_withMultipleLinksBetweenTwoFields_givesOneGroup() { RowSpec spec = new RowSpec(fields, fieldSpecMap, relations); - Set groups = RowSpecGrouper.createGroups(spec); + Set groups = RowSpecGrouper.createGroups(spec); assertEquals(1, groups.size()); } diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterField.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterField.feature deleted file mode 100644 index db6b5bea9..000000000 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterField.feature +++ /dev/null @@ -1,27 +0,0 @@ -Feature: User can specify that one date should be after another date - - Background: - Given the generation strategy is full - And there is a field foo - And foo has type "datetime" - And there is a field bar - And bar has type "datetime" - - @ignore #other field functionality is broken - Scenario: Running an "afterField" constraint allows one date to be always later than another - Given foo is after 2018-09-01T00:00:00.000Z - And the generator can generate at most 3 rows - And there is a constraint: - """ - { - "field": "bar", - "is": "after", - "otherField": "foo" - } - """ - Then the following data should be generated: - | foo | bar | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.002Z | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.003Z | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.004Z | - diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterOrAtField.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterOrAtField.feature deleted file mode 100644 index cffe18397..000000000 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/AfterOrAtField.feature +++ /dev/null @@ -1,27 +0,0 @@ -Feature: User can specify that one date should be after or equal to another date - - Background: - Given the generation strategy is full - And there is a field foo - And foo has type "datetime" - And foo is after 2018-09-01T00:00:00.000Z - And there is a field bar - And bar has type "datetime" - - @ignore #other field functionality is broken - Scenario: Running an "afterOrAtField" constraint allows one date to be always later than or equal to another - Given the generator can generate at most 3 rows - And there is a constraint: - """ - { - "field": "bar", - "is": "afterOrAt", - "otherField": "foo" - } - """ - Then the following data should be generated: - | foo | bar | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.001Z | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.002Z | - | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.003Z | - diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeField.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeField.feature deleted file mode 100644 index f6c2accdb..000000000 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeField.feature +++ /dev/null @@ -1,26 +0,0 @@ -Feature: User can specify that one date should be before another date - - Background: - Given the generation strategy is full - And there is a field foo - And foo has type "datetime" - And there is a field bar - And bar has type "datetime" - - @ignore #other field functionality is broken - Scenario: Running a "beforeField" constraint allows one date to be always earlier than another - Given the generator can generate at most 3 rows - And there is a constraint: - """ - { - "field": "foo", - "is": "before", - "otherField": "bar" - } - """ - Then the following data should be generated: - | foo | bar | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.001Z | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.002Z | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.003Z | - diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeOrAtField.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeOrAtField.feature deleted file mode 100644 index 6a906e228..000000000 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/BeforeOrAtField.feature +++ /dev/null @@ -1,26 +0,0 @@ -Feature: User can specify that one date should be before another date - - Background: - Given the generation strategy is full - And there is a field foo - And foo has type "datetime" - And there is a field bar - And bar has type "datetime" - - @ignore #other field functionality is broken - Scenario: Running a "beforeOrAtField" constraint allows one date to be always earlier than or equal to another - Given the generator can generate at most 3 rows - And there is a constraint: - """ - { - "field": "foo", - "is": "beforeOrAt", - "otherField": "bar" - } - """ - Then the following data should be generated: - | foo | bar | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.000Z | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.001Z | - | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.002Z | - diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/EqualToField.feature b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/DateTimeOtherField.feature similarity index 50% rename from orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/EqualToField.feature rename to orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/DateTimeOtherField.feature index 65e165c5e..30a82ae24 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/EqualToField.feature +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/features/operators/temporal/DateTimeOtherField.feature @@ -1,11 +1,91 @@ -Feature: User can specify that one date should be equal to another date +Feature: running datetimes related to otherfield datetimes Background: Given the generation strategy is full And there is a field foo And foo has type "datetime" + And foo is anything but null And there is a field bar And bar has type "datetime" + And bar is anything but null + And the combination strategy is exhaustive + + Scenario: Running an "afterField" constraint allows one date to be always later than another + Given foo is after 2018-09-01T00:00:00.000Z + And bar is before 2018-09-01T00:00:00.004Z + And there is a constraint: + """ + { + "field": "bar", + "is": "after", + "otherField": "foo" + } + """ + Then the following data should be generated: + | foo | bar | + | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.002Z | + | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.003Z | + | 2018-09-01T00:00:00.002Z | 2018-09-01T00:00:00.003Z | + + + Scenario: Running an "afterOrAtField" constraint allows one date to be always later than or equal to another + Given foo is after 2018-09-01T00:00:00.000Z + And bar is before 2018-09-01T00:00:00.004Z + And there is a constraint: + """ + { + "field": "bar", + "is": "afterOrAt", + "otherField": "foo" + } + """ + Then the following data should be generated: + | foo | bar | + | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.001Z | + | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.002Z | + | 2018-09-01T00:00:00.001Z | 2018-09-01T00:00:00.003Z | + | 2018-09-01T00:00:00.002Z | 2018-09-01T00:00:00.002Z | + | 2018-09-01T00:00:00.002Z | 2018-09-01T00:00:00.003Z | + | 2018-09-01T00:00:00.003Z | 2018-09-01T00:00:00.003Z | + + + Scenario: Running a "beforeField" constraint allows one date to be always earlier than another + Given the generator can generate at most 3 rows + And bar is before 0001-01-01T00:00:00.003Z + And there is a constraint: + """ + { + "field": "foo", + "is": "before", + "otherField": "bar" + } + """ + Then the following data should be generated: + | foo | bar | + | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.001Z | + | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.002Z | + | 0001-01-01T00:00:00.001Z | 0001-01-01T00:00:00.002Z | + + + Scenario: Running a "beforeOrAtField" constraint allows one date to be always earlier than or equal to another + And bar is before 0001-01-01T00:00:00.003Z + And there is a constraint: + """ + { + "field": "foo", + "is": "beforeOrAt", + "otherField": "bar" + } + """ + Then the following data should be generated: + | foo | bar | + | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.000Z | + | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.001Z | + | 0001-01-01T00:00:00.000Z | 0001-01-01T00:00:00.002Z | + | 0001-01-01T00:00:00.001Z | 0001-01-01T00:00:00.001Z | + | 0001-01-01T00:00:00.001Z | 0001-01-01T00:00:00.002Z | + | 0001-01-01T00:00:00.002Z | 0001-01-01T00:00:00.002Z | + Scenario: Running an "equalToField" constraint allows one date to be always equal to another Given foo is equal to 2018-09-01T00:00:00.000Z @@ -22,7 +102,6 @@ Feature: User can specify that one date should be equal to another date | foo | bar | | 2018-09-01T00:00:00.000Z | 2018-09-01T00:00:00.000Z | - @ignore #other field functionality is broken Scenario: Running an "equalToField" constraint allows one date to be always equal to another with a positive offset Given the generator can generate at most 1 rows And there is a constraint: @@ -39,7 +118,6 @@ Feature: User can specify that one date should be equal to another date | foo | bar | | 0001-01-01T00:00:00.000Z | 0001-01-04T00:00:00.000Z | - @ignore #other field functionality is broken Scenario: Running an "equalToField" constraint allows one date to be always equal to another with a negative offset Given foo is after 2018-01-04T00:00:00.000Z And the generator can generate at most 1 rows @@ -58,7 +136,6 @@ Feature: User can specify that one date should be equal to another date | 2018-01-04T00:00:00.001Z | 2018-01-07T00:00:00.001Z | # Results accomodate for the fact that the 5 working days include non-working days - @ignore #other field functionality is broken Scenario: Running an "equalToField" constraint allows one date to be always equal to another plus a value in working days Given the generator can generate at most 1 rows And there is a constraint: @@ -76,9 +153,9 @@ Feature: User can specify that one date should be equal to another date | 0001-01-01T00:00:00.000Z | 0001-01-08T00:00:00.000Z | # Results accomodate for the fact that the 5 working days include non-working days - @ignore #other field functionality is broken Scenario: Running an "equalToField" constraint allows one date to be always equal to another minus a value in working days Given the generator can generate at most 1 rows + And foo is after or at 0001-01-01T00:00:00.000Z And there is a constraint: """ {