Rework TestSshCommand to re-run its command and assertions within timeout
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/404e4948 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/404e4948 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/404e4948 Branch: refs/heads/master Commit: 404e4948b0b55e34d6f7e0af205ba76266397bef Parents: a3cddf1 Author: Sam Corbett <sam.corb...@cloudsoftcorp.com> Authored: Tue Jun 21 18:46:26 2016 +0100 Committer: Sam Corbett <sam.corb...@cloudsoftcorp.com> Committed: Tue Jun 21 18:46:26 2016 +0100 ---------------------------------------------------------------------- .../test/framework/TestFrameworkAssertions.java | 2 +- .../test/framework/TestSshCommandImpl.java | 86 ++++++++++++++------ .../TestSshCommandIntegrationTest.java | 33 +++++++- 3 files changed, 95 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/404e4948/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java index ba32f40..bc358f8 100644 --- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java @@ -155,7 +155,7 @@ public class TestFrameworkAssertions { } } - private static <T> void checkActualAgainstAssertions(Map<String, Object> assertions, + public static <T> void checkActualAgainstAssertions(Map<String, Object> assertions, String target, T actual) { for (Map.Entry<String, Object> assertion : assertions.entrySet()) { String condition = assertion.getKey().toString(); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/404e4948/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java index 5d1b886..3d2ae02 100644 --- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSshCommandImpl.java @@ -23,7 +23,7 @@ import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.RUNNING; import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STARTING; import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STOPPED; import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState; -import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkAssertions; +import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkActualAgainstAssertions; import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions; import static org.apache.brooklyn.util.text.Strings.isBlank; import static org.apache.brooklyn.util.text.Strings.isNonBlank; @@ -36,6 +36,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.TaskFactory; @@ -43,12 +44,13 @@ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.location.Machines; import org.apache.brooklyn.location.ssh.SshMachineLocation; -import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.ssh.SshTasks; import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; +import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; @@ -57,17 +59,15 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.base.Splitter; -import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -// TODO assertions below should use TestFrameworkAssertions but that class needs to be improved to give better error messages public class TestSshCommandImpl extends TargetableTestComponentImpl implements TestSshCommand { private static final Logger LOG = LoggerFactory.getLogger(TestSshCommandImpl.class); private static final int A_LINE = 80; - public static final String DEFAULT_NAME = "download.sh"; + private static final String DEFAULT_NAME = "download.sh"; private static final String CD = "cd"; @Override @@ -113,36 +113,59 @@ public class TestSshCommandImpl extends TargetableTestComponentImpl implements T } } - protected void handle(Result result) { - LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] { - this, result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr()) - }); - ImmutableMap<String, Duration> flags = ImmutableMap.of("timeout", getConfig(TIMEOUT)); - AssertionSupport support = new AssertionSupport(); - checkAssertions(support, flags, exitCodeAssertions(), "exit code", Suppliers.ofInstance(result.getExitCode())); - checkAssertions(support, flags, getAssertions(this, ASSERT_OUT), "stdout", Suppliers.ofInstance(result.getStdout())); - checkAssertions(support, flags, getAssertions(this, ASSERT_ERR), "stderr", Suppliers.ofInstance(result.getStderr())); - support.validate(); - } - private String shorten(String text) { return Strings.maxlenWithEllipsis(text, A_LINE); } + private static class MarkerException extends Exception { + public MarkerException(Throwable cause) { + super(cause); + } + } + public void execute() { try { - SshMachineLocation machineLocation = - Machines.findUniqueMachineLocation(resolveTarget().getLocations(), SshMachineLocation.class).get(); - executeCommand(machineLocation); - setUpAndRunState(true, RUNNING); + final SshMachineLocation machineLocation = + Machines.findUniqueMachineLocation(resolveTarget().getLocations(), SshMachineLocation.class).get(); + final Duration timeout = config().get(TIMEOUT); + + ReferenceWithError<Boolean> result = Repeater.create("Running ssh-command tests") + .limitTimeTo(timeout) + .every(timeout.multiply(0.1)) + .until(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + try { + Result result = executeCommand(machineLocation); + handle(result); + } catch (AssertionError e) { + // Repeater will only handle Exceptions gracefully. Other Throwables are thrown + // immediately, so the AssertionError thrown by handle causes early exit. + throw new MarkerException(e); + } + return true; + } + }) + .runKeepingError(); + + if (!result.hasError()) { + setUpAndRunState(true, RUNNING); + } else { + setUpAndRunState(false, ON_FIRE); + Throwable error = result.getError(); + if (error instanceof MarkerException) { + error = error.getCause(); + } + throw Exceptions.propagate(error); + } + } catch (Throwable t) { setUpAndRunState(false, ON_FIRE); throw Exceptions.propagate(t); } } - private void executeCommand(SshMachineLocation machineLocation) { - + private Result executeCommand(SshMachineLocation machineLocation) { Result result = null; String downloadUrl = getConfig(DOWNLOAD_URL); String command = getConfig(COMMAND); @@ -167,7 +190,22 @@ public class TestSshCommandImpl extends TargetableTestComponentImpl implements T result = executeShellCommand(machineLocation, command, env); } - handle(result); + return result; + } + + protected void handle(Result result) { + LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] { + this, result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr()) + }); + for (Map<String, Object> assertion : exitCodeAssertions()) { + checkActualAgainstAssertions(assertion, "exit code", result.getExitCode()); + } + for (Map<String, Object> assertion : getAssertions(this, ASSERT_OUT)) { + checkActualAgainstAssertions(assertion, "stdout", result.getStdout()); + } + for (Map<String, Object> assertion : getAssertions(this, ASSERT_ERR)) { + checkActualAgainstAssertions(assertion, "stderr", result.getStderr()); + } } private Result executeDownloadedScript(SshMachineLocation machineLocation, String url, String scriptPath, Map<String, Object> env) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/404e4948/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandIntegrationTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandIntegrationTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandIntegrationTest.java index 60c6aaf..c0930c6 100644 --- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandIntegrationTest.java +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSshCommandIntegrationTest.java @@ -28,6 +28,7 @@ import static org.apache.brooklyn.test.framework.TestSshCommand.ASSERT_STATUS; import static org.apache.brooklyn.test.framework.TestSshCommand.COMMAND; import static org.apache.brooklyn.test.framework.TestSshCommand.DOWNLOAD_URL; import static org.apache.brooklyn.test.framework.TestSshCommand.RUN_DIR; +import static org.apache.brooklyn.test.framework.TestSshCommand.TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; @@ -38,12 +39,15 @@ import java.util.Map; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.time.Duration; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -58,12 +62,39 @@ public class TestSshCommandIntegrationTest extends BrooklynAppUnitTestSupport { @Override public void setUp() throws Exception { super.setUp(); - testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class) .location(TestApplication.LOCALHOST_MACHINE_SPEC)); } @Test(groups = "Integration") + public void shouldRetryCommandWithinTimeout() throws Exception { + TestSshCommand testWithCmd = app.createAndManageChild(EntitySpec.create(TestSshCommand.class) + .configure(TARGET_ENTITY, testEntity) + .configure(COMMAND, "_djaf-_f£39r24") + .configure(RUN_DIR, "/tmp") + .configure(TIMEOUT, Duration.TEN_SECONDS) + .configure(ASSERT_STATUS, makeAssertions(ImmutableMap.of(EQUALS, 0)))); + + // Run the test's assertions in parallel with the app. + Thread t = new Thread() { + @Override + public void run() { + app.start(ImmutableList.<Location>of()); + } + }; + t.start(); + + // Times out in one second. + EntityAsserts.assertAttributeContinuallyNotEqualTo(testWithCmd, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + // Replace command with a valid one and the test should start to pass. + testWithCmd.config().set(COMMAND, "true"); + EntityAsserts.assertAttributeEqualsEventually(testWithCmd, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + t.join(); + } + + @Test(groups = "Integration") public void shouldExecuteInTheRunDir() throws Exception { Path pwdPath = createTempScript("pwd", "pwd");