// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.api.ads.common.lib.testing; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import org.openqa.selenium.net.PortProber; import java.util.Deque; import java.util.NoSuchElementException; import java.util.Properties; /** * Registry of available ports for tests. If the test environment does not support dynamic * discovery of available ports, you can override the list of available ports by setting * the {@link #UNUSED_PORTS_PROPERTY_KEY} system property to a comma-separated list of integers. */ public class TestPortFinder { /** System property key for overriding the comma-separated list of available unused ports. */ public static final String UNUSED_PORTS_PROPERTY_KEY = "com.google.api.ads.common.lib.testing.TestPortFinder.unusedPorts"; private static final TestPortFinder INSTANCE = new TestPortFinder(System.getProperties()); /** * The registry of available ports from the {@link #UNUSED_PORTS_PROPERTY_KEY} system property, * if specified. If the system property is not set, this will be {@code null}. */ private final Deque<Integer> availablePorts; @VisibleForTesting TestPortFinder(Properties properties) { String availablePortsString = properties.getProperty(UNUSED_PORTS_PROPERTY_KEY); if (availablePortsString == null) { availablePorts = null; } else { availablePorts = Lists.newLinkedList(); for (String availablePortString : Splitter.on(',').split(availablePortsString)) { availablePorts.add(Integer.valueOf(availablePortString)); } } } /** * Returns the singleton of this class. */ public static TestPortFinder getInstance() { return INSTANCE; } /** * Checks out the next available unused port. Callers should send the returned port * back to {@link #releaseUnusedPort(int)} when it's no longer being used. * * @throws NoSuchElementException if no unused port is available. */ public synchronized int checkOutUnusedPort() { if (availablePorts == null) { return PortProber.findFreePort(); } else if (availablePorts.isEmpty()) { // All available ports are checked out. throw new NoSuchElementException("No unused ports are available"); } return availablePorts.pop(); } /** * Returns the specified unused port to the list of available ports. */ public synchronized void releaseUnusedPort(int unusedPort) { if (availablePorts == null) { // Nothing to do in this case. return; } availablePorts.addLast(unusedPort); } }