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");
 

Reply via email to