diff --git a/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java b/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java
index e9fb4cdc50..41303e0358 100644
--- a/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java
+++ b/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java
@@ -8,6 +8,9 @@
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
@@ -93,6 +96,7 @@ public enum State {
private final String title;
private Dependencies dependencies;
private Reporting reporting = Reporting.NEVER;
+ private String[] reportPath = {};
private Writer report;
/**
@@ -136,11 +140,11 @@ public enum State {
* The path to the execution report that we're replaying data from, or
* null
if we're not replaying
*/
- private final String replaySource = Replay.source();
+ private String replaySource = Replay.source();
/**
* If we're replaying, we'll use this as the source of data for assertion
*/
- private final Replay replay = new Replay( replaySource );
+ private Replay replay = new Replay( replaySource );
/**
* Test action
@@ -190,12 +194,17 @@ protected AbstractFlocessor( String title, Model model ) {
/**
* Controls report generation
*
- * @param r Whether or not to generate a report, and whether or not to display
- * it at the conclusion of testing
+ * @param r Whether or not to generate a report, and whether or not to
+ * display it at the conclusion of testing
+ * @param path The directory path underneath the flow artifact directory in
+ * which to write the report
* @return this
*/
- public T reporting( Reporting r ) {
+ public T reporting( Reporting r, String... path ) {
reporting = r;
+ reportPath = path;
+ replaySource = Replay.source( path );
+ replay = new Replay( replaySource );
return self();
}
@@ -207,7 +216,7 @@ public T reporting( Reporting r ) {
* @return this
*/
public T masking( Unpredictable... sources ) {
- this.masks = sources.clone();
+ masks = sources.clone();
return self();
}
@@ -342,8 +351,8 @@ public T filtering( Consumer cfg ) {
* @see AssertionOptions#SUPPRESS_FILTER
*/
public T exercising( Predicate filter, Consumer rejectionLog ) {
- this.flowFilter = filter;
- this.filterRejectionLog = rejectionLog;
+ flowFilter = filter;
+ filterRejectionLog = rejectionLog;
return self();
}
@@ -937,11 +946,14 @@ private T self() {
private void report( Consumer data, boolean error ) {
if( reporting.writing() ) {
- boolean alreadyBrowsing = true;
+ Path testDir = null;
+ Path reportDir = null;
if( report == null ) {
String testTitle = title;
+ testDir = Paths.get( AssertionOptions.ARTIFACT_DIR.value(), reportPath );
+
// work out what the report directory should be called
String name = AssertionOptions.REPORT_NAME.value();
if( name == null ) {
@@ -958,14 +970,51 @@ private void report( Consumer data, boolean error ) {
// the REPORT_NAME property is the same as the REPLAY property
}
- report = new Writer( model.title(), testTitle,
- Paths.get( AssertionOptions.ARTIFACT_DIR.value() ).resolve( name ) );
- alreadyBrowsing = false;
+ reportDir = testDir.resolve( name );
+
+ report = new Writer( model.title(), testTitle, reportDir );
}
+
data.accept( report );
- if( !alreadyBrowsing && reporting.shouldOpen( error ) ) {
- report.browse();
+ if( testDir != null && reportDir != null ) {
+ // We've just created a new report! We should:
+
+ // if required, create a predictably-named link to it
+ if( !"latest".equals( reportDir.getFileName().toString() ) ) {
+ try {
+ Path linkPath = testDir.resolve( "latest" );
+ boolean shouldLink;
+ // we want to delete an existing symlink, but avoid changing any other kind of
+ // file that might exist at that path
+ if( Files.exists( linkPath, LinkOption.NOFOLLOW_LINKS ) ) {
+ if( Files.isSymbolicLink( linkPath ) ) {
+ Files.delete( linkPath );
+ shouldLink = true;
+ }
+ else {
+ shouldLink = false;
+ }
+ }
+ else {
+ shouldLink = true;
+ }
+
+ if( shouldLink ) {
+ Files.createSymbolicLink( linkPath, linkPath.getParent().relativize( reportDir ) );
+ }
+ }
+ catch( @SuppressWarnings("unused") IOException ioe ) {
+ // The symlink to the latest report is a nice-to-have. Some platforms (e.g.:
+ // windows) restrict the ability to create symlinks so we can't count on it
+ // working.
+ }
+ }
+
+ // also, if appropriate, open a browser to it
+ if( reporting.shouldOpen( error ) ) {
+ report.browse();
+ }
}
}
}
diff --git a/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java b/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java
index aaf54dc26f..5d1f45ce60 100644
--- a/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java
+++ b/assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java
@@ -1,10 +1,14 @@
package com.mastercard.test.flow.assrt;
+import static java.util.stream.Collectors.joining;
+
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Stream;
import com.mastercard.test.flow.Interaction;
import com.mastercard.test.flow.report.Reader;
@@ -52,13 +56,15 @@ public static boolean isActive() {
/**
* Works out the appropriate data source based on system properties
*
+ * @param path The elements of the path from the Flow artifact directory to
+ * where the test writes reports
* @return The path to the report that should be used as a source of data, or
* null
if there is no such report
*/
- public static String source() {
+ public static String source( String... path ) {
String src = AssertionOptions.REPLAY.value();
if( LATEST.equals( src ) ) {
- src = mostRecent();
+ src = mostRecent( path );
}
return src;
}
@@ -69,7 +75,7 @@ public static String source() {
/**
* @param source The path to the report to use as a source of data, or
* null
for an empty {@link Replay}
- * @see #source()
+ * @see #source(String...)
*/
public Replay( String source ) {
reader = source != null ? new Reader( Paths.get( source ) ) : null;
@@ -131,8 +137,14 @@ public String populate( Assertion t ) {
*
* @return The most recent execution report
*/
- private static final String mostRecent() {
- Path report = Reader.mostRecent( AssertionOptions.ARTIFACT_DIR.value(),
+ private static final String mostRecent( String... path ) {
+ Path report = Reader.mostRecent(
+ // searching in the test's report directory
+ Stream.concat(
+ Stream.of( AssertionOptions.ARTIFACT_DIR.value() ),
+ Stream.of( path ) )
+ .filter( Objects::nonNull )
+ .collect( joining( "/" ) ),
// let's stick to primary sources of data
p -> !p.getFileName().toString().endsWith( REPLAYED_SUFFIX ) );
if( report == null ) {
diff --git a/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReplayTest.java b/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReplayTest.java
index 1d370ff949..799ffefe72 100644
--- a/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReplayTest.java
+++ b/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReplayTest.java
@@ -34,7 +34,7 @@ class ReplayTest {
private static TestFlocessor build( String title, List behaviourLog ) {
return new TestFlocessor( title, TestModel.abc() )
.system( State.LESS, Actors.B )
- .reporting( QUIETLY )
+ .reporting( QUIETLY, title )
.behaviour( assrt -> {
behaviourLog.add( "exercising system with "
+ assrt.expected().request().assertable() );
@@ -45,7 +45,6 @@ private static TestFlocessor build( String title, List behaviourLog ) {
}
private static Path generateReport( String title ) {
- System.setProperty( AssertionOptions.REPORT_NAME.property(), title );
List behaviourLog = new ArrayList<>();
TestFlocessor tf = build( title, behaviourLog );
tf.execute();
@@ -79,7 +78,6 @@ private static Path generateReport( String title ) {
@AfterEach
void clearReplayProperty() {
System.clearProperty( AssertionOptions.REPLAY.property() );
- System.clearProperty( AssertionOptions.REPORT_NAME.property() );
}
/**
@@ -125,45 +123,60 @@ void noLatest() {
*/
@Test
void latest() throws Exception {
- generateReport( "older" );
- Path dataSource = generateReport( "latest" );
-
- // this one was created later but is named as a replay report, so should be
- // ignored as a source of data
- generateReport( "later_replay" );
-
- // this one was also created later but lacks detail files, so should also be
- // ignored as a source of data
- Path noDetails = generateReport( "no_detail" );
- QuietFiles.recursiveDelete( noDetails.resolve( "detail" ) );
-
- // this one was also created later but lacks an index, so should also be
- // ignored as a source of data
- Path noIndex = generateReport( "no_index" );
- QuietFiles.recursiveDelete( noIndex.resolve( "index.html" ) );
-
- // activate replay mode
- System.setProperty( AssertionOptions.REPLAY.property(), Replay.LATEST );
-
- // run again
- TestFlocessor tf = build( "bar", new ArrayList<>() );
- tf.execute();
-
- assertTrue( tf.report().toString().endsWith( "_replay" ),
- "reports from replay runs (e.g.: " + tf.report()
- + ") have an obvious filename suffix" );
- // read the report generated by the replay run
- Reader r = new Reader( tf.report() );
- Index index = r.read();
- assertEquals( "bar (replay)",
- index.meta.testTitle,
- "reports from replay runs are titled as such" );
- FlowData fd = r.detail( index.entries.get( 0 ) );
-
- String msg = fd.logs.get( 0 ).message;
- assertEquals( "Replaying data from " + dataSource, msg,
- "flow log first entry shows the data source" );
-
+ try {
+ QuietFiles.recursiveDelete( Paths.get( "target/mctf/latest" ) );
+
+ // this one will be ignored as it's not the latest valid report
+ AssertionOptions.REPORT_NAME.set( "older" );
+ generateReport( "latest" );
+
+ // This is the one that will be replayed
+ AssertionOptions.REPORT_NAME.set( "replay_source" );
+ Path dataSource = generateReport( "latest" );
+
+ // this one was created later but is named as a replay report, so should be
+ // ignored as a source of data
+ AssertionOptions.REPORT_NAME.set( "later_replay" );
+ generateReport( "latest" );
+
+ // this one was also created later but lacks detail files, so should also be
+ // ignored as a source of data
+ AssertionOptions.REPORT_NAME.set( "no_detail" );
+ Path noDetails = generateReport( "latest" );
+ QuietFiles.recursiveDelete( noDetails.resolve( "detail" ) );
+
+ // this one was also created later but lacks an index, so should also be
+ // ignored as a source of data
+ AssertionOptions.REPORT_NAME.set( "no_index" );
+ Path noIndex = generateReport( "latest" );
+ QuietFiles.recursiveDelete( noIndex.resolve( "index.html" ) );
+
+ // activate replay mode
+ System.setProperty( AssertionOptions.REPLAY.property(), Replay.LATEST );
+
+ // run again
+ TestFlocessor tf = build( "latest", new ArrayList<>() );
+ tf.execute();
+
+ assertTrue( tf.report().toString().endsWith( "_replay" ),
+ "reports from replay runs (e.g.: " + tf.report()
+ + ") have an obvious filename suffix" );
+ // read the report generated by the replay run
+ Reader r = new Reader( tf.report() );
+ Index index = r.read();
+ assertEquals( "latest (replay)",
+ index.meta.testTitle,
+ "reports from replay runs are titled as such" );
+ FlowData fd = r.detail( index.entries.get( 0 ) );
+
+ String msg = fd.logs.get( 0 ).message;
+ assertEquals( "Replaying data from " + dataSource, msg,
+ "flow log first entry shows the data source" );
+
+ }
+ finally {
+ AssertionOptions.REPORT_NAME.clear();
+ }
}
/**
@@ -381,8 +394,11 @@ void missingIndex() throws Exception {
// try to run again
IllegalStateException ise = assertThrows( IllegalStateException.class,
() -> build( "blah", null ) );
- assertEquals(
- "No index data found in " + AssertionOptions.ARTIFACT_DIR.value() + "/missingIndexEntry",
- ise.getMessage().replace( '\\', '/' ) );
+ assertEquals( String.format(
+ "No index data found in %s/missingIndexEntry/masked_timestamp",
+ AssertionOptions.ARTIFACT_DIR.value() ),
+ ise.getMessage()
+ .replace( '\\', '/' )
+ .replaceAll( "/\\d{6}-\\d{6}$", "/masked_timestamp" ) );
}
}
diff --git a/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReportingTest.java b/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReportingTest.java
index 37ee1304b6..073e7e4a48 100644
--- a/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReportingTest.java
+++ b/assert/assert-core/src/test/java/com/mastercard/test/flow/assrt/ReportingTest.java
@@ -11,12 +11,18 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.EnumSet;
import java.util.function.Function;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mastercard.test.flow.Actor;
@@ -25,6 +31,7 @@
import com.mastercard.test.flow.report.data.Entry;
import com.mastercard.test.flow.report.data.FlowData;
import com.mastercard.test.flow.report.data.Index;
+import com.mastercard.test.flow.util.Option.Temporary;
/**
* Exercises {@link Reporting} values
@@ -306,4 +313,41 @@ void exercised() {
FlowData fd = r.detail( ie );
assertEquals( "[B]", fd.exercised.toString() );
}
+
+ /**
+ * Shows that a stably-named symlink is created that points to the latest report
+ */
+ @Test
+ @DisabledOnOs(OS.WINDOWS) // requires special permissions to create symlinks
+ void symlink() {
+ TestFlocessor tf = new TestFlocessor( "symlink", TestModel.abc() )
+ .system( State.FUL, B )
+ .reporting( QUIETLY, "symlink" )
+ .behaviour( assrt -> {
+ // no assertions made
+ } );
+ try( Temporary t = AssertionOptions.REPORT_NAME.temporarily( "sub/path/report" ) ) {
+ tf.execute();
+ }
+
+ Path writtenPath = tf.report();
+ Path linkedPath = Paths.get( "target/mctf/symlink/latest" );
+
+ assertEquals( "target/mctf/symlink/sub/path/report", writtenPath.toString() );
+ assertTrue( Files.exists( linkedPath, LinkOption.NOFOLLOW_LINKS ),
+ "The expected symlink has been created" );
+ assertTrue( Files.isSymbolicLink( linkedPath ),
+ "It really is a symlink" );
+
+ Reader dr = new Reader( writtenPath );
+ Index direct = dr.read();
+
+ Reader lr = new Reader( linkedPath );
+ Index linked = lr.read();
+
+ // reading either provides the same data
+ assertEquals( direct.meta.modelTitle, linked.meta.modelTitle );
+ assertEquals( direct.meta.testTitle, linked.meta.testTitle );
+ assertEquals( direct.meta.timestamp, linked.meta.timestamp );
+ }
}
diff --git a/doc/src/main/markdown/further.md b/doc/src/main/markdown/further.md
index 79fb47ca21..924373376a 100644
--- a/doc/src/main/markdown/further.md
+++ b/doc/src/main/markdown/further.md
@@ -6,14 +6,14 @@ This guide explores some features that will be useful as the system model grows
## Execution report
-Adding a call to [`.reporting()`][AbstractFlocessor.reporting(Reporting)] to the construction chain of the `Flocessor` instance controls whether a HTML report of the test run is produced. The report will detail both the expected and observed system behaviour and the results of comparing the two.
+Adding a call to [`.reporting()`][AbstractFlocessor.reporting(Reporting,String...)] to the construction chain of the `Flocessor` instance controls whether a HTML report of the test run is produced. The report will detail both the expected and observed system behaviour and the results of comparing the two.
The [`Reporting` enum value][Reporting] that you supply controls whether the report is generated and under what circumstances it is automatically opened in a browser.
By default the report will be saved to a timestamped directory under `target/mctf`, but the `mctf.report.dir` system property offers control over the destination directory.
-[AbstractFlocessor.reporting(Reporting)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L190-L197,190-197
+[AbstractFlocessor.reporting(Reporting,String...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L194-L203,194-203
[Reporting]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Reporting.java
@@ -35,7 +35,7 @@ You can use [`Replay.isActive()`][Replay.isActive()] in your assertion component
-[Replay.isActive()]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java#L41-L48,41-48
+[Replay.isActive()]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Replay.java#L45-L52,45-52
@@ -78,8 +78,8 @@ Note that only the tag/index-based filtering can be used to avoid flow construct
-[AbstractFlocessor.filtering(Consumer)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L306-L314,306-314
-[AbstractFlocessor.exercising(Predicate,Consumer)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L319-L344,319-344
+[AbstractFlocessor.filtering(Consumer)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L315-L323,315-323
+[AbstractFlocessor.exercising(Predicate,Consumer)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L328-L353,328-353
@@ -123,7 +123,7 @@ Note that the assertion components will not make any assumptions about the forma
[LogCapture]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/LogCapture.java
[Tail]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/log/Tail.java
[Merge]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/log/Merge.java
-[AbstractFlocessor.logs(LogCapture)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L274-L281,274-281
+[AbstractFlocessor.logs(LogCapture)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L283-L290,283-290
@@ -246,7 +246,7 @@ Consider the following worked example:
[flow.Unpredictable]: ../../../../api/src/main/java/com/mastercard/test/flow/Unpredictable.java
[AbstractMessage.masking(Unpredictable,UnaryOperator)]: ../../../../message/message-core/src/main/java/com/mastercard/test/flow/msg/AbstractMessage.java#L50-L57,50-57
-[AbstractFlocessor.masking(Unpredictable...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L202-L209,202-209
+[AbstractFlocessor.masking(Unpredictable...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L211-L218,211-218
[mask.BenSys]: ../../test/java/com/mastercard/test/flow/doc/mask/BenSys.java
[mask.DieSys]: ../../test/java/com/mastercard/test/flow/doc/mask/DieSys.java
[mask.Unpredictables]: ../../test/java/com/mastercard/test/flow/doc/mask/Unpredictables.java
@@ -256,7 +256,7 @@ Consider the following worked example:
[msg.Mask.andThen(Consumer)]: ../../../../message/message-core/src/main/java/com/mastercard/test/flow/msg/Mask.java#L290-L292,290-292
[BenDiceTest?masking]: ../../test/java/com/mastercard/test/flow/doc/mask/BenDiceTest.java#L31,31
[BenTest]: ../../test/java/com/mastercard/test/flow/doc/mask/BenTest.java
-[AbstractFlocessor.masking(Unpredictable...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L202-L209,202-209
+[AbstractFlocessor.masking(Unpredictable...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L211-L218,211-218
@@ -276,7 +276,7 @@ You can see usage of these types in the example system:
[flow.Context]: ../../../../api/src/main/java/com/mastercard/test/flow/Context.java
[Builder.context(Context)]: ../../../../builder/src/main/java/com/mastercard/test/flow/builder/Builder.java#L225-L232,225-232
[assrt.Applicator]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Applicator.java
-[AbstractFlocessor.applicators(Applicator...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L248-L254,248-254
+[AbstractFlocessor.applicators(Applicator...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L257-L263,257-263
[model.ctx.QueueProcessing]: ../../../../example/app-model/src/main/java/com/mastercard/test/flow/example/app/model/ctx/QueueProcessing.java
[QueueProcessingApplicator]: ../../../../example/app-assert/src/main/java/com/mastercard/test/flow/example/app/assrt/ctx/QueueProcessingApplicator.java
@@ -299,7 +299,7 @@ You can see usage of these types in the example system:
[flow.Residue]: ../../../../api/src/main/java/com/mastercard/test/flow/Residue.java
[assrt.Checker]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/Checker.java
-[AbstractFlocessor.checkers(Checker...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L261-L267,261-267
+[AbstractFlocessor.checkers(Checker...)]: ../../../../assert/assert-core/src/main/java/com/mastercard/test/flow/assrt/AbstractFlocessor.java#L270-L276,270-276
[model.rsd.DBItems]: ../../../../example/app-model/src/main/java/com/mastercard/test/flow/example/app/model/rsd/DBItems.java
[DBItemsChecker]: ../../../../example/app-assert/src/main/java/com/mastercard/test/flow/example/app/assrt/rsd/DBItemsChecker.java
diff --git a/example/app-store/src/test/java/com/mastercard/test/flow/example/app/store/QueryTest.java b/example/app-store/src/test/java/com/mastercard/test/flow/example/app/store/QueryTest.java
index 0e541fab69..ac07d80e67 100644
--- a/example/app-store/src/test/java/com/mastercard/test/flow/example/app/store/QueryTest.java
+++ b/example/app-store/src/test/java/com/mastercard/test/flow/example/app/store/QueryTest.java
@@ -79,7 +79,7 @@ public static void startService() {
if( !Replay.isActive() ) {
service.start();
}
- reportName = AssertionOptions.REPORT_NAME.temporarily( "query_latest" );
+ reportName = AssertionOptions.REPORT_NAME.temporarily( "latest" );
}
/**
@@ -99,7 +99,7 @@ public static void stopService() {
@TestFactory
Stream flows() {
Flocessor flocessor = new Flocessor( "Query test", ExampleSystem.MODEL )
- .reporting( FAILURES )
+ .reporting( FAILURES, "query" )
.exercising( flow -> Flows.intersects( flow, Actors.STORE ), LOG::info )
.system( State.LESS, Actors.STORE )
.masking( BORING, HOST, CLOCK, RNG )
diff --git a/report/report-core/src/main/java/com/mastercard/test/flow/report/QuietFiles.java b/report/report-core/src/main/java/com/mastercard/test/flow/report/QuietFiles.java
index d59b0c31c6..212aff0008 100644
--- a/report/report-core/src/main/java/com/mastercard/test/flow/report/QuietFiles.java
+++ b/report/report-core/src/main/java/com/mastercard/test/flow/report/QuietFiles.java
@@ -2,7 +2,9 @@
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.nio.file.CopyOption;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
@@ -57,10 +59,10 @@ private QuietFiles() {
*/
public static void recursiveDelete( Path path ) {
try {
- if( !Files.exists( path ) ) {
+ if( !Files.exists( path, LinkOption.NOFOLLOW_LINKS ) ) {
// we're done here
}
- else if( Files.isDirectory( path ) ) {
+ else if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) ) {
try( Stream children = Files.list( path ) ) {
children.forEach( QuietFiles::recursiveDelete );
}
@@ -130,6 +132,18 @@ public static Stream list( Path dir ) {
return wrap( () -> Files.list( dir ) );
}
+ /**
+ * @see Files#move(Path, Path, CopyOption...)
+ * @param source the path to the file to move
+ * @param target the path to the target file (may be associated with a
+ * different provider to the source path)
+ * @param options options specifying how the move should be done
+ * @return the path to the target file
+ */
+ public static Path move( Path source, Path target, CopyOption... options ) {
+ return wrap( () -> Files.move( source, target, options ) );
+ }
+
/**
* @see Files#readAllBytes(Path)
* @param path The path to read
diff --git a/report/report-core/src/main/java/com/mastercard/test/flow/report/Reader.java b/report/report-core/src/main/java/com/mastercard/test/flow/report/Reader.java
index dc5a5bf94f..9bf16890b7 100644
--- a/report/report-core/src/main/java/com/mastercard/test/flow/report/Reader.java
+++ b/report/report-core/src/main/java/com/mastercard/test/flow/report/Reader.java
@@ -94,7 +94,7 @@ private static T extract( URI uri, Class type ) {
String file = br.lines().collect( joining( "\n" ) );
return Template.extract( file, type );
}
- catch( @SuppressWarnings("unused") FileNotFoundException fnfe ) {
+ catch( FileNotFoundException fnfe ) {
if( fnfe.getMessage().contains( "Permission denied" ) ) {
// on linux platforms missing read permission results in a
// FileNotFoundException.
@@ -113,8 +113,8 @@ private static T extract( URI uri, Class type ) {
}
/**
- * Searches the a directory for the most recent execution report that satisfies
- * a constraint
+ * Searches a directory for the most recent execution report that satisfies a
+ * constraint
*
* @param dir The directory to search in
* @param filter Additional search constraint