This is an automated email from the ASF dual-hosted git repository. samt pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push: new a821214 CASSANDRA-15942 Improve tooling testing framework a821214 is described below commit a8212149aba7a85139de9758988f6e01f7bc8d91 Author: Bereng <berenguerbl...@gmail.com> AuthorDate: Tue Jul 14 16:44:58 2020 +0200 CASSANDRA-15942 Improve tooling testing framework Patch by Berenguer Blasi; reviewed by Robert Stupp and Sam Tunnicliffe for CASSANDRA-15942 --- .../config/DatabaseDescriptorRefTest.java | 2 +- test/unit/org/apache/cassandra/cql3/CQLTester.java | 66 +++- .../org/apache/cassandra/tools/BulkLoaderTest.java | 28 +- .../apache/cassandra/tools/ClearSnapshotTest.java | 91 ++--- .../cassandra/tools/CompactionStressTest.java | 41 +- .../org/apache/cassandra/tools/GetVersionTest.java | 6 +- .../apache/cassandra/tools/LoaderOptionsTest.java | 2 +- .../{ToolsTester.java => OfflineToolUtils.java} | 66 +--- .../tools/SSTableExpiredBlockersTest.java | 10 +- .../apache/cassandra/tools/SSTableExportTest.java | 11 +- .../cassandra/tools/SSTableLevelResetterTest.java | 11 +- .../cassandra/tools/SSTableMetadataViewerTest.java | 10 +- .../cassandra/tools/SSTableOfflineRelevelTest.java | 10 +- .../tools/SSTableRepairedAtSetterTest.java | 11 +- .../cassandra/tools/StandaloneSSTableUtilTest.java | 11 +- .../cassandra/tools/StandaloneScrubberTest.java | 11 +- .../cassandra/tools/StandaloneSplitterTest.java | 8 +- .../cassandra/tools/StandaloneUpgraderTest.java | 11 +- .../cassandra/tools/StandaloneVerifierTest.java | 11 +- .../org/apache/cassandra/tools/ToolRunner.java | 437 +++++++++++++++++++++ 20 files changed, 676 insertions(+), 178 deletions(-) diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java index 576862b..8f731e2 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java @@ -49,7 +49,7 @@ import static org.junit.Assert.fail; * unexpected threads. * * {@link DatabaseDescriptor#toolInitialization()} is tested via unit tests extending - * {@link org.apache.cassandra.tools.ToolsTester}. + * {@link org.apache.cassandra.tools.OfflineToolUtils}. */ public class DatabaseDescriptorRefTest { diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 619fdad..0455f97 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -22,9 +22,12 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.UnknownHostException; import java.nio.ByteBuffer; +import java.rmi.server.RMISocketFactory; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -34,6 +37,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; + import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -80,6 +89,7 @@ import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.JMXServerUtils; import org.apache.cassandra.security.ThreadAwareSecurityManager; import static junit.framework.Assert.assertNotNull; @@ -103,6 +113,11 @@ public abstract class CQLTester public static final String RACK1 = "rack1"; private static org.apache.cassandra.transport.Server server; + private static JMXConnectorServer jmxServer; + protected static String jmxHost; + protected static int jmxPort; + protected static MBeanServerConnection jmxConnection; + protected static final int nativePort; protected static final InetAddress nativeAddr; protected static final Set<InetAddressAndPort> remoteAddrs = new HashSet<>(); @@ -240,6 +255,43 @@ public abstract class CQLTester AuditLogManager.instance.initialize(); isServerPrepared = true; } + + /** + * Starts the JMX server. It's safe to call this method multiple times. + */ + public static void startJMXServer() throws Exception + { + if (jmxServer != null) + return; + + InetAddress loopback = InetAddress.getLoopbackAddress(); + jmxHost = loopback.getHostAddress(); + try (ServerSocket sock = new ServerSocket()) + { + sock.bind(new InetSocketAddress(loopback, 0)); + jmxPort = sock.getLocalPort(); + } + + jmxServer = JMXServerUtils.createJMXServer(jmxPort, true); + jmxServer.start(); + } + + public static void createMBeanServerConnection() throws Exception + { + assert jmxServer != null : "jmxServer not started"; + + Map<String, Object> env = new HashMap<>(); + env.put("com.sun.jndi.rmi.factory.socket", RMISocketFactory.getDefaultSocketFactory()); + JMXConnector jmxc = JMXConnectorFactory.connect(getJMXServiceURL(), env); + jmxConnection = jmxc.getMBeanServerConnection(); + } + + public static JMXServiceURL getJMXServiceURL() throws MalformedURLException + { + assert jmxServer != null : "jmxServer not started"; + + return new JMXServiceURL(String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", jmxHost, jmxPort)); + } public static void cleanupAndLeaveDirs() throws IOException { @@ -399,7 +451,19 @@ public abstract class CQLTester } }); } - + + public static List<String> buildNodetoolArgs(List<String> args) + { + List<String> allArgs = new ArrayList<>(); + allArgs.add("bin/nodetool"); + allArgs.add("-p"); + allArgs.add(Integer.toString(jmxPort)); + allArgs.add("-h"); + allArgs.add(jmxHost); + allArgs.addAll(args); + return allArgs; + } + // lazy initialization for all tests that require Java Driver protected static void requireNetwork() throws ConfigurationException { diff --git a/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java b/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java index 6ed38a0..354511a 100644 --- a/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java +++ b/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java @@ -24,15 +24,22 @@ import org.junit.runner.RunWith; import com.datastax.driver.core.exceptions.NoHostAvailableException; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(OrderedJUnit4ClassRunner.class) -public class BulkLoaderTest extends ToolsTester +public class BulkLoaderTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test - public void testBulkLoader_NoArgs() + public void testBulkLoader_NoArgs() throws Exception { - runTool(1, "org.apache.cassandra.tools.BulkLoader"); + ToolRunner tool = runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader"); + assertEquals(1, tool.getExitCode()); + assertTrue(!tool.getStderr().isEmpty()); + assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -40,13 +47,14 @@ public class BulkLoaderTest extends ToolsTester assertKeyspaceNotLoaded(); assertServerNotLoaded(); } - + @Test public void testBulkLoader_WithArgs() throws Exception { try { - runTool(0, "org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", sstableDirName("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", OfflineToolUtils.sstableDirName("legacy_sstables", "legacy_ma_simple")) + .waitAndAssertOnCleanExit(); fail(); } catch (RuntimeException e) @@ -69,7 +77,8 @@ public class BulkLoaderTest extends ToolsTester { try { - runTool(0, "org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", "--port", "9042", sstableDirName("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", "--port", "9042", OfflineToolUtils.sstableDirName("legacy_sstables", "legacy_ma_simple")) + .waitAndAssertOnCleanExit(); fail(); } catch (RuntimeException e) @@ -92,7 +101,8 @@ public class BulkLoaderTest extends ToolsTester { try { - runTool(0, "org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1:9042", "--port", "9041", sstableDirName("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1:9042", "--port", "9041", OfflineToolUtils.sstableDirName("legacy_sstables", "legacy_ma_simple")) + .waitAndAssertOnCleanExit(); fail(); } catch (RuntimeException e) @@ -115,7 +125,7 @@ public class BulkLoaderTest extends ToolsTester { try { - runTool(1, "org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", "--port", "9041", sstableDirName("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1", "--port", "9041", OfflineToolUtils.sstableDirName("legacy_sstables", "legacy_ma_simple")); } catch (RuntimeException e) { @@ -128,7 +138,7 @@ public class BulkLoaderTest extends ToolsTester { try { - runTool(1, "org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1:9041", sstableDirName("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.BulkLoader", "-d", "127.9.9.1:9041", OfflineToolUtils.sstableDirName("legacy_sstables", "legacy_ma_simple")); } catch (RuntimeException e) { diff --git a/test/unit/org/apache/cassandra/tools/ClearSnapshotTest.java b/test/unit/org/apache/cassandra/tools/ClearSnapshotTest.java index a73493b..7e70467 100644 --- a/test/unit/org/apache/cassandra/tools/ClearSnapshotTest.java +++ b/test/unit/org/apache/cassandra/tools/ClearSnapshotTest.java @@ -20,102 +20,85 @@ package org.apache.cassandra.tools; import java.io.IOException; import java.util.Map; + import javax.management.openmbean.TabularData; -import org.apache.commons.lang3.ArrayUtils; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import org.apache.cassandra.SchemaLoader; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.service.EmbeddedCassandraService; +import org.apache.cassandra.cql3.CQLTester; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -public class ClearSnapshotTest extends ToolsTester +public class ClearSnapshotTest extends CQLTester { - private static EmbeddedCassandraService cassandra; - private static String initialJmxPortValue; private static NodeProbe probe; - private static final int JMX_PORT = 7188; + private ToolRunner.Runners runner = new ToolRunner.Runners(); @BeforeClass - public static void setup() throws IOException + public static void setup() throws Exception { - // Set system property to enable JMX port on localhost for embedded server - initialJmxPortValue = System.getProperty("cassandra.jmx.local.port"); - System.setProperty("cassandra.jmx.local.port", String.valueOf(JMX_PORT)); - - SchemaLoader.prepareServer(); - // CASSANDRA-15776 - size_estimates and table_estimates get truncated on startup, and auto snapshot is true by default for tests - // set it false so the test state doesn't see those snapshots - DatabaseDescriptor.setAutoSnapshot(false); - cassandra = new EmbeddedCassandraService(); - cassandra.start(); - - probe = new NodeProbe("127.0.0.1", JMX_PORT); + startJMXServer(); + probe = new NodeProbe(jmxHost, jmxPort); } @AfterClass public static void teardown() throws IOException { - cassandra.stop(); - if (initialJmxPortValue != null) - { - System.setProperty("cassandra.jmx.local.port", initialJmxPortValue); - } - probe.close(); } - private String[] constructParamaterArray(final String command, final String... commandParams) - { - String[] baseCommandLine = {"-p", String.valueOf(JMX_PORT), command}; - return ArrayUtils.addAll(baseCommandLine, commandParams); - } - @Test public void testClearSnapshot_NoArgs() throws IOException { - runTool(2, "org.apache.cassandra.tools.NodeTool", - constructParamaterArray("clearsnapshot")); + ToolRunner tool = runner.invokeNodetool("clearsnapshot"); + assertEquals(2, tool.getExitCode()); + assertTrue("Tool stderr: " + tool.getStderr(), tool.getStderr().contains("Specify snapshot name or --all")); + + runner.invokeNodetool("clearsnapshot", "--all").waitAndAssertOnCleanExit(); } @Test public void testClearSnapshot_AllAndName() throws IOException { - runTool(2, "org.apache.cassandra.tools.NodeTool", - constructParamaterArray("clearsnapshot", "-t", "some-name", "--all")); + ToolRunner tool = runner.invokeNodetool("clearsnapshot", "-t", "some-name", "--all"); + assertEquals(2, tool.getExitCode()); + assertTrue("Tool stderr: " + tool.getStderr(), tool.getStderr().contains("Specify only one of snapshot name or --all")); } @Test public void testClearSnapshot_RemoveByName() throws IOException { - runTool(0,"org.apache.cassandra.tools.NodeTool", - constructParamaterArray("snapshot","-t","some-name")); - - Map<String, TabularData> snapshots_before = probe.getSnapshotDetails(); - Assert.assertTrue(snapshots_before.containsKey("some-name")); - - runTool(0,"org.apache.cassandra.tools.NodeTool", - constructParamaterArray("clearsnapshot","-t","some-name")); - Map<String, TabularData> snapshots_after = probe.getSnapshotDetails(); - Assert.assertFalse(snapshots_after.containsKey("some-name")); + ToolRunner tool = runner.invokeNodetool("snapshot","-t","some-name").waitAndAssertOnCleanExit(); + assertTrue(!tool.getStdout().isEmpty()); + + Map<String, TabularData> snapshots_before = probe.getSnapshotDetails(); + Assert.assertTrue(snapshots_before.containsKey("some-name")); + + tool = runner.invokeNodetool("clearsnapshot","-t","some-name").waitAndAssertOnCleanExit(); + assertTrue(!tool.getStdout().isEmpty()); + + Map<String, TabularData> snapshots_after = probe.getSnapshotDetails(); + Assert.assertFalse(snapshots_after.containsKey("some-name")); } @Test public void testClearSnapshot_RemoveMultiple() throws IOException { - runTool(0,"org.apache.cassandra.tools.NodeTool", - constructParamaterArray("snapshot","-t","some-name")); - runTool(0,"org.apache.cassandra.tools.NodeTool", - constructParamaterArray("snapshot","-t","some-other-name")); - + ToolRunner tool = runner.invokeNodetool("snapshot","-t","some-name").waitAndAssertOnCleanExit(); + assertTrue(!tool.getStdout().isEmpty()); + tool = runner.invokeNodetool("snapshot","-t","some-other-name").waitAndAssertOnCleanExit(); + assertTrue(!tool.getStdout().isEmpty()); + Map<String, TabularData> snapshots_before = probe.getSnapshotDetails(); Assert.assertTrue(snapshots_before.size() == 2); - runTool(0,"org.apache.cassandra.tools.NodeTool", - constructParamaterArray("clearsnapshot","--all")); + tool = runner.invokeNodetool("clearsnapshot","--all").waitAndAssertOnCleanExit(); + assertTrue(!tool.getStdout().isEmpty()); + Map<String, TabularData> snapshots_after = probe.getSnapshotDetails(); Assert.assertTrue(snapshots_after.size() == 0); } diff --git a/test/unit/org/apache/cassandra/tools/CompactionStressTest.java b/test/unit/org/apache/cassandra/tools/CompactionStressTest.java index c8b0b97..651e24d 100644 --- a/test/unit/org/apache/cassandra/tools/CompactionStressTest.java +++ b/test/unit/org/apache/cassandra/tools/CompactionStressTest.java @@ -26,12 +26,14 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; @RunWith(OrderedJUnit4ClassRunner.class) -public class CompactionStressTest extends ToolsTester +public class CompactionStressTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testNoArgs() { - runTool(0, "org.apache.cassandra.stress.CompactionStress"); + runner.invokeClassAsTool("org.apache.cassandra.stress.CompactionStress").waitAndAssertOnCleanExit(); } @Test @@ -41,20 +43,27 @@ public class CompactionStressTest extends ToolsTester File file = new File(classLoader.getResource("blogpost.yaml").getFile()); String profileFile = file.getAbsolutePath(); - runTool(0, - "org.apache.cassandra.stress.CompactionStress", - "write", - "-d", "build/test/cassandra", - "-g", "0", - "-p", profileFile, - "-t", "4"); - - runTool(0, - "org.apache.cassandra.stress.CompactionStress", - "compact", - "-d", "build/test/cassandra", - "-p", profileFile, - "-t", "4"); + runner.invokeClassAsTool("org.apache.cassandra.stress.CompactionStress", + "write", + "-d", + "build/test/cassandra", + "-g", + "0", + "-p", + profileFile, + "-t", + "4") + .waitAndAssertOnCleanExit(); + + runner.invokeClassAsTool("org.apache.cassandra.stress.CompactionStress", + "compact", + "-d", + "build/test/cassandra", + "-p", + profileFile, + "-t", + "4") + .waitAndAssertOnCleanExit(); } } diff --git a/test/unit/org/apache/cassandra/tools/GetVersionTest.java b/test/unit/org/apache/cassandra/tools/GetVersionTest.java index 84e2f49..c5f5282 100644 --- a/test/unit/org/apache/cassandra/tools/GetVersionTest.java +++ b/test/unit/org/apache/cassandra/tools/GetVersionTest.java @@ -24,12 +24,14 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; @RunWith(OrderedJUnit4ClassRunner.class) -public class GetVersionTest extends ToolsTester +public class GetVersionTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testGetVersion() { - runTool(0, "org.apache.cassandra.tools.GetVersion"); + runner.invokeClassAsTool("org.apache.cassandra.tools.GetVersion").waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/LoaderOptionsTest.java b/test/unit/org/apache/cassandra/tools/LoaderOptionsTest.java index 67258ad..6377c59 100644 --- a/test/unit/org/apache/cassandra/tools/LoaderOptionsTest.java +++ b/test/unit/org/apache/cassandra/tools/LoaderOptionsTest.java @@ -23,7 +23,7 @@ import org.junit.Test; import org.apache.cassandra.config.DatabaseDescriptor; -import static org.apache.cassandra.tools.ToolsTester.sstableDirName; +import static org.apache.cassandra.tools.OfflineToolUtils.sstableDirName; import static org.junit.Assert.*; // LoaderOptionsTester for custom configuration diff --git a/test/unit/org/apache/cassandra/tools/ToolsTester.java b/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java similarity index 80% rename from test/unit/org/apache/cassandra/tools/ToolsTester.java rename to test/unit/org/apache/cassandra/tools/OfflineToolUtils.java index 0bb9beb..9383f7d 100644 --- a/test/unit/org/apache/cassandra/tools/ToolsTester.java +++ b/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java @@ -23,9 +23,7 @@ import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.security.Permission; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -35,20 +33,20 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; + import org.junit.BeforeClass; import org.slf4j.LoggerFactory; import static org.apache.cassandra.utils.FBUtilities.preventIllegalAccessWarnings; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** - * Base unit test class for standalone tools + * Helper class for running a tool and doing in-process checks */ -public abstract class ToolsTester +public abstract class OfflineToolUtils { static { @@ -193,69 +191,13 @@ public abstract class ToolsTester fail(clazz + " has not been loaded"); } - public void runTool(int expectedExitCode, String clazz, String... args) - { - try - { - // install security manager to get informed about the exit-code - System.setSecurityManager(new SecurityManager() - { - public void checkExit(int status) - { - throw new SystemExitException(status); - } - - public void checkPermission(Permission perm) - { - } - - public void checkPermission(Permission perm, Object context) - { - } - }); - - try - { - Class.forName(clazz).getDeclaredMethod("main", String[].class).invoke(null, (Object) args); - } - catch (InvocationTargetException e) - { - Throwable cause = e.getCause(); - if (cause instanceof Error) - throw (Error) cause; - if (cause instanceof RuntimeException) - throw (RuntimeException) cause; - throw e; - } - - assertEquals("Unexpected exit code", expectedExitCode, 0); - } - catch (SystemExitException e) - { - assertEquals("Unexpected exit code", expectedExitCode, e.status); - } - catch (InvocationTargetException e) - { - throw new RuntimeException(e.getTargetException()); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - finally - { - // uninstall security manager - System.setSecurityManager(null); - } - } - @BeforeClass public static void setupTester() { System.setProperty("cassandra.partitioner", "org.apache.cassandra.dht.Murmur3Partitioner"); // may start an async appender - LoggerFactory.getLogger(ToolsTester.class); + LoggerFactory.getLogger(OfflineToolUtils.class); ThreadMXBean threads = ManagementFactory.getThreadMXBean(); initialThreads = Arrays.asList(threads.getThreadInfo(threads.getAllThreadIds())); diff --git a/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java b/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java index 1c09174..ad2dc3e 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableExpiredBlockersTest extends ToolsTester +public class SSTableExpiredBlockersTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableExpiredBlockers_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableExpiredBlockers"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableExpiredBlockers").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -42,7 +46,7 @@ public class SSTableExpiredBlockersTest extends ToolsTester public void testSSTableExpiredBlockers_WithArgs() { // returns exit code 1, since no sstables are there - runTool(1, "org.apache.cassandra.tools.SSTableExpiredBlockers", "system_schema", "tables"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableExpiredBlockers", "system_schema", "tables").getExitCode()); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableExportTest.java b/test/unit/org/apache/cassandra/tools/SSTableExportTest.java index 0e49bb5..54af60c 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableExportTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableExportTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableExportTest extends ToolsTester +public class SSTableExportTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableExport_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableExport"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableExport").getExitCode()); assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class SSTableExportTest extends ToolsTester @Test public void testSSTableExport_WithArgs() throws Exception { - runTool(0, "org.apache.cassandra.tools.SSTableExport", findOneSSTable("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableExport",findOneSSTable("legacy_sstables", "legacy_ma_simple")) + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java b/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java index b70fcef..947a988 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableLevelResetterTest extends ToolsTester +public class SSTableLevelResetterTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableLevelResetter_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableLevelResetter"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableLevelResetter").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class SSTableLevelResetterTest extends ToolsTester @Test public void testSSTableLevelResetter_WithArgs() { - runTool(0, "org.apache.cassandra.tools.SSTableLevelResetter", "--really-reset", "system_schema", "tables"); + runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableLevelResetter", "--really-reset", "system_schema", "tables") + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java b/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java index 8b782e3..9370315 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableMetadataViewerTest extends ToolsTester +public class SSTableMetadataViewerTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableOfflineRelevel_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableMetadataViewer"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableMetadataViewer").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,7 @@ public class SSTableMetadataViewerTest extends ToolsTester @Test public void testSSTableOfflineRelevel_WithArgs() { - runTool(0, "org.apache.cassandra.tools.SSTableMetadataViewer", "ks", "tab"); + runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableMetadataViewer", "ks", "tab").waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java b/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java index 0c6eecf..1d155bc 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableOfflineRelevelTest extends ToolsTester +public class SSTableOfflineRelevelTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableOfflineRelevel_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableOfflineRelevel"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableOfflineRelevel").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -42,7 +46,7 @@ public class SSTableOfflineRelevelTest extends ToolsTester public void testSSTableOfflineRelevel_WithArgs() { // Note: SSTableOfflineRelevel exits with code 1 if no sstables to relevel have been found - runTool(1, "org.apache.cassandra.tools.SSTableOfflineRelevel", "system_schema", "tables"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableOfflineRelevel", "system_schema", "tables").getExitCode()); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java b/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java index 9dece5a..737b0eb 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class SSTableRepairedAtSetterTest extends ToolsTester +public class SSTableRepairedAtSetterTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testSSTableRepairedAtSetter_NoArgs() { - runTool(1, "org.apache.cassandra.tools.SSTableRepairedAtSetter"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableRepairedAtSetter").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class SSTableRepairedAtSetterTest extends ToolsTester @Test public void testSSTableRepairedAtSetter_WithArgs() throws Exception { - runTool(0, "org.apache.cassandra.tools.SSTableRepairedAtSetter", "--really-set", "--is-repaired", findOneSSTable("legacy_sstables", "legacy_ma_simple")); + runner.invokeClassAsTool("org.apache.cassandra.tools.SSTableRepairedAtSetter", "--really-set", "--is-repaired", findOneSSTable("legacy_sstables", "legacy_ma_simple")) + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/StandaloneSSTableUtilTest.java b/test/unit/org/apache/cassandra/tools/StandaloneSSTableUtilTest.java index 2026a79..834f537 100644 --- a/test/unit/org/apache/cassandra/tools/StandaloneSSTableUtilTest.java +++ b/test/unit/org/apache/cassandra/tools/StandaloneSSTableUtilTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class StandaloneSSTableUtilTest extends ToolsTester +public class StandaloneSSTableUtilTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testStandaloneSSTableUtil_NoArgs() { - runTool(1, "org.apache.cassandra.tools.StandaloneSSTableUtil"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneSSTableUtil").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class StandaloneSSTableUtilTest extends ToolsTester @Test public void testStandaloneSSTableUtil_WithArgs() { - runTool(0, "org.apache.cassandra.tools.StandaloneSSTableUtil", "--debug", "-c", "system_schema", "tables"); + runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneSSTableUtil", "--debug", "-c", "system_schema", "tables") + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/StandaloneScrubberTest.java b/test/unit/org/apache/cassandra/tools/StandaloneScrubberTest.java index 6aef5eb..a99f657 100644 --- a/test/unit/org/apache/cassandra/tools/StandaloneScrubberTest.java +++ b/test/unit/org/apache/cassandra/tools/StandaloneScrubberTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class StandaloneScrubberTest extends ToolsTester +public class StandaloneScrubberTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testStandaloneScrubber_NoArgs() { - runTool(1, "org.apache.cassandra.tools.StandaloneScrubber"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneScrubber").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class StandaloneScrubberTest extends ToolsTester @Test public void testStandaloneScrubber_WithArgs() { - runTool(0, "org.apache.cassandra.tools.StandaloneScrubber", "--debug", "system_schema", "tables"); + runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneScrubber", "--debug", "system_schema", "tables") + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/StandaloneSplitterTest.java b/test/unit/org/apache/cassandra/tools/StandaloneSplitterTest.java index c0f8593..753a258 100644 --- a/test/unit/org/apache/cassandra/tools/StandaloneSplitterTest.java +++ b/test/unit/org/apache/cassandra/tools/StandaloneSplitterTest.java @@ -24,9 +24,13 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class StandaloneSplitterTest extends ToolsTester +public class StandaloneSplitterTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @BeforeClass public static void before() { @@ -39,7 +43,7 @@ public class StandaloneSplitterTest extends ToolsTester @Test public void testStandaloneSplitter_NoArgs() { - runTool(1, "org.apache.cassandra.tools.StandaloneSplitter"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneSplitter").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/StandaloneUpgraderTest.java b/test/unit/org/apache/cassandra/tools/StandaloneUpgraderTest.java index f703a49..1bfbbd2 100644 --- a/test/unit/org/apache/cassandra/tools/StandaloneUpgraderTest.java +++ b/test/unit/org/apache/cassandra/tools/StandaloneUpgraderTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class StandaloneUpgraderTest extends ToolsTester +public class StandaloneUpgraderTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testStandaloneUpgrader_NoArgs() { - runTool(1, "org.apache.cassandra.tools.StandaloneUpgrader"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneUpgrader").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class StandaloneUpgraderTest extends ToolsTester @Test public void testStandaloneUpgrader_WithArgs() { - runTool(0, "org.apache.cassandra.tools.StandaloneUpgrader", "--debug", "system_schema", "tables"); + runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneUpgrader", "--debug", "system_schema", "tables") + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/StandaloneVerifierTest.java b/test/unit/org/apache/cassandra/tools/StandaloneVerifierTest.java index 38418f3..e33a154 100644 --- a/test/unit/org/apache/cassandra/tools/StandaloneVerifierTest.java +++ b/test/unit/org/apache/cassandra/tools/StandaloneVerifierTest.java @@ -23,13 +23,17 @@ import org.junit.runner.RunWith; import org.apache.cassandra.OrderedJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @RunWith(OrderedJUnit4ClassRunner.class) -public class StandaloneVerifierTest extends ToolsTester +public class StandaloneVerifierTest extends OfflineToolUtils { + private ToolRunner.Runners runner = new ToolRunner.Runners(); + @Test public void testStandaloneVerifier_NoArgs() { - runTool(1, "org.apache.cassandra.tools.StandaloneVerifier"); + assertEquals(1, runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneVerifier").getExitCode()); assertNoUnexpectedThreadsStarted(null, null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); @@ -41,7 +45,8 @@ public class StandaloneVerifierTest extends ToolsTester @Test public void testStandaloneVerifier_WithArgs() { - runTool(0, "org.apache.cassandra.tools.StandaloneVerifier", "--debug", "system_schema", "tables"); + runner.invokeClassAsTool("org.apache.cassandra.tools.StandaloneVerifier", "--debug", "system_schema", "tables") + .waitAndAssertOnCleanExit(); assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/ToolRunner.java b/test/unit/org/apache/cassandra/tools/ToolRunner.java new file mode 100644 index 0000000..61e32b0 --- /dev/null +++ b/test/unit/org/apache/cassandra/tools/ToolRunner.java @@ -0,0 +1,437 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.tools; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.security.Permission; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.google.common.base.Preconditions; + +import org.apache.commons.io.IOUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.tools.OfflineToolUtils.SystemExitException; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ToolRunner implements AutoCloseable +{ + protected static final Logger logger = LoggerFactory.getLogger(ToolRunner.class); + + private final List<String> allArgs = new ArrayList<>(); + private Process process; + private final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream(); + private final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + private InputStream stdin; + private boolean stdinAutoClose; + private long defaultTimeoutMillis = TimeUnit.SECONDS.toMillis(30); + private Thread ioWatcher; + private Map<String, String> envs; + private boolean runOutOfProcess = true; + + public ToolRunner(List<String> args) + { + this.allArgs.addAll(args); + } + + public ToolRunner(List<String> args, boolean runOutOfProcess) + { + this.allArgs.addAll(args); + this.runOutOfProcess = runOutOfProcess; + } + + public ToolRunner withStdin(InputStream stdin, boolean autoClose) + { + this.stdin = stdin; + this.stdinAutoClose = autoClose; + return this; + } + + public ToolRunner withEnvs(Map<String, String> envs) + { + Preconditions.checkArgument(runOutOfProcess, "Not supported"); + this.envs = envs; + return this; + } + + public ToolRunner start() + { + if (process != null) + throw new IllegalStateException("Process already started. Create a new ToolRunner instance for each invocation."); + + logger.debug("Starting {} with args {}", runOutOfProcess ? "process" : "class" , argsToLogString()); + + try + { + if (runOutOfProcess) + { + ProcessBuilder pb = new ProcessBuilder(allArgs); + if (envs != null) + pb.environment().putAll(envs); + process = pb.start(); + } + else + { + PrintStream originalSysOut = System.out; + PrintStream originalSysErr = System.err; + InputStream originalSysIn = System.in; + originalSysOut.flush(); + originalSysErr.flush(); + ByteArrayOutputStream toolOut = new ByteArrayOutputStream(); + ByteArrayOutputStream toolErr = new ByteArrayOutputStream(); + + System.setIn(stdin == null ? originalSysIn : stdin); + int exit = 0; + try (PrintStream newOut = new PrintStream(toolOut); PrintStream newErr = new PrintStream(toolErr);) + { + System.setOut(newOut); + System.setErr(newErr); + String clazz = allArgs.get(0); + String[] clazzArgs = allArgs.subList(1, allArgs.size()).toArray(new String[0]); + exit = runClassAsTool(clazz, clazzArgs); + } + + final int exitCode = exit; + System.setOut(originalSysOut); + System.setErr(originalSysErr); + System.setIn(originalSysIn); + + process = new Process() { + + @Override + public void destroy() + { + } + + @Override + public int exitValue() + { + return exitCode; + } + + @Override + public InputStream getErrorStream() + { + return new ByteArrayInputStream(toolErr.toByteArray()); + } + + @Override + public InputStream getInputStream() + { + return new ByteArrayInputStream(toolOut.toByteArray()); + } + + @Override + public OutputStream getOutputStream() + { + if (stdin == null) + return null; + + ByteArrayOutputStream out = null; + try + { + out = new ByteArrayOutputStream(stdin.available()); + IOUtils.copy(stdin, out); + } + catch(IOException e) + { + throw new RuntimeException("Failed to get stdin", e); + } + return out; + } + + @Override + public int waitFor() throws InterruptedException + { + return exitValue(); + } + + }; + } + + ioWatcher = new Thread(this::watchIO); + ioWatcher.setDaemon(true); + ioWatcher.start(); + } + catch (IOException e) + { + throw new RuntimeException("Failed to start " + allArgs, e); + } + + return this; + } + + private void watchIO() + { + OutputStream in = process.getOutputStream(); + InputStream err = process.getErrorStream(); + InputStream out = process.getInputStream(); + while (true) + { + boolean errHandled; + boolean outHandled; + try + { + if (stdin != null) + { + IOUtils.copy(stdin, in); + if (stdinAutoClose) + { + in.close(); + stdin = null; + } + } + errHandled = IOUtils.copy(err, errBuffer) > 0; + outHandled = IOUtils.copy(out, outBuffer) > 0; + } + catch(IOException e1) + { + logger.error("Error trying to use in/err/out from process"); + Thread.currentThread().interrupt(); + break; + } + if (!errHandled && !outHandled) + { + if (!process.isAlive()) + return; + try + { + Thread.sleep(50L); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + break; + } + } + } + } + + public int runClassAsTool(String clazz, String... args) + { + try + { + // install security manager to get informed about the exit-code + System.setSecurityManager(new SecurityManager() + { + public void checkExit(int status) + { + throw new SystemExitException(status); + } + + public void checkPermission(Permission perm) + { + } + + public void checkPermission(Permission perm, Object context) + { + } + }); + + try + { + Class.forName(clazz).getDeclaredMethod("main", String[].class).invoke(null, (Object) args); + } + catch (InvocationTargetException e) + { + Throwable cause = e.getCause(); + if (cause instanceof Error) + throw (Error) cause; + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + throw e; + } + + return 0; + } + catch (SystemExitException e) + { + return e.status; + } + catch (InvocationTargetException e) + { + throw new RuntimeException(e.getTargetException()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + finally + { + // uninstall security manager + System.setSecurityManager(null); + } + } + + public boolean isRunning() + { + return process != null && process.isAlive(); + } + + public boolean waitFor() + { + return waitFor(defaultTimeoutMillis, TimeUnit.MILLISECONDS); + } + + public boolean waitFor(long time, TimeUnit timeUnit) + { + try + { + if (!process.waitFor(time, timeUnit)) + return false; + ioWatcher.join(); + return true; + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + } + + public ToolRunner waitAndAssertOnExitCode() + { + assertTrue(String.format("Tool %s didn't terminate", + argsToLogString()), + waitFor()); + return assertOnExitCode(); + } + + public ToolRunner waitAndAssertOnCleanExit() + { + return waitAndAssertOnExitCode().assertEmptyStdErr(); + } + + public ToolRunner assertEmptyStdErr() + { + assertTrue(getStderr().isEmpty()); + return this; + } + + public ToolRunner assertOnExitCode() + { + int code = getExitCode(); + if (code != 0) + fail(String.format("%s%nexited with code %d%nstderr:%n%s%nstdout:%n%s", + argsToLogString(), + code, + getStderr(), + getStdout())); + return this; + } + + public String argsToLogString() + { + return allArgs.stream().collect(Collectors.joining(",\n ", "[", "]")); + } + + public int getExitCode() + { + return process.exitValue(); + } + + public String getStdout() + { + return outBuffer.toString(); + } + + public String getStderr() + { + return errBuffer.toString(); + } + + public void forceKill() + { + try + { + process.exitValue(); + // process no longer alive - just ignore that fact + } + catch (IllegalThreadStateException e) + { + process.destroyForcibly(); + } + } + + @Override + public void close() + { + forceKill(); + } + + static class Runners + { + protected ToolRunner invokeNodetool(String... args) + { + return invokeNodetool(Arrays.asList(args)); + } + + protected ToolRunner invokeNodetool(List<String> args) + { + return invokeTool(buildNodetoolArgs(args), true); + } + + private static List<String> buildNodetoolArgs(List<String> args) + { + return CQLTester.buildNodetoolArgs(args); + } + + protected ToolRunner invokeClassAsTool(String... args) + { + return invokeClassAsTool(Arrays.asList(args)); + } + + protected ToolRunner invokeClassAsTool(List<String> args) + { + return invokeTool(args, false); + } + + protected ToolRunner invokeTool(String... args) + { + return invokeTool(Arrays.asList(args)); + } + + protected ToolRunner invokeTool(List<String> args) + { + return invokeTool(args, true); + } + + protected ToolRunner invokeTool(List<String> args, boolean runOutOfProcess) + { + ToolRunner runner = new ToolRunner(args, runOutOfProcess); + runner.start().waitFor(); + return runner; + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org