/** * This file is part of git-as-svn. It is subject to the license terms * in the LICENSE file found in the top-level directory of this distribution * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn, * including this file, may be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ package svnserver.tester; import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.ITestContext; import org.testng.ITestListener; import org.testng.ITestResult; import org.tmatesoft.svn.core.SVNAuthenticationException; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.BasicAuthenticationManager; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import svnserver.TestHelper; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.net.InetAddress; import java.net.ServerSocket; import java.text.MessageFormat; import java.util.concurrent.TimeUnit; /** * Listener for creating SvnTesterExternal. * * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com> */ public class SvnTesterExternalListener implements ITestListener { @NotNull private static final Logger log = LoggerFactory.getLogger(SvnTesterExternalListener.class); @NotNull private static final String USER_NAME = "tester"; @NotNull private static final String PASSWORD = "passw0rd"; @NotNull private static final String HOST = "127.0.0.2"; @NotNull private static final String CONFIG_SERVER = "" + "[general]\n" + "anon-access = none\n" + "auth-access = write\n" + "password-db = {0}\n"; @NotNull private static final String CONFIG_PASSWD = "" + "[users]\n" + "{0} = {1}\n"; private static final long SERVER_STARTUP_TIMEOUT = TimeUnit.SECONDS.toMillis(30); private static final long SERVER_STARTUP_DELAY = TimeUnit.MILLISECONDS.toMillis(20); @Nullable private static NativeDaemon daemon; @Override public void onTestStart(ITestResult result) { } @Override public void onTestSuccess(ITestResult result) { } @Override public void onTestFailure(ITestResult result) { } @Override public void onTestSkipped(ITestResult result) { } @Override public void onTestFailedButWithinSuccessPercentage(ITestResult result) { } @Override public void onStart(ITestContext context) { try { if (System.getenv("TRAVIS") != null) { log.warn("Native svn daemon disabled on travis"); return; } final String svnserve = findExecutable("svnserve"); final String svnadmin = findExecutable("svnadmin"); if (svnserve != null && svnadmin != null) { log.warn("Native svn daemon executables: {}, {}", svnserve, svnadmin); daemon = new NativeDaemon(svnserve, svnadmin); } else { log.warn("Native svn daemon disabled"); } //SvnTesterExternal.create(); } catch (Exception e) { throw new IllegalStateException(e); } } @Nullable private static String findExecutable(@NotNull String name) { final String path = System.getenv("PATH"); if (path != null) { final String suffix = SystemUtils.IS_OS_WINDOWS ? ".exe" : ""; for (String dir : path.split(File.pathSeparator)) { final File file = new File(dir, name + suffix); if (file.exists()) { return file.getAbsolutePath(); } } } return null; } @Override public void onFinish(ITestContext context) { if (daemon != null) { try { daemon.close(); } catch (Exception e) { throw new IllegalStateException(e); } finally { daemon = null; } } } @Nullable public static SvnTesterFactory get() { return daemon; } private static class NativeDaemon implements SvnTesterFactory, AutoCloseable { @NotNull private final Process daemon; @NotNull private final File repo; @NotNull private final SVNURL url; public NativeDaemon(@NotNull String svnserve, @NotNull String svnadmin) throws IOException, InterruptedException, SVNException { int port = detectPort(); url = SVNURL.create("svn", null, HOST, port, null, true); repo = TestHelper.createTempDir("git-as-svn-repo"); log.info("Starting native svn daemon at: {}, url: {}", repo, url); Runtime.getRuntime().exec(new String[]{ svnadmin, "create", repo.getAbsolutePath() }).waitFor(); File config = createConfigs(repo); daemon = Runtime.getRuntime().exec(new String[]{ svnserve, "--daemon", "--root", repo.getAbsolutePath(), "--config-file", config.getAbsolutePath(), "--listen-host", HOST, "--listen-port", Integer.toString(port) }); long serverStartupTimeout = System.currentTimeMillis() + SERVER_STARTUP_TIMEOUT; while (true) { try { SVNRepositoryFactory.create(url).getRevisionPropertyValue(0, "example"); } catch (SVNAuthenticationException ignored) { break; } catch (SVNException e) { if ((e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_SVN_IO_ERROR) && (System.currentTimeMillis() < serverStartupTimeout)) { Thread.sleep(SERVER_STARTUP_DELAY); continue; } throw e; } break; } } @NotNull private static File createConfigs(@NotNull File repo) throws IOException { final File config = new File(repo, "conf/server.conf"); final File passwd = new File(repo, "conf/server.passwd"); try (Writer writer = new FileWriter(config)) { writer.write(MessageFormat.format(CONFIG_SERVER, passwd.getAbsolutePath())); } try (Writer writer = new FileWriter(passwd)) { writer.write(MessageFormat.format(CONFIG_PASSWD, USER_NAME, PASSWORD)); } return config; } private int detectPort() throws IOException { try (ServerSocket socket = new ServerSocket(0, 0, InetAddress.getByName(HOST))) { return socket.getLocalPort(); } } @NotNull @Override public SvnTester create() throws Exception { return new SvnTesterExternal(url, BasicAuthenticationManager.newInstance(USER_NAME, PASSWORD.toCharArray())); } @Override public void close() throws Exception { log.info("Stopping native svn daemon."); daemon.destroy(); daemon.waitFor(); TestHelper.deleteDirectory(repo); } } }