Repository: zest-java Updated Branches: refs/heads/develop 246e588cd -> 420054295
Review FreePortFinder test utility to reduce test flakiness on CI Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/42005429 Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/42005429 Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/42005429 Branch: refs/heads/develop Commit: 420054295fe5138f61051bb33676afb7280b9ba5 Parents: 246e588 Author: Paul Merlin <[email protected]> Authored: Mon Nov 7 11:15:09 2016 +0100 Committer: Paul Merlin <[email protected]> Committed: Mon Nov 7 11:15:09 2016 +0100 ---------------------------------------------------------------------- .../apache/zest/test/util/FreePortFinder.java | 190 +++++++++++++++++-- .../zest/library/rest/admin/RestTest.java | 2 +- .../zest/library/servlet/ServletTest.java | 2 +- .../library/shiro/web/WebHttpShiroTest.java | 2 +- .../library/shiro/web/WebRealmServiceTest.java | 2 +- .../library/shiro/web/WebServletShiroTest.java | 2 +- 6 files changed, 176 insertions(+), 24 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/core/testsupport/src/main/java/org/apache/zest/test/util/FreePortFinder.java ---------------------------------------------------------------------- diff --git a/core/testsupport/src/main/java/org/apache/zest/test/util/FreePortFinder.java b/core/testsupport/src/main/java/org/apache/zest/test/util/FreePortFinder.java index f52a485..7f4eef5 100644 --- a/core/testsupport/src/main/java/org/apache/zest/test/util/FreePortFinder.java +++ b/core/testsupport/src/main/java/org/apache/zest/test/util/FreePortFinder.java @@ -20,42 +20,194 @@ package org.apache.zest.test.util; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.InetAddress; import java.net.ServerSocket; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.function.IntPredicate; +import java.util.stream.Stream; + +import static java.util.Collections.shuffle; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.rangeClosed; public class FreePortFinder { + private static class Range + { + final int lowerBound; + final int higherBound; + + private Range( int lowerBound, int higherBound ) + { + this.lowerBound = lowerBound; + this.higherBound = higherBound; + } + } + + // Presumably the least used port ranges with >500 slots + // See http://stackoverflow.com/a/28369841/300053 + private static final List<Range> LEAST_USED_RANGES = Arrays.asList( + new Range( 29170, 29998 ), + new Range( 38866, 39680 ), + new Range( 41798, 42507 ), + new Range( 43442, 44122 ), + new Range( 46337, 46997 ), + new Range( 35358, 36000 ), + new Range( 36866, 37474 ), + new Range( 38204, 38799 ), + new Range( 33657, 34248 ), + new Range( 30261, 30831 ), + new Range( 41231, 41793 ), + new Range( 21011, 21552 ), + new Range( 28590, 29117 ), + new Range( 14415, 14935 ), + new Range( 26490, 26999 ) + ); + + private static final int MAX_PORT_CHECKS = 20; + + public static boolean isFreePortOnLocalHost( int port ) + { + return isFreePort( getLocalHostUnchecked(), port ); + } + + public static boolean isFreePort( InetAddress address, int port ) + { + try + { + checkFreePort( address, port ); + return true; + } + catch( IOException ex ) + { + return false; + } + } + + public static void checkFreePortOnLocalHost( int port ) throws IOException + { + checkFreePort( getLocalHostUnchecked(), port ); + } - public static int findFreePortOnLoopback() - throws IOException + public static void checkFreePort( InetAddress address, int port ) throws IOException { - return findFreePortOnIface( InetAddress.getLocalHost() ); + ServerSocket server = null; + try + { + server = new ServerSocket( port, 1, address ); + } + finally + { + if( server != null ) + { + try + { + server.close(); + } + catch( IOException e ) + { + // Ignore + } + } + } } - public static int findFreePortOnLoopback( int prefered ) - throws IOException + public static int findFreePortOnLocalHost() + throws IOException { - return findFreePortOnIface( InetAddress.getLocalHost(), prefered ); + return findFreePort( getLocalHostUnchecked() ); } - public static int findFreePortOnIface( InetAddress address ) - throws IOException + public static int findFreePort( InetAddress address ) + throws IOException { - return findFreePortOnIface( address, -1 ); + FreePortPredicate check = new FreePortPredicate( address ); + // Randomly choose MAX_PORT_CHECKS ports from the least used ranges + OptionalInt port = LEAST_USED_RANGES.stream() + .flatMapToInt( range -> rangeClosed( range.lowerBound, range.higherBound ) ) + .boxed() + .collect( collectingAndThen( toList(), collected -> + { + shuffle( collected ); + return collected.stream(); + } ) ) + .limit( MAX_PORT_CHECKS ) + .mapToInt( Integer::intValue ) + .filter( check ) + .findFirst(); + return port.orElseThrow( () -> + { + IOException exception = new IOException( "Unable to find a free port on " + address ); + check.errors.build().forEach( exception::addSuppressed ); + return exception; + } ); } - public static int findFreePortOnIface( InetAddress address, int prefered ) - throws IOException + public static int findFreePortInRangeOnLocalhost( int lowerBound, int higherBound ) + throws IOException { - ServerSocket server; - if ( prefered > 0 ) { - server = new ServerSocket( prefered, 1, address ); - } else { - server = new ServerSocket( 0, 1, address ); + return findFreePortInRange( getLocalHostUnchecked(), lowerBound, higherBound ); + } + + public static int findFreePortInRange( InetAddress address, int lowerBound, int higherBound ) + throws IOException + { + if( higherBound - lowerBound < 0 ) + { + throw new IllegalArgumentException( "Invalid port range " + lowerBound + '-' + higherBound ); } - int port = server.getLocalPort(); - server.close(); - return port; + FreePortPredicate check = new FreePortPredicate( address ); + OptionalInt port = rangeClosed( lowerBound, higherBound ).filter( check ).findFirst(); + return port.orElseThrow( () -> + { + String message = "Unable to find a free port in range " + lowerBound + '-' + higherBound + " on " + address; + IOException exception = new IOException( message ); + check.errors.build().forEach( exception::addSuppressed ); + return exception; + } ); } + private static class FreePortPredicate implements IntPredicate + { + private final InetAddress address; + private final Stream.Builder<Throwable> errors; + + private FreePortPredicate( InetAddress address ) + { + this.address = address; + this.errors = Stream.builder(); + } + + @Override + public boolean test( int candidate ) + { + try + { + checkFreePort( address, candidate ); + return true; + } + catch( IOException ex ) + { + errors.add( ex ); + return false; + } + } + } + + private static InetAddress getLocalHostUnchecked() + { + try + { + return InetAddress.getLocalHost(); + } + catch( UnknownHostException ex ) + { + throw new UncheckedIOException( ex ); + } + } } http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/libraries/rest/src/test/java/org/apache/zest/library/rest/admin/RestTest.java ---------------------------------------------------------------------- diff --git a/libraries/rest/src/test/java/org/apache/zest/library/rest/admin/RestTest.java b/libraries/rest/src/test/java/org/apache/zest/library/rest/admin/RestTest.java index fef807b..0bec7e8 100644 --- a/libraries/rest/src/test/java/org/apache/zest/library/rest/admin/RestTest.java +++ b/libraries/rest/src/test/java/org/apache/zest/library/rest/admin/RestTest.java @@ -74,7 +74,7 @@ public class RestTest extends AbstractZestTest { try { - ADMIN_PORT = FreePortFinder.findFreePortOnLoopback(); + ADMIN_PORT = FreePortFinder.findFreePortOnLocalHost(); } catch( IOException ex ) { http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/libraries/servlet/src/test/java/org/apache/zest/library/servlet/ServletTest.java ---------------------------------------------------------------------- diff --git a/libraries/servlet/src/test/java/org/apache/zest/library/servlet/ServletTest.java b/libraries/servlet/src/test/java/org/apache/zest/library/servlet/ServletTest.java index 29aa1eb..a0dda4e 100644 --- a/libraries/servlet/src/test/java/org/apache/zest/library/servlet/ServletTest.java +++ b/libraries/servlet/src/test/java/org/apache/zest/library/servlet/ServletTest.java @@ -88,7 +88,7 @@ public class ServletTest public void test() throws Exception { - int port = FreePortFinder.findFreePortOnLoopback( 9001 ); + int port = FreePortFinder.findFreePortOnLocalHost(); Server server = new Server( port ); try { http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebHttpShiroTest.java ---------------------------------------------------------------------- diff --git a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebHttpShiroTest.java b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebHttpShiroTest.java index c6d54a9..95d9dca 100644 --- a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebHttpShiroTest.java +++ b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebHttpShiroTest.java @@ -49,7 +49,7 @@ public class WebHttpShiroTest new JettyServiceAssembler().withConfig( configModule, Visibility.layer ).assemble( module ); // END SNIPPET: assembly - port = FreePortFinder.findFreePortOnLoopback(); + port = FreePortFinder.findFreePortOnLocalHost(); JettyConfiguration config = module.forMixin( JettyConfiguration.class ).declareDefaults(); config.hostName().set( "127.0.0.1" ); config.port().set( port ); http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebRealmServiceTest.java ---------------------------------------------------------------------- diff --git a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebRealmServiceTest.java b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebRealmServiceTest.java index d810dd3..0b2aaea 100644 --- a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebRealmServiceTest.java +++ b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebRealmServiceTest.java @@ -143,7 +143,7 @@ public class WebRealmServiceTest new JettyServiceAssembler().withConfig( configModule, Visibility.layer ).assemble( module ); // END SNIPPET: assembly - port = FreePortFinder.findFreePortOnLoopback(); + port = FreePortFinder.findFreePortOnLocalHost(); JettyConfiguration config = module.forMixin( JettyConfiguration.class ).declareDefaults(); config.hostName().set( "127.0.0.1" ); config.port().set( port ); http://git-wip-us.apache.org/repos/asf/zest-java/blob/42005429/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebServletShiroTest.java ---------------------------------------------------------------------- diff --git a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebServletShiroTest.java b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebServletShiroTest.java index 73c9a4a..7eb5245 100644 --- a/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebServletShiroTest.java +++ b/libraries/shiro-web/src/test/java/org/apache/zest/library/shiro/web/WebServletShiroTest.java @@ -36,7 +36,7 @@ public class WebServletShiroTest public void test() throws Exception { - int port = FreePortFinder.findFreePortOnLoopback( 9001 ); + int port = FreePortFinder.findFreePortOnLocalHost(); Server server = new Server( port ); try {
