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 6f6faba2e..1dc301402 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 @@ -29,6 +29,7 @@ import com.scottlogic.deg.generator.inputs.validation.ProfileValidator; import com.scottlogic.deg.generator.utils.JavaUtilRandomNumberGenerator; import com.scottlogic.deg.generator.walker.DecisionTreeWalker; +import com.scottlogic.deg.generator.walker.ReductiveWalkerRetryChecker; import com.scottlogic.deg.generator.walker.reductive.IterationVisualiser; import java.time.OffsetDateTime; @@ -70,6 +71,7 @@ protected void configure() { bind(DataGenerator.class).to(DecisionTreeDataGenerator.class); bind(DecisionTreeFactory.class).to(MaxStringLengthInjectingDecisionTreeFactory.class); bind(FieldValueSourceEvaluator.class).to(StandardFieldValueSourceEvaluator.class); + bind(ReductiveWalkerRetryChecker.class).toInstance(new ReductiveWalkerRetryChecker(10000)); bind(JavaUtilRandomNumberGenerator.class) .toInstance(new JavaUtilRandomNumberGenerator(OffsetDateTime.now().getNano())); diff --git a/generator/src/main/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalker.java b/generator/src/main/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalker.java index 81ba3ff61..3d8eef472 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalker.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalker.java @@ -19,6 +19,7 @@ import com.google.inject.Inject; import com.scottlogic.deg.generator.decisiontree.DecisionTree; import com.scottlogic.deg.generator.fieldspecs.RowSpec; +import com.scottlogic.deg.generator.generation.DataGeneratorMonitor; import com.scottlogic.deg.generator.generation.databags.DataBag; import java.util.Optional; @@ -26,10 +27,12 @@ public class RandomReductiveDecisionTreeWalker implements DecisionTreeWalker { private final ReductiveDecisionTreeWalker underlyingWalker; + private final DataGeneratorMonitor monitor; @Inject - RandomReductiveDecisionTreeWalker(ReductiveDecisionTreeWalker underlyingWalker) { + RandomReductiveDecisionTreeWalker(ReductiveDecisionTreeWalker underlyingWalker, DataGeneratorMonitor monitor) { this.underlyingWalker = underlyingWalker; + this.monitor = monitor; } @Override @@ -49,7 +52,16 @@ public Stream walk(DecisionTree tree) { } private Optional getFirstRowSpecFromRandomisingIteration(DecisionTree tree) { - return underlyingWalker.walk(tree) - .findFirst(); + try { + return underlyingWalker.walk(tree) + .findFirst(); + } catch (RetryLimitReachedException ex) { + monitor.addLineToPrintAtEndOfGeneration(""); + monitor.addLineToPrintAtEndOfGeneration("The retry limit for generating data has been hit."); + monitor.addLineToPrintAtEndOfGeneration("This may mean that a lot or all of the profile is contradictory."); + monitor.addLineToPrintAtEndOfGeneration("Either fix the profile, or try running the same command again."); + return Optional.empty(); + } + } } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalker.java b/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalker.java index 7805a6ebe..176378818 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalker.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalker.java @@ -41,6 +41,7 @@ public class ReductiveDecisionTreeWalker implements DecisionTreeWalker { private final ReductiveDataGeneratorMonitor monitor; private final FieldSpecValueGenerator fieldSpecValueGenerator; private final FixFieldStrategyFactory fixFieldStrategyFactory; + private ReductiveWalkerRetryChecker retryChecker; @Inject public ReductiveDecisionTreeWalker( @@ -49,13 +50,15 @@ public ReductiveDecisionTreeWalker( ReductiveDataGeneratorMonitor monitor, ReductiveTreePruner treePruner, FieldSpecValueGenerator fieldSpecValueGenerator, - FixFieldStrategyFactory fixFieldStrategyFactory) { + FixFieldStrategyFactory fixFieldStrategyFactory, + ReductiveWalkerRetryChecker retryChecker) { this.iterationVisualiser = iterationVisualiser; this.reductiveFieldSpecBuilder = reductiveFieldSpecBuilder; this.monitor = monitor; this.treePruner = treePruner; this.fieldSpecValueGenerator = fieldSpecValueGenerator; this.fixFieldStrategyFactory = fixFieldStrategyFactory; + this.retryChecker = retryChecker; } /* initialise the walker with a set (ReductiveState) of unfixed fields */ @@ -64,6 +67,7 @@ public Stream walk(DecisionTree tree) { ReductiveState initialState = new ReductiveState(tree.fields); visualise(tree.getRootNode(), initialState); FixFieldStrategy fixFieldStrategy = fixFieldStrategyFactory.create(tree.getRootNode()); + retryChecker.reset(); return fixNextField(tree.getRootNode(), initialState, fixFieldStrategy); } @@ -95,6 +99,7 @@ private Stream pruneTreeForNextValue( if (reducedTree.isContradictory()){ //yielding an empty stream will cause back-tracking this.monitor.unableToStepFurther(reductiveState); + retryChecker.retryUnsuccessful(); return Stream.empty(); } @@ -105,6 +110,7 @@ private Stream pruneTreeForNextValue( visualise(reducedTree.get(), newReductiveState); if (newReductiveState.allFieldsAreFixed()) { + retryChecker.retrySuccessful(); return Stream.of(newReductiveState.asDataBag()); } diff --git a/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryChecker.java b/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryChecker.java new file mode 100644 index 000000000..deaa8d0ac --- /dev/null +++ b/generator/src/main/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryChecker.java @@ -0,0 +1,25 @@ +package com.scottlogic.deg.generator.walker; + +public class ReductiveWalkerRetryChecker { + private int numRetriesSoFar = 0; + private int retryLimit; + + public ReductiveWalkerRetryChecker(int retryLimit) { + this.retryLimit = retryLimit; + } + + void retrySuccessful() { + numRetriesSoFar = 0; + } + + void retryUnsuccessful() { + numRetriesSoFar++; + if (numRetriesSoFar > retryLimit) { + throw new RetryLimitReachedException(); + } + } + + void reset() { + numRetriesSoFar = 0; + } +} diff --git a/generator/src/main/java/com/scottlogic/deg/generator/walker/RetryLimitReachedException.java b/generator/src/main/java/com/scottlogic/deg/generator/walker/RetryLimitReachedException.java new file mode 100644 index 000000000..ad7ee6435 --- /dev/null +++ b/generator/src/main/java/com/scottlogic/deg/generator/walker/RetryLimitReachedException.java @@ -0,0 +1,8 @@ +package com.scottlogic.deg.generator.walker; + +public class RetryLimitReachedException extends RuntimeException { + // This exception must be a RuntimeException to be able to break out of a stream evaluation. + public RetryLimitReachedException() { + super(); + } +} diff --git a/generator/src/test/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalkerTests.java b/generator/src/test/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalkerTests.java index 8c2c5e19f..dd0b498f0 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalkerTests.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/walker/RandomReductiveDecisionTreeWalkerTests.java @@ -20,24 +20,28 @@ import com.scottlogic.deg.common.profile.ProfileFields; import com.scottlogic.deg.generator.decisiontree.DecisionTree; import com.scottlogic.deg.generator.decisiontree.TreeConstraintNode; +import com.scottlogic.deg.generator.generation.DataGeneratorMonitor; import com.scottlogic.deg.generator.generation.databags.DataBag; import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; class RandomReductiveDecisionTreeWalkerTests { private DecisionTree tree; private RandomReductiveDecisionTreeWalker walker; private ReductiveDecisionTreeWalker underlyingWalker; + private DataGeneratorMonitor monitor = mock(DataGeneratorMonitor.class); @BeforeEach public void beforeEach(){ @@ -47,7 +51,7 @@ public void beforeEach(){ ); underlyingWalker = mock(ReductiveDecisionTreeWalker.class); - walker = new RandomReductiveDecisionTreeWalker(underlyingWalker); + walker = new RandomReductiveDecisionTreeWalker(underlyingWalker, monitor); } /** @@ -99,6 +103,32 @@ public void shouldAccommodateNoDataInSubsequentIteration() { is(false)); } + @Test + public void getFirstRowSpecFromRandomisingIteration_onRetryFail_returnsEmptyStream() { + when(underlyingWalker.walk(tree)).thenReturn( + Stream.iterate(new DataBag(new HashMap<>()), dataBag -> { + throw new RetryLimitReachedException(); + }).skip(1)); + + DataBag result = walker.walk(tree).findFirst().orElse(null); + + verify(underlyingWalker, times(1)).walk(tree); + assertNull(result); + + } + + @Test + public void getFirstRowSpecFromRandomisingIteration_onRetryFail_reportsError() { + when(underlyingWalker.walk(tree)).thenReturn( + Stream.iterate(new DataBag(new HashMap<>()), dataBag -> { + throw new RetryLimitReachedException(); + }).skip(1)); + + walker.walk(tree).findFirst(); + + verify(monitor, atLeastOnce()).addLineToPrintAtEndOfGeneration(anyString()); + } + private static DataBag rowSpec(String detail) { return mock(DataBag.class, detail); } diff --git a/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalkerTests.java b/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalkerTests.java index a5f008fe6..0849f838d 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalkerTests.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveDecisionTreeWalkerTests.java @@ -18,6 +18,8 @@ import com.scottlogic.deg.common.profile.Field; import com.scottlogic.deg.common.profile.ProfileFields; +import com.scottlogic.deg.generator.builders.ConstraintNodeBuilder; +import com.scottlogic.deg.generator.decisiontree.ConstraintNode; import com.scottlogic.deg.generator.decisiontree.DecisionTree; import com.scottlogic.deg.generator.decisiontree.TreeConstraintNode; import com.scottlogic.deg.generator.fieldspecs.FieldSpec; @@ -26,10 +28,12 @@ import com.scottlogic.deg.generator.generation.NoopDataGeneratorMonitor; import com.scottlogic.deg.generator.generation.databags.DataBag; import com.scottlogic.deg.generator.generation.databags.DataBagValue; -import com.scottlogic.deg.generator.walker.reductive.*; +import com.scottlogic.deg.generator.walker.reductive.Merged; +import com.scottlogic.deg.generator.walker.reductive.NoOpIterationVisualiser; +import com.scottlogic.deg.generator.walker.reductive.ReductiveFieldSpecBuilder; +import com.scottlogic.deg.generator.walker.reductive.ReductiveTreePruner; import com.scottlogic.deg.generator.walker.reductive.fieldselectionstrategy.FixFieldStrategy; import com.scottlogic.deg.generator.walker.reductive.fieldselectionstrategy.FixFieldStrategyFactory; -import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,7 +41,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.hamcrest.collection.IsEmptyCollection.empty; +import static java.time.Duration.ofMillis; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -49,6 +56,8 @@ class ReductiveDecisionTreeWalkerTests { private FixFieldStrategy fixFieldStrategy; private FixFieldStrategyFactory fixFieldStrategyFactory; private FieldSpecValueGenerator fieldSpecValueGenerator; + private ReductiveTreePruner treePruner; + private ReductiveWalkerRetryChecker retryChecker = new ReductiveWalkerRetryChecker(100); private Field field1 = new Field("field1"); private Field field2 = new Field("field2"); @@ -57,8 +66,6 @@ public void beforeEach(){ ProfileFields fields = new ProfileFields(Arrays.asList(field1, field2)); rootNode = new TreeConstraintNode(); tree = new DecisionTree(rootNode, fields); - ReductiveTreePruner treePruner = mock(ReductiveTreePruner.class); - when(treePruner.pruneConstraintNode(eq(rootNode), any(), any())).thenReturn(Merged.of(rootNode)); reductiveFieldSpecBuilder = mock(ReductiveFieldSpecBuilder.class); fieldSpecValueGenerator = mock(FieldSpecValueGenerator.class); @@ -66,6 +73,8 @@ public void beforeEach(){ when(fixFieldStrategy.getNextFieldToFix(any())).thenReturn(field1, field2); fixFieldStrategyFactory = mock(FixFieldStrategyFactory.class); when(fixFieldStrategyFactory.create(any())).thenReturn(fixFieldStrategy); + treePruner = mock(ReductiveTreePruner.class); + when(treePruner.pruneConstraintNode(eq(rootNode), any(), any())).thenReturn(Merged.of(rootNode)); walker = new ReductiveDecisionTreeWalker( new NoOpIterationVisualiser(), @@ -73,7 +82,8 @@ public void beforeEach(){ new NoopDataGeneratorMonitor(), treePruner, fieldSpecValueGenerator, - fixFieldStrategyFactory + fixFieldStrategyFactory, + retryChecker ); } @@ -87,7 +97,7 @@ public void shouldReturnEmptyCollectionOfRowsWhenFirstFieldCannotBeFixed() { List result = walker.walk(tree).collect(Collectors.toList()); verify(reductiveFieldSpecBuilder).getDecisionFieldSpecs(eq(rootNode), any()); - Assert.assertThat(result, empty()); + assertThat(result, empty()); } /** @@ -106,6 +116,34 @@ public void shouldReturnEmptyCollectionOfRowsWhenSecondFieldCannotBeFixed() { List result = walker.walk(tree).collect(Collectors.toList()); verify(reductiveFieldSpecBuilder, times(2)).getDecisionFieldSpecs(eq(rootNode), any()); - Assert.assertThat(result, empty()); + assertThat(result, empty()); + } + + @Test + public void walk_whereFirstFieldCannotBeFixed_throwsException() { + ProfileFields fields = new ProfileFields(Arrays.asList(field1, field2)); + FieldSpec firstFieldSpec = FieldSpec.Empty.withNotNull(); + FieldSpec secondFieldSpec = FieldSpec.Empty.withNotNull(); + Set fieldSpecs = new HashSet<>(); + fieldSpecs.add(firstFieldSpec); + fieldSpecs.add(secondFieldSpec); + ConstraintNode root = ConstraintNodeBuilder.constraintNode() + .where(field1).isNull() + .where(field1).isNotNull() + .build(); + DecisionTree tree = new DecisionTree(root, fields); + DataBagValue dataBagValue = mock(DataBagValue.class); + when(fixFieldStrategy.getNextFieldToFix(any())).thenReturn(field1, field2); + when(fixFieldStrategyFactory.create(any())).thenReturn(fixFieldStrategy); + when(treePruner.pruneConstraintNode(eq(root), any(), any())).thenReturn(Merged.of(root)); + when(reductiveFieldSpecBuilder.getDecisionFieldSpecs(any(), any())).thenReturn(fieldSpecs); + when(treePruner.pruneConstraintNode(eq(root), any(), any())).thenReturn(Merged.contradictory()); + + Stream infiniteStream = Stream.iterate(dataBagValue, i -> dataBagValue); + when(fieldSpecValueGenerator.generate(anySetOf(FieldSpec.class))).thenReturn(infiniteStream); + + assertTimeoutPreemptively(ofMillis(100), () -> { + assertThrows(RetryLimitReachedException.class, () -> walker.walk(tree).findFirst()); + }); } } \ No newline at end of file diff --git a/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryCheckerTests.java b/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryCheckerTests.java new file mode 100644 index 000000000..e76fba373 --- /dev/null +++ b/generator/src/test/java/com/scottlogic/deg/generator/walker/ReductiveWalkerRetryCheckerTests.java @@ -0,0 +1,65 @@ +package com.scottlogic.deg.generator.walker; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ReductiveWalkerRetryCheckerTests { + @Test + public void retryChecker_withRepeatedFailure_throwsException() { + //Arrange + ReductiveWalkerRetryChecker retryChecker = new ReductiveWalkerRetryChecker(2); + + //Act + retryChecker.retryUnsuccessful(); + retryChecker.retryUnsuccessful(); + + //Assert + assertThrows(RetryLimitReachedException.class, retryChecker::retryUnsuccessful); + } + + @Test + public void retryChecker_withRepeatedFailureAfterSuccess_doesNotThrowException() { + //Arrange + ReductiveWalkerRetryChecker retryChecker = new ReductiveWalkerRetryChecker(2); + + //Act + retryChecker.retryUnsuccessful(); + retryChecker.retrySuccessful(); + retryChecker.retryUnsuccessful(); + + //Assert + assertDoesNotThrow(retryChecker::retryUnsuccessful); + } + + @Test + public void reset_resetsLimit() { + //Arrange + ReductiveWalkerRetryChecker retryChecker = new ReductiveWalkerRetryChecker(2); + + //Act + retryChecker.retryUnsuccessful(); + retryChecker.retryUnsuccessful(); + retryChecker.reset(); + retryChecker.retryUnsuccessful(); + retryChecker.retryUnsuccessful(); + + //Assert + assertThrows(RetryLimitReachedException.class, retryChecker::retryUnsuccessful); + } + + @Test + public void reset_resetsGuaranteeOfSuccess() { + //Arrange + ReductiveWalkerRetryChecker retryChecker = new ReductiveWalkerRetryChecker(2); + + //Act + retryChecker.retrySuccessful(); + retryChecker.reset(); + retryChecker.retryUnsuccessful(); + retryChecker.retryUnsuccessful(); + + //Assert + assertThrows(RetryLimitReachedException.class, retryChecker::retryUnsuccessful); + } +} diff --git a/orchestrator/src/main/java/com/scottlogic/deg/orchestrator/generate/GenerateExecute.java b/orchestrator/src/main/java/com/scottlogic/deg/orchestrator/generate/GenerateExecute.java index b0352dde3..aa1be9c0e 100644 --- a/orchestrator/src/main/java/com/scottlogic/deg/orchestrator/generate/GenerateExecute.java +++ b/orchestrator/src/main/java/com/scottlogic/deg/orchestrator/generate/GenerateExecute.java @@ -17,18 +17,17 @@ package com.scottlogic.deg.orchestrator.generate; import com.google.inject.Inject; -import com.scottlogic.deg.common.ValidationException; +import com.scottlogic.deg.common.output.GeneratedObject; import com.scottlogic.deg.common.profile.Profile; import com.scottlogic.deg.generator.generation.DataGenerator; import com.scottlogic.deg.generator.generation.DataGeneratorMonitor; -import com.scottlogic.deg.common.output.GeneratedObject; -import com.scottlogic.deg.output.writer.DataSetWriter; -import com.scottlogic.deg.orchestrator.guice.AllConfigSource; import com.scottlogic.deg.generator.inputs.validation.ProfileValidator; +import com.scottlogic.deg.generator.walker.RetryLimitReachedException; +import com.scottlogic.deg.orchestrator.guice.AllConfigSource; +import com.scottlogic.deg.orchestrator.validator.ConfigValidator; import com.scottlogic.deg.output.outputtarget.SingleDatasetOutputTarget; +import com.scottlogic.deg.output.writer.DataSetWriter; import com.scottlogic.deg.profile.reader.ProfileReader; -import com.scottlogic.deg.orchestrator.validator.ConfigValidator; -import com.scottlogic.deg.generator.validators.ErrorReporter; import com.scottlogic.deg.profile.v0_1.ProfileSchemaValidator; import java.io.IOException; @@ -88,6 +87,12 @@ private void outputData(Profile profile, Stream generatedDataIt } }); } + catch (RetryLimitReachedException ignored) { + monitor.addLineToPrintAtEndOfGeneration(""); + monitor.addLineToPrintAtEndOfGeneration("The retry limit for generating data has been hit."); + monitor.addLineToPrintAtEndOfGeneration("This may mean that a lot or all of the profile is contradictory."); + monitor.addLineToPrintAtEndOfGeneration("Either fix the profile, or try running the same command again."); + } monitor.endGeneration(); } } diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/generate/GenerateExecuteTests.java b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/generate/GenerateExecuteTests.java new file mode 100644 index 000000000..89d3bea3c --- /dev/null +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/generate/GenerateExecuteTests.java @@ -0,0 +1,80 @@ +package com.scottlogic.deg.orchestrator.generate; + +import com.scottlogic.deg.common.output.GeneratedObject; +import com.scottlogic.deg.common.profile.Profile; +import com.scottlogic.deg.generator.generation.DataGenerator; +import com.scottlogic.deg.generator.generation.DataGeneratorMonitor; +import com.scottlogic.deg.generator.generation.databags.DataBag; +import com.scottlogic.deg.generator.inputs.validation.ProfileValidator; +import com.scottlogic.deg.generator.walker.RetryLimitReachedException; +import com.scottlogic.deg.orchestrator.guice.AllConfigSource; +import com.scottlogic.deg.orchestrator.validator.ConfigValidator; +import com.scottlogic.deg.output.outputtarget.SingleDatasetOutputTarget; +import com.scottlogic.deg.profile.reader.ProfileReader; +import com.scottlogic.deg.profile.v0_1.ProfileSchemaValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.atLeastOnce; + +class GenerateExecuteTests { + private AllConfigSource configSource; + private SingleDatasetOutputTarget singleDatasetOutputTarget; + private ConfigValidator configValidator; + private ProfileReader profileReader; + private DataGenerator dataGenerator; + private ProfileValidator profileValidator; + private DataGeneratorMonitor monitor; + private ProfileSchemaValidator profileSchemaValidator; + private GenerateExecute generateExecute; + private Profile profile; + + @BeforeEach + void setup() { + configSource = mock(AllConfigSource.class); + singleDatasetOutputTarget = mock(SingleDatasetOutputTarget.class); + configValidator = mock(ConfigValidator.class); + profileReader = mock(ProfileReader.class); + dataGenerator = mock(DataGenerator.class); + profileValidator = mock(ProfileValidator.class); + monitor = mock(DataGeneratorMonitor.class); + profileSchemaValidator = mock(ProfileSchemaValidator.class); + profile = mock(Profile.class); + + generateExecute = new GenerateExecute( + profileReader, + dataGenerator, + configSource, + singleDatasetOutputTarget, + configValidator, + profileValidator, + profileSchemaValidator, + monitor); + } + + + @Test + public void execute_onRetryFail_reportsError() throws IOException { + when(profileReader.read(any())).thenReturn(profile); + File file = mock(File.class); + when(configSource.getProfileFile()).thenReturn(file); + when(file.toPath()).thenReturn(mock(Path.class)); + when(dataGenerator.generateData(profile)).thenReturn( + Stream.iterate(mock(GeneratedObject.class), dataBag -> { + throw new RetryLimitReachedException(); + }).skip(1)); + + generateExecute.execute(); + + verify(monitor, atLeastOnce()).addLineToPrintAtEndOfGeneration(anyString()); + } +} \ No newline at end of file