Skip to content

Commit

Permalink
classpath banter
Browse files Browse the repository at this point in the history
  • Loading branch information
therealryan committed Oct 27, 2023
1 parent a054dc6 commit 2d23a95
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import com.mastercard.test.flow.report.data.LogEvent;
import com.mastercard.test.flow.report.data.ResidueData;
import com.mastercard.test.flow.report.data.TransmissionData;
import com.mastercard.test.flow.report.duct.Duct;
import com.mastercard.test.flow.util.Dependencies;
import com.mastercard.test.flow.util.Flows;

Expand Down Expand Up @@ -1013,7 +1014,12 @@ private void report( Consumer<Writer> data, boolean error ) {

// also, if appropriate, open a browser to it
if( reporting.shouldOpen( error ) ) {
report.browse();
if( AssertionOptions.DUCT.isTrue() ) {
Duct.serve( report.path() );
}
else {
report.browse();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.mastercard.test.flow.Flow;
import com.mastercard.test.flow.assrt.filter.FilterOptions;
import com.mastercard.test.flow.report.duct.Duct;
import com.mastercard.test.flow.util.Option;

/**
Expand All @@ -24,6 +25,13 @@ public enum AssertionOptions implements Option {
*/
ARTIFACT_DIR(FilterOptions.ARTIFACT_DIR),

/**
* Controls whether we use {@link Duct} or not
*/
DUCT(b -> b.property( "mctf.report.serve" )
.description( ""
+ "Set to `true` to browse reports on a local web server rather than the filesystem" )),

/**
* Controls {@link Replay} parameters
*/
Expand Down
13 changes: 0 additions & 13 deletions example/app-itest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,6 @@

<dependencies>

<dependency>
<!-- REST framework, used to run the services and serve the test report -->
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<exclusions>
<exclusion>
<!-- Avoid incompatible slf4j -->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- The test suite can instantiate the application, so obviously it needs
to have the constituent services as dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

package com.mastercard.test.flow.example.app.itest;

import static com.mastercard.test.flow.assrt.Reporting.ALWAYS;
import static com.mastercard.test.flow.assrt.Reporting.FAILURES;
import static com.mastercard.test.flow.example.app.model.ExampleSystem.Actors.CORE;
import static com.mastercard.test.flow.example.app.model.ExampleSystem.Actors.DB;
Expand All @@ -14,9 +15,6 @@
import static com.mastercard.test.flow.example.app.model.ExampleSystem.Unpredictables.HOST;
import static com.mastercard.test.flow.example.app.model.ExampleSystem.Unpredictables.RNG;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.function.Supplier;
Expand Down Expand Up @@ -47,8 +45,6 @@
import com.mastercard.test.flow.msg.http.HttpReq;
import com.mastercard.test.flow.msg.web.WebSequence;

import spark.Service;

/**
* Spins up a complete instance of the application and compares its behaviour
* against our model
Expand Down Expand Up @@ -91,40 +87,7 @@ public static void startApp() {
*/
@AfterAll
public static void stopApp() throws Exception {

clusterManager.stopCluster();

if( "true".equals( System.getProperty( "mctf.itest.report.serve" ) ) ) {
// serve the report
String reportDir = reportLocation.get().toString();
System.out.println( "Serving " + reportDir );
Service service = Service.ignite()
.port( 0 )
.externalStaticFileLocation( reportDir );
service.staticFiles.header( "Access-Control-Allow-Origin", "*" );
service.init();
service.awaitInitialization();

// open the browser
URI uri = new URI( "http://localhost:" + service.port() );
if( Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported( Action.BROWSE ) ) {
System.out.println( "Opening browser to " + uri );
Desktop.getDesktop().browse( uri );
}
else {
System.out.println( "Open your browser to " + uri );
}

// wait it till the human is done with it
System.out.println( "Hit enter to shutdown the server" );
// noinspection ResultOfMethodCallIgnored
System.in.read();

// shut it down
service.stop();
service.awaitStop();
System.out.println( "Shutdown complete" );
}
}

/**
Expand All @@ -133,7 +96,7 @@ public static void stopApp() throws Exception {
@TestFactory
Stream<DynamicNode> flows() {
Flocessor f = new Flocessor( "Integration test", ExampleSystem.MODEL )
.reporting( FAILURES )
.reporting( AssertionOptions.DUCT.isTrue() ? ALWAYS : FAILURES )
.system( State.FUL, WEB_UI, UI, CORE, QUEUE, HISTOGRAM, STORE, DB )
.masking( BORING, CLOCK, HOST, RNG )
.logs( Util.LOG_CAPTURE )
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@
<!-- Default to the same filters as last time -->
<mctf.filter.repeat>true</mctf.filter.repeat>
<!-- Causes the report to be presented in a browser -->
<mctf.itest.report.serve>true</mctf.itest.report.serve>
<mctf.report.serve>true</mctf.report.serve>
</systemPropertyVariables>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -17,6 +18,7 @@
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -36,6 +38,18 @@
*/
public class Duct {

/**
* When <code>true</code>, failures will be printed to stderr. This framework
* does not assume that clients use a logging framework, and it tries to keep
* silent on stdout. This class uses slf4j for the bulk of operations, but those
* will be running in a different process to the test. The interaction between
* the test and the duct process (where we can't use slf4j) does a bunch of
* failure-prone things though, so it's nice to have the option of seeing the
* issues when you're wondering why your report is not being served.
*/
static final boolean NOISY_FAILS = "true"
.equals( System.getProperty( "mctf.duct.noisy" ) );

/**
* The preference name where we save our index directories
*/
Expand All @@ -58,10 +72,15 @@ public static void main( String[] args ) {
.map( duct::add )
.forEach( served -> {
try {
Desktop.getDesktop().browse( served.toURI() );
if( Desktop.isDesktopSupported() ) {
Desktop.getDesktop().browse( served.toURI() );
}
}
catch( Exception e ) {
System.err.println( "Failed to browse " + served + " due to " + e.getMessage() );
if( NOISY_FAILS ) {
System.err.println( "Failed to browse " + served );
e.printStackTrace();
}
}
} );
}
Expand All @@ -79,33 +98,67 @@ public static void serve( Path report ) {
if( added != null ) {
// there's an existing instance!
try {
Desktop.getDesktop().browse( added.toURI() );
if( Desktop.isDesktopSupported() ) {
Desktop.getDesktop().browse( added.toURI() );
}
}
catch( @SuppressWarnings("unused") IOException | URISyntaxException e ) {
// oh well, we tried
catch( IOException | URISyntaxException e ) {
if( NOISY_FAILS ) {
System.err.println( "Failed to browse " + added );
e.printStackTrace();
}
}
}
else {
// we'll have to spawn our own instance
ProcessBuilder pb = new ProcessBuilder(
"java",
// re-use the current JVM's classpath. It's running this class, so it should
// also have the dependencies we need. The classpath will be bigger than duct
// strictly needs, but the cost of that is negligible
"-cp", getClassPath(),
// invoke this class's main method
Duct.class.getName(),
// pass the report path on the commandline - the above main method will take
// care of adding and browsing it
report.toAbsolutePath().toString() );
try {
// this process will persist after the demise of the current JVM
ProcessBuilder pb = new ProcessBuilder(
"java",
// re-use the current JVM's classpath. It's running this class, so it should
// also have the dependencies we need. The classpath will be bigger than duct
// strictly needs, but the cost of that is negligible
"-cp", System.getProperty( "java.class.path" ),
// invoke this class's main method
Duct.class.getName(),
// pass the report path on the commandline - the above main method will take
// care of adding and browsing it
report.toAbsolutePath().toString() );
pb.start();
}
catch( @SuppressWarnings("unused") IOException e ) {
// oh well, we tried
catch( IOException e ) {
if( NOISY_FAILS ) {
System.err.println( "Failed to launch:\n"
+ pb.command().stream().collect( joining( " " ) ) );
e.printStackTrace();
}
}
}
}

/**
* Gets the classpath of the current JVM. Ordinarily you can find the classpath
* just with the <code>java.class.path</code> system property. When running
* tests via maven (a really common use-case for us) <a href=
* "https://cwiki.apache.org/confluence/display/MAVEN/Maven+3.x+Class+Loading">that
* just contains <code>plexus-classworlds.jar</code></a>, which is no use to us.
* Hence we're reduced to this: ascending the classloader chain and pulling out
* any URL that we find.
*
* @return The classpath of the current JVM
*/
static String getClassPath() {
List<URL> urls = new ArrayList<>();
ClassLoader cl = Duct.class.getClassLoader();
while( cl != null ) {
if( cl instanceof URLClassLoader ) {
Collections.addAll( urls, ((URLClassLoader) cl).getURLs() );
}
cl = cl.getParent();
}
return urls.stream()
.map( URL::getPath )
.collect( joining( File.pathSeparator ) );
}

/**
Expand All @@ -123,9 +176,17 @@ private static URL tryAdd( Path report ) {
b -> new String( b, UTF_8 ) );
return new URL( res.body );
}
catch( @SuppressWarnings("unused") Exception e ) {
catch( Exception e ) {
// A failure on this request is not unexpected - it could just be a signal that
// we need to start a new instance of duct
if( NOISY_FAILS ) {
System.err.println( String.format(
"Failed to add via http:\n%s %s\n%s",
"http://localhost:" + PORT + "/add",
"POST",
report.toAbsolutePath().toString() ) );
e.printStackTrace();
}
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -36,6 +37,17 @@ class Server {
private static final ObjectMapper JSON = new ObjectMapper()
.enable( SerializationFeature.INDENT_OUTPUT );

private static final Map<String, String> MIME_TYPES;
static {
Map<String, String> mtm = new HashMap<>();
mtm.put( ".css", "text/css" );
mtm.put( ".html", "text/html" );
mtm.put( ".ico", "image/vnd.microsoft.icon" );
mtm.put( ".js", "text/javascript" );
mtm.put( ".txt", "text/plain" );
MIME_TYPES = Collections.unmodifiableMap( mtm );
}

/**
* Restricts our server to only working with local clients. This application
* will merrily serve up the contents of directories, so we have to be mindful
Expand All @@ -44,7 +56,6 @@ class Server {
* localhost
*/
private static final Filter LOCAL_ORIGIN_ONLY = ( request, response ) -> {
LOG.info( "REQUEST TO " + request.pathInfo() );
// SECURITY-CRITICAL BEHAVIOUR
try {
InetAddress addr = InetAddress.getByName( request.ip() );
Expand Down Expand Up @@ -155,6 +166,12 @@ void map( String path, Path dir ) {

private static Route respondWithFileBytes( Path f ) {
return ( req, res ) -> {

MIME_TYPES.entrySet().stream()
.filter( e -> f.toString().endsWith( e.getKey() ) )
.findFirst()
.ifPresent( e -> res.header( "Content-Type", e.getValue() ) );

byte[] buff = new byte[8192];
try( InputStream is = Files.newInputStream( f );
OutputStream os = res.raw().getOutputStream(); ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private static PopupMenu menu( Duct duct ) {
}

private static MenuItem index( Duct duct ) {
MenuItem index = new MenuItem( "Report index..." );
MenuItem index = new MenuItem( "Index" );
index.addActionListener( ev -> {
try {
Desktop.getDesktop().browse( new URI( "http://localhost:" + duct.port() ) );
Expand All @@ -104,7 +104,7 @@ private static MenuItem index( Duct duct ) {
}

private static MenuItem add( Duct duct ) {
MenuItem add = new MenuItem( "Add reports..." );
MenuItem add = new MenuItem( "Add..." );
add.addActionListener( ev -> {
File start = null;
try {
Expand Down Expand Up @@ -138,15 +138,15 @@ private static MenuItem add( Duct duct ) {
}

private static MenuItem clearIndex( Duct duct ) {
MenuItem item = new MenuItem( "Clear index" );
MenuItem item = new MenuItem( "Clear" );
item.addActionListener( ev -> {
duct.clearIndex();
} );
return item;
}

private static MenuItem reindex( Duct duct ) {
MenuItem item = new MenuItem( "Refresh index" );
MenuItem item = new MenuItem( "Refresh" );
item.addActionListener( ev -> duct.reindex() );
return item;
}
Expand Down
2 changes: 1 addition & 1 deletion report/duct/src/main/resources/simplelogger.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# http://www.slf4j.org/api/org/slf4j/impl/SimpleLogger.html
# org.slf4j.simpleLogger.logFile=duct_log.txt set at runtime
# org.slf4j.simpleLogger.logFile=location of log.txt set at runtime
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ
org.slf4j.simpleLogger.defaultLogLevel=info
Loading

0 comments on commit 2d23a95

Please sign in to comment.