From 39770da24c1e7cf3a62c993b6cdf1674c4f46e1c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 9 Mar 2018 12:42:32 +1100 Subject: [PATCH 1/8] CLI Command: MultiCommand must close subcommands to release resources properly Changes done to override close method and call close on subcommands. Closes #28953 --- .../java/org/elasticsearch/cli/MultiCommand.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java index ba6b447792aa1..d3266084b33b1 100644 --- a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java +++ b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java @@ -19,6 +19,7 @@ package org.elasticsearch.cli; +import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -74,4 +75,16 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { } subcommand.mainWithoutErrorHandling(Arrays.copyOfRange(args, 1, args.length), terminal); } + + @Override + public void close() throws IOException { + if (subcommands.isEmpty()) { + throw new IllegalStateException("No subcommands configured"); + } + for (Command command : subcommands.values()) { + if (command != null) { + command.close(); + } + } + } } From bf6a3eb2c6e131ce1f2a68026bfe4ea47b2d0b82 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 13 Mar 2018 08:40:37 +1100 Subject: [PATCH 2/8] CLI: MultiCommand#close address review comments Handling sub command close exceptions, so others can be closed and then rethrow exception. Adding unit tests. Closes #28953 --- .../org/elasticsearch/cli/MultiCommand.java | 47 ++++++++++++++++--- .../elasticsearch/cli/MultiCommandTests.java | 36 ++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java index d3266084b33b1..ba6507c2b68fd 100644 --- a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java +++ b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java @@ -19,6 +19,7 @@ package org.elasticsearch.cli; +import java.io.Closeable; import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashMap; @@ -77,14 +78,46 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { } @Override - public void close() throws IOException { - if (subcommands.isEmpty()) { - throw new IllegalStateException("No subcommands configured"); - } - for (Command command : subcommands.values()) { - if (command != null) { - command.close(); + public void close() throws IOException, RuntimeException { + Throwable th = null; + for (Closeable object : subcommands.values()) { + try { + if (object != null) { + object.close(); + } + } catch (Throwable t) { + addSuppressed(th, t); + if (th == null) { + th = t; + } } } + if (th != null) { + throw reThrowAlways(th); + } + } + + // Following methods are similar to IOUtils, + // avoiding lucene dependency in CLI. + private static void addSuppressed(Throwable t, Throwable suppressed) { + if (t != null && suppressed != null) { + t.addSuppressed(suppressed); + } + } + + private static Error reThrowAlways(Throwable th) throws IOException, RuntimeException { + if (th instanceof IOException) { + throw (IOException) th; + } + + if (th instanceof RuntimeException) { + throw (RuntimeException) th; + } + + if (th instanceof Error) { + throw (Error) th; + } + + throw new RuntimeException(th); } } diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index f4448bbedfef5..dc4373e5ffa47 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -21,6 +21,14 @@ import joptsimple.OptionSet; import org.junit.Before; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class MultiCommandTests extends CommandTestCase { @@ -102,4 +110,32 @@ public void testSubcommandArguments() throws Exception { assertFalse(output, output.contains("command1")); assertTrue(output, output.contains("Arguments: [foo, bar]")); } + + public void testClose() throws Exception { + Command spySubCommand1 = spy(new DummySubCommand()); + Command spySubCommand2 = spy(new DummySubCommand()); + multiCommand.subcommands.put("command1", spySubCommand1); + multiCommand.subcommands.put("command2", spySubCommand2); + multiCommand.close(); + verify(spySubCommand1, times(1)).close(); + verify(spySubCommand2, times(1)).close(); + } + + public void testCloseWhenSubCommandCloseThrowsException() throws Exception { + Command subCommand1 = mock(DummySubCommand.class); + Mockito.doThrow(new IOException()).when(subCommand1).close(); + Command spySubCommand2 = spy(new DummySubCommand()); + multiCommand.subcommands.put("command1", subCommand1); + multiCommand.subcommands.put("command2", spySubCommand2); + IOException ioe = null; + try { + multiCommand.close(); + } catch (IOException e) { + ioe = e; + } + // verify exception is thrown, as well as other non failed sub-commands closed + // properly. + assertNotNull("Expected IOException", ioe); + verify(spySubCommand2, times(1)).close(); + } } From 52a71d96dc9cd00dc689d824c39a53962ae387a0 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 14 Mar 2018 10:37:37 +1100 Subject: [PATCH 3/8] CLI: MultiCommand#close address review comments CLI now depends on elasticsearch-core, for IOUtils. Closes #28953 --- server/cli/build.gradle | 1 + .../org/elasticsearch/cli/MultiCommand.java | 44 ++----------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/server/cli/build.gradle b/server/cli/build.gradle index c41c4d975b082..91fbca19eca99 100644 --- a/server/cli/build.gradle +++ b/server/cli/build.gradle @@ -36,6 +36,7 @@ archivesBaseName = 'elasticsearch-cli' dependencies { compile 'net.sf.jopt-simple:jopt-simple:5.0.2' + compile "org.elasticsearch:elasticsearch-core:${version}" } test.enabled = false diff --git a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java index ba6507c2b68fd..054a29e78a6cc 100644 --- a/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java +++ b/server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java @@ -28,6 +28,8 @@ import joptsimple.NonOptionArgumentSpec; import joptsimple.OptionSet; +import org.elasticsearch.core.internal.io.IOUtils; + /** * A cli tool which is made up of multiple subcommands. */ @@ -78,46 +80,8 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { } @Override - public void close() throws IOException, RuntimeException { - Throwable th = null; - for (Closeable object : subcommands.values()) { - try { - if (object != null) { - object.close(); - } - } catch (Throwable t) { - addSuppressed(th, t); - if (th == null) { - th = t; - } - } - } - if (th != null) { - throw reThrowAlways(th); - } + public void close() throws IOException { + IOUtils.close(subcommands.values()); } - // Following methods are similar to IOUtils, - // avoiding lucene dependency in CLI. - private static void addSuppressed(Throwable t, Throwable suppressed) { - if (t != null && suppressed != null) { - t.addSuppressed(suppressed); - } - } - - private static Error reThrowAlways(Throwable th) throws IOException, RuntimeException { - if (th instanceof IOException) { - throw (IOException) th; - } - - if (th instanceof RuntimeException) { - throw (RuntimeException) th; - } - - if (th instanceof Error) { - throw (Error) th; - } - - throw new RuntimeException(th); - } } From 050a9f102cb6097176c4fd912a95d2dec171fbc7 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 15 Mar 2018 07:33:28 +1100 Subject: [PATCH 4/8] CLI: MultiCommand#close address review comments Closes #28953 --- .../elasticsearch/cli/MultiCommandTests.java | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index dc4373e5ffa47..45cac1b12b591 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -21,31 +21,47 @@ import joptsimple.OptionSet; import org.junit.Before; -import org.mockito.Mockito; import java.io.IOException; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import java.util.concurrent.atomic.AtomicBoolean; public class MultiCommandTests extends CommandTestCase { static class DummyMultiCommand extends MultiCommand { + + AtomicBoolean closed = new AtomicBoolean(); DummyMultiCommand() { super("A dummy multi command", () -> {}); } + @Override + public void close() throws IOException { + super.close(); + this.closed.compareAndSet(false, true); + } } static class DummySubCommand extends Command { + AtomicBoolean throwsExceptionOnClose = new AtomicBoolean(); + AtomicBoolean closed = new AtomicBoolean(); DummySubCommand() { super("A dummy subcommand", () -> {}); } + DummySubCommand(boolean throwsExceptionOnClose) { + super("A dummy subcommand", () -> {}); + this.throwsExceptionOnClose.compareAndSet(false, throwsExceptionOnClose); + } @Override protected void execute(Terminal terminal, OptionSet options) throws Exception { terminal.println("Arguments: " + options.nonOptionArguments().toString()); } + @Override + public void close() throws IOException { + if (throwsExceptionOnClose.get()) { + throw new IOException(); + } else { + closed.compareAndSet(false, true); + } + } } DummyMultiCommand multiCommand; @@ -112,30 +128,25 @@ public void testSubcommandArguments() throws Exception { } public void testClose() throws Exception { - Command spySubCommand1 = spy(new DummySubCommand()); - Command spySubCommand2 = spy(new DummySubCommand()); - multiCommand.subcommands.put("command1", spySubCommand1); - multiCommand.subcommands.put("command2", spySubCommand2); + DummySubCommand subCommand1 = new DummySubCommand(); + DummySubCommand subCommand2 = new DummySubCommand(); + multiCommand.subcommands.put("command1", subCommand1); + multiCommand.subcommands.put("command2", subCommand2); multiCommand.close(); - verify(spySubCommand1, times(1)).close(); - verify(spySubCommand2, times(1)).close(); + assertTrue("MultiCommand must have been closed when close method is invoked", multiCommand.closed.get()); + assertTrue("SubCommand1 must have been closed when close method is invoked", subCommand1.closed.get()); + assertTrue("SubCommand2 must have been closed when close method is invoked", subCommand2.closed.get()); } - public void testCloseWhenSubCommandCloseThrowsException() throws Exception { - Command subCommand1 = mock(DummySubCommand.class); - Mockito.doThrow(new IOException()).when(subCommand1).close(); - Command spySubCommand2 = spy(new DummySubCommand()); + public void testCloseWhenSubCommandCloseThrowsException() { + boolean throwExceptionWhenClosed = true; + DummySubCommand subCommand1 = new DummySubCommand(throwExceptionWhenClosed); + DummySubCommand subCommand2 = new DummySubCommand(); multiCommand.subcommands.put("command1", subCommand1); - multiCommand.subcommands.put("command2", spySubCommand2); - IOException ioe = null; - try { - multiCommand.close(); - } catch (IOException e) { - ioe = e; - } + multiCommand.subcommands.put("command2", subCommand2); // verify exception is thrown, as well as other non failed sub-commands closed // properly. - assertNotNull("Expected IOException", ioe); - verify(spySubCommand2, times(1)).close(); + expectThrows(IOException.class, () -> multiCommand.close()); + assertTrue("SubCommand2 must have been closed when close method is invoked", subCommand2.closed.get()); } } From 4b42afd2320b197534122a5b62c4c379b1df7a5d Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 15 Mar 2018 14:54:57 +1100 Subject: [PATCH 5/8] CLI: MultiCommand#close address review comments Closes #28953 --- .../elasticsearch/cli/MultiCommandTests.java | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index 45cac1b12b591..29c44f2933281 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -29,37 +29,49 @@ public class MultiCommandTests extends CommandTestCase { static class DummyMultiCommand extends MultiCommand { - AtomicBoolean closed = new AtomicBoolean(); + final AtomicBoolean closed = new AtomicBoolean(); + DummyMultiCommand() { - super("A dummy multi command", () -> {}); + super("A dummy multi command", () -> { + }); } + @Override public void close() throws IOException { super.close(); - this.closed.compareAndSet(false, true); + if (!this.closed.compareAndSet(false, true)) { + throw new IOException("DummyMultiCommand closed"); + } } } static class DummySubCommand extends Command { - AtomicBoolean throwsExceptionOnClose = new AtomicBoolean(); - AtomicBoolean closed = new AtomicBoolean(); + final boolean throwsExceptionOnClose; + final AtomicBoolean closed = new AtomicBoolean(); + DummySubCommand() { - super("A dummy subcommand", () -> {}); + this(false); } + DummySubCommand(boolean throwsExceptionOnClose) { - super("A dummy subcommand", () -> {}); - this.throwsExceptionOnClose.compareAndSet(false, throwsExceptionOnClose); + super("A dummy subcommand", () -> { + }); + this.throwsExceptionOnClose = throwsExceptionOnClose; } + @Override protected void execute(Terminal terminal, OptionSet options) throws Exception { terminal.println("Arguments: " + options.nonOptionArguments().toString()); } + @Override public void close() throws IOException { - if (throwsExceptionOnClose.get()) { + if (throwsExceptionOnClose) { throw new IOException(); } else { - closed.compareAndSet(false, true); + if (!this.closed.compareAndSet(false, true)) { + throw new IOException("DummySubCommand closed"); + } } } } @@ -133,20 +145,27 @@ public void testClose() throws Exception { multiCommand.subcommands.put("command1", subCommand1); multiCommand.subcommands.put("command2", subCommand2); multiCommand.close(); - assertTrue("MultiCommand must have been closed when close method is invoked", multiCommand.closed.get()); - assertTrue("SubCommand1 must have been closed when close method is invoked", subCommand1.closed.get()); - assertTrue("SubCommand2 must have been closed when close method is invoked", subCommand2.closed.get()); + assertTrue("MultiCommand was not closed when close method is invoked", multiCommand.closed.get()); + assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closed.get()); + assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closed.get()); } public void testCloseWhenSubCommandCloseThrowsException() { - boolean throwExceptionWhenClosed = true; - DummySubCommand subCommand1 = new DummySubCommand(throwExceptionWhenClosed); - DummySubCommand subCommand2 = new DummySubCommand(); + boolean command1Throws = randomBoolean(); + boolean command2Throws = randomBoolean(); + System.out.println("c1 "+command1Throws+", c2 "+command2Throws); + DummySubCommand subCommand1 = new DummySubCommand(command1Throws); + DummySubCommand subCommand2 = new DummySubCommand(command2Throws); multiCommand.subcommands.put("command1", subCommand1); multiCommand.subcommands.put("command2", subCommand2); // verify exception is thrown, as well as other non failed sub-commands closed // properly. - expectThrows(IOException.class, () -> multiCommand.close()); - assertTrue("SubCommand2 must have been closed when close method is invoked", subCommand2.closed.get()); + IOException ioe = expectThrows(IOException.class, () -> multiCommand.close()); + if (command1Throws && command2Throws) { + assertEquals(1, ioe.getSuppressed().length); + assertTrue("Missing suppressed exceptions", ioe.getSuppressed()[0] instanceof IOException); + } + assertTrue("SubCommand1 was not closed when close method is invoked", command1Throws ^ subCommand1.closed.get()); + assertTrue("SubCommand2 was not closed when close method is invoked", command2Throws ^ subCommand2.closed.get()); } } From 6eb66e7886992fd2460b140329cddedbc577b64b Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 15 Mar 2018 15:29:20 +1100 Subject: [PATCH 6/8] CLI: MultiCommand#close address review comments Closes #28953 --- .../java/org/elasticsearch/cli/MultiCommandTests.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index 29c44f2933281..1d2299b517180 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -53,7 +53,7 @@ static class DummySubCommand extends Command { this(false); } - DummySubCommand(boolean throwsExceptionOnClose) { + DummySubCommand(final boolean throwsExceptionOnClose) { super("A dummy subcommand", () -> { }); this.throwsExceptionOnClose = throwsExceptionOnClose; @@ -151,11 +151,10 @@ public void testClose() throws Exception { } public void testCloseWhenSubCommandCloseThrowsException() { - boolean command1Throws = randomBoolean(); - boolean command2Throws = randomBoolean(); - System.out.println("c1 "+command1Throws+", c2 "+command2Throws); - DummySubCommand subCommand1 = new DummySubCommand(command1Throws); - DummySubCommand subCommand2 = new DummySubCommand(command2Throws); + final boolean command1Throws = randomBoolean(); + final boolean command2Throws = randomBoolean(); + final DummySubCommand subCommand1 = new DummySubCommand(command1Throws); + final DummySubCommand subCommand2 = new DummySubCommand(command2Throws); multiCommand.subcommands.put("command1", subCommand1); multiCommand.subcommands.put("command2", subCommand2); // verify exception is thrown, as well as other non failed sub-commands closed From 91d785eb21c8a7a339057b098bdd7e3e6a1b5c58 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 15 Mar 2018 18:08:13 +1100 Subject: [PATCH 7/8] CLI: MultiCommand#close address review comments Closes #28953 --- .../elasticsearch/cli/MultiCommandTests.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index 1d2299b517180..6b54481179d35 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -39,15 +39,15 @@ static class DummyMultiCommand extends MultiCommand { @Override public void close() throws IOException { super.close(); - if (!this.closed.compareAndSet(false, true)) { - throw new IOException("DummyMultiCommand closed"); + if (this.closed.compareAndSet(false, true) == false) { + throw new IOException("DummyMultiCommand already closed"); } } } static class DummySubCommand extends Command { final boolean throwsExceptionOnClose; - final AtomicBoolean closed = new AtomicBoolean(); + final AtomicBoolean closeCalled = new AtomicBoolean(); DummySubCommand() { this(false); @@ -66,12 +66,11 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { @Override public void close() throws IOException { + if (this.closeCalled.compareAndSet(false, true) == false) { + throw new IOException("DummySubCommand already closed"); + } if (throwsExceptionOnClose) { - throw new IOException(); - } else { - if (!this.closed.compareAndSet(false, true)) { - throw new IOException("DummySubCommand closed"); - } + throw new IOException("Error occurred while closing DummySubCommand"); } } } @@ -146,25 +145,31 @@ public void testClose() throws Exception { multiCommand.subcommands.put("command2", subCommand2); multiCommand.close(); assertTrue("MultiCommand was not closed when close method is invoked", multiCommand.closed.get()); - assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closed.get()); - assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closed.get()); + assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closeCalled.get()); + assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closeCalled.get()); } - public void testCloseWhenSubCommandCloseThrowsException() { + public void testCloseWhenSubCommandCloseThrowsException() throws Exception { final boolean command1Throws = randomBoolean(); final boolean command2Throws = randomBoolean(); final DummySubCommand subCommand1 = new DummySubCommand(command1Throws); final DummySubCommand subCommand2 = new DummySubCommand(command2Throws); multiCommand.subcommands.put("command1", subCommand1); multiCommand.subcommands.put("command2", subCommand2); - // verify exception is thrown, as well as other non failed sub-commands closed - // properly. - IOException ioe = expectThrows(IOException.class, () -> multiCommand.close()); - if (command1Throws && command2Throws) { - assertEquals(1, ioe.getSuppressed().length); - assertTrue("Missing suppressed exceptions", ioe.getSuppressed()[0] instanceof IOException); + if (command1Throws || command2Throws) { + // verify exception is thrown, as well as other non failed sub-commands closed + // properly. + IOException ioe = expectThrows(IOException.class, multiCommand::close); + assertEquals("Error occurred while closing DummySubCommand", ioe.getMessage()); + if (command1Throws && command2Throws) { + assertEquals(1, ioe.getSuppressed().length); + assertTrue("Missing suppressed exceptions", ioe.getSuppressed()[0] instanceof IOException); + assertEquals("Error occurred while closing DummySubCommand", ioe.getSuppressed()[0].getMessage()); + } + } else { + multiCommand.close(); } - assertTrue("SubCommand1 was not closed when close method is invoked", command1Throws ^ subCommand1.closed.get()); - assertTrue("SubCommand2 was not closed when close method is invoked", command2Throws ^ subCommand2.closed.get()); + assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closeCalled.get()); + assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closeCalled.get()); } } From 862090e52017fdb9b34e3ceed421bd7fef21f517 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 16 Mar 2018 07:39:37 +1100 Subject: [PATCH 8/8] CLI: MultiCommand#close address review comments Closes #28953 --- .../test/java/org/elasticsearch/cli/MultiCommandTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java index 6b54481179d35..41fe851ed2561 100644 --- a/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java @@ -40,7 +40,7 @@ static class DummyMultiCommand extends MultiCommand { public void close() throws IOException { super.close(); if (this.closed.compareAndSet(false, true) == false) { - throw new IOException("DummyMultiCommand already closed"); + throw new IllegalStateException("DummyMultiCommand already closed"); } } } @@ -67,7 +67,7 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { @Override public void close() throws IOException { if (this.closeCalled.compareAndSet(false, true) == false) { - throw new IOException("DummySubCommand already closed"); + throw new IllegalStateException("DummySubCommand already closed"); } if (throwsExceptionOnClose) { throw new IOException("Error occurred while closing DummySubCommand"); @@ -172,4 +172,5 @@ public void testCloseWhenSubCommandCloseThrowsException() throws Exception { assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closeCalled.get()); assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closeCalled.get()); } + }