Skip to content

Commit

Permalink
Report naming api (#559)
Browse files Browse the repository at this point in the history
* Added api for report naming

* usage example

* test fix
  • Loading branch information
therealryan authored Oct 2, 2023
1 parent 7800ce1 commit 06632e8
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

/**
Expand Down Expand Up @@ -136,11 +140,11 @@ public enum State {
* The path to the execution report that we're replaying data from, or
* <code>null</code> 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
Expand Down Expand Up @@ -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 <code>this</code>
*/
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();
}

Expand All @@ -207,7 +216,7 @@ public T reporting( Reporting r ) {
* @return <code>this</code>
*/
public T masking( Unpredictable... sources ) {
this.masks = sources.clone();
masks = sources.clone();
return self();
}

Expand Down Expand Up @@ -342,8 +351,8 @@ public T filtering( Consumer<FilterConfiguration> cfg ) {
* @see AssertionOptions#SUPPRESS_FILTER
*/
public T exercising( Predicate<Flow> filter, Consumer<String> rejectionLog ) {
this.flowFilter = filter;
this.filterRejectionLog = rejectionLog;
flowFilter = filter;
filterRejectionLog = rejectionLog;
return self();
}

Expand Down Expand Up @@ -937,11 +946,14 @@ private T self() {

private void report( Consumer<Writer> 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 ) {
Expand All @@ -958,14 +970,51 @@ private void report( Consumer<Writer> 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();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
* <code>null</code> 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;
}
Expand All @@ -69,7 +75,7 @@ public static String source() {
/**
* @param source The path to the report to use as a source of data, or
* <code>null</code> for an empty {@link Replay}
* @see #source()
* @see #source(String...)
*/
public Replay( String source ) {
reader = source != null ? new Reader( Paths.get( source ) ) : null;
Expand Down Expand Up @@ -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 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ReplayTest {
private static TestFlocessor build( String title, List<String> 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() );
Expand All @@ -45,7 +45,6 @@ private static TestFlocessor build( String title, List<String> behaviourLog ) {
}

private static Path generateReport( String title ) {
System.setProperty( AssertionOptions.REPORT_NAME.property(), title );
List<String> behaviourLog = new ArrayList<>();
TestFlocessor tf = build( title, behaviourLog );
tf.execute();
Expand Down Expand Up @@ -79,7 +78,6 @@ private static Path generateReport( String title ) {
@AfterEach
void clearReplayProperty() {
System.clearProperty( AssertionOptions.REPLAY.property() );
System.clearProperty( AssertionOptions.REPORT_NAME.property() );
}

/**
Expand Down Expand Up @@ -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();
}
}

/**
Expand Down Expand Up @@ -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" ) );
}
}
Loading

0 comments on commit 06632e8

Please sign in to comment.