You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2017/12/15 11:49:12 UTC

[sling-org-apache-sling-testing-clients] branch feature/wait-for-component updated (42bf1f5 -> a9aa186)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a change to branch feature/wait-for-component
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git.


    omit 42bf1f5  SLING-7297 add methods to wait for a component to be registered and a bundle to be started
     new a9aa186  SLING-7297 add methods to wait for a component and a service

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (42bf1f5)
            \
             N -- N -- N   refs/heads/feature/wait-for-component (a9aa186)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   6 +
 .../testing/clients/osgi/OsgiConsoleClient.java    |  61 +++++++++
 .../osgi/{ComponentInfo.java => ServiceInfo.java}  |  44 ++++---
 .../sling/testing/clients/osgi/ServicesInfo.java   | 142 +++++++++++++++++++++
 .../sling/testing/clients/osgi/package-info.java   |   2 +-
 .../testing/clients/osgi/ServicesInfoTest.java     |  34 +++++
 6 files changed, 268 insertions(+), 21 deletions(-)
 copy src/main/java/org/apache/sling/testing/clients/osgi/{ComponentInfo.java => ServiceInfo.java} (54%)
 create mode 100644 src/main/java/org/apache/sling/testing/clients/osgi/ServicesInfo.java
 create mode 100644 src/test/java/org/apache/sling/testing/clients/osgi/ServicesInfoTest.java

-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-testing-clients] 01/01: SLING-7297 add methods to wait for a component and a service

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/wait-for-component
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit a9aa186c5242c6c38b6b92f56d586aea62a273bb
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sun Dec 10 12:22:15 2017 +0100

    SLING-7297 add methods to wait for a component and a service
---
 pom.xml                                            |   6 +
 .../testing/clients/osgi/OsgiConsoleClient.java    | 153 ++++++++++++++++++++-
 .../sling/testing/clients/osgi/ServiceInfo.java    |  73 ++++++++++
 .../sling/testing/clients/osgi/ServicesInfo.java   | 142 +++++++++++++++++++
 .../sling/testing/clients/osgi/package-info.java   |   2 +-
 .../sling/testing/clients/util/poller/Polling.java |   2 +-
 .../testing/clients/osgi/ServicesInfoTest.java     |  34 +++++
 7 files changed, 403 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index ee4d472..17c4562 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,12 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.hapi.client</artifactId>
             <version>1.0.0</version>
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 55bdb25..1e8fac4 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -39,6 +39,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -77,6 +78,11 @@ public class OsgiConsoleClient extends SlingClient {
      * The URL for components requests
      */
     private final String URL_COMPONENTS = CONSOLE_ROOT_URL + "/components";
+    
+    /**
+     * The URL for service requests
+     */
+    private final String URL_SERVICES = CONSOLE_ROOT_URL + "/services";
 
 
     public static final String JSON_KEY_ID = "id";
@@ -162,6 +168,105 @@ public class OsgiConsoleClient extends SlingClient {
         return new ComponentInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
     }
 
+    /**
+     * Returns the wrapper for the component info json
+     *
+     * @param id the id of the component
+     * @return the component info or {@code null} if the component with that name is not found
+     */
+    private ComponentInfo getComponentInfo(String name) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_COMPONENTS + "/" + name + ".json");
+        if (HttpUtils.getHttpStatus(resp) == SC_OK) {
+            return new ComponentInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+        }
+        return null;
+    }
+    
+    /**
+     * Returns the service info wrapper for all services implementing the given type.
+     *
+     * @param name the type of the service
+     * @return the service infos or {@code null} if no service for the given type is registered
+     */
+    private Collection<ServiceInfo> getServiceInfos(String type) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_SERVICES + ".json");
+        if (HttpUtils.getHttpStatus(resp) == SC_OK) {
+            return new ServicesInfo(JsonUtils.getJsonNodeFromString(resp.getContent())).forType(type);
+        }
+        return null;
+    }
+
+    /**
+     * Wait until the component with the given name is registered. This means the component must be either in state "Registered" or "Active".
+     * @param componentName the component's name
+     * @param timeout how long to wait for the component to become registered before throwing a {@code TimeoutException} in milliseconds
+     * @param delay time to wait between checks of the state in milliseconds
+     * @throws TimeoutException if the component did not become registered before timeout was reached
+     * @throws InterruptedException if interrupted
+     * @see "OSGi Comp. R6, §112.5 Component Life Cycle"
+     */
+    public void waitComponentRegistered(final String componentName, final long timeout, final long delay) throws TimeoutException, InterruptedException {
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                ComponentInfo info = getComponentInfo(componentName);
+                if (info != null) {
+                    return ((info.getStatus() == Component.Status.REGISTERED) || (info.getStatus() == Component.Status.ACTIVE));
+                } else {
+                    LOG.debug("Could not get component info for component name {}", componentName);
+                }
+                return false;
+            }
+
+            @Override
+            protected String message() {
+                return "Component " + componentName + " was not registered in %1$d ms";
+            }
+        };
+        p.poll(timeout, delay);
+    }
+    
+    /**
+     * Wait until the service with the given name is registered. This means the component must be either in state "Registered" or "Active".
+     * @param type the type of the service (usually the name of a Java interface)
+     * @param bundleSymbolicName the symbolic name of the bundle supposed to register that service. 
+     * May be {@code null} in which case this method just waits for any service with the requested type being registered (independent of the registering bundle).
+     * @param timeout how long to wait for the component to become registered before throwing a {@code TimeoutException} in milliseconds
+     * @param delay time to wait between checks of the state in milliseconds
+     * @throws TimeoutException if the component did not become registered before timeout was reached
+     * @throws InterruptedException if interrupted
+     */
+    public void waitServiceRegistered(final String type, final String bundleSymbolicName , final long timeout, final long delay) throws TimeoutException, InterruptedException {
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                Collection<ServiceInfo> infos = getServiceInfos(type);
+                if (infos != null) {
+                    if (bundleSymbolicName != null) {
+                        for (ServiceInfo info : infos) {
+                            if (bundleSymbolicName.equals(info.getBundleSymbolicName())) {
+                                return true;
+                            }
+                        }
+                        LOG.debug("Could not find service info for service type {} provided by bundle {}", type, bundleSymbolicName);
+                        return false;
+                    } else {
+                        return !infos.isEmpty();
+                    }
+                } else {
+                    LOG.debug("Could not find any service info for service type {}", type);
+                }
+                return false;
+            }
+
+            @Override
+            protected String message() {
+                return "Service with type " + type + " was not registered in %1$d ms";
+            }
+        };
+        p.poll(timeout, delay);
+    }
+
     //
     // OSGi configurations
     //
@@ -462,12 +567,12 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed.
      * @param f the bundle file
      * @param startBundle whether to start the bundle or not
      * @param startLevel the start level of the bundle. negative values mean default start level
-     * @param timeout how much to wait for the bundle to be installed before throwing a {@code TimeoutException}
-     * @param delay time to wait between checks of the state
+     * @param timeout how long to wait for the bundle to be installed before throwing a {@code TimeoutException} in milliseconds
+     * @param delay time to wait between checks of the state in milliseconds
      * @throws ClientException if the request failed
      * @throws TimeoutException if the bundle did not install before timeout was reached
      * @throws InterruptedException if interrupted
@@ -484,12 +589,13 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Wait until the bundle is installed
+     * Wait until the bundle is installed.
      * @param symbolicName symbolic name of bundle
-     * @param timeout how much to wait for the bundle to be installed before throwing a {@code TimeoutException}
-     * @param delay time to wait between checks of the state
+     * @param timeout how long to wait for the bundle to be installed before throwing a {@code TimeoutException} in milliseconds
+     * @param delay time to wait between checks of the state in milliseconds
      * @throws TimeoutException if the bundle did not install before timeout was reached
      * @throws InterruptedException if interrupted
+     * @see "OSGi Core R6, §4.4.2 Bundle State"
      */
     public void waitBundleInstalled(final String symbolicName, final long timeout, final long delay)
             throws TimeoutException, InterruptedException {
@@ -503,7 +609,40 @@ public class OsgiConsoleClient extends SlingClient {
 
             @Override
             protected String message() {
-                return "Bundle " + symbolicName + " did not install in %1$ ms";
+                return "Bundle " + symbolicName + " did not install in %1$d ms";
+            }
+        };
+
+        p.poll(timeout, delay);
+    }
+    
+    /**
+     * Wait until the bundle is started
+     * @param symbolicName symbolic name of bundle
+     * @param timeout how long to wait for the bundle to be installed before throwing a {@code TimeoutException} in milliseconds.
+     * @param delay time to wait between checks of the state in milliseconds.
+     * @throws TimeoutException if the bundle did not install before timeout was reached
+     * @throws InterruptedException if interrupted
+     * @see "OSGi Core R6, §4.4.2 Bundle State"
+     */
+    public void waitBundleStarted(final String symbolicName, final long timeout, final long delay)
+            throws TimeoutException, InterruptedException {
+
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                try {
+                    BundleInfo bundleInfo = getBundleInfo(symbolicName, 200);
+                    return (bundleInfo.getStatus() == Bundle.Status.ACTIVE);
+                } catch (ClientException e) {
+                    LOG.debug("Could not get bundle state for {}: {}", symbolicName, e.getLocalizedMessage(), e);
+                    return false;
+                }
+            }
+
+            @Override
+            protected String message() {
+                return "Bundle " + symbolicName + " did not start in %1$d ms";
             }
         };
 
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/ServiceInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/ServiceInfo.java
new file mode 100644
index 0000000..8df6463
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/ServiceInfo.java
@@ -0,0 +1,73 @@
+/*
+ * 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.sling.testing.clients.osgi;
+
+import java.util.List;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+public class ServiceInfo {
+
+    private JsonNode service;
+
+    public ServiceInfo(JsonNode root) throws ClientException {
+        if(root.get("id") != null) {
+            service = root;
+        } else {
+            if(root.get("data") == null && root.get("data").size() < 1) {
+                throw new ClientException("No service info returned");
+            }
+            service = root.get("data").get(0);
+        }
+    }
+
+    /**
+     * @return the service identifier
+     */
+    public int getId() {
+        return service.get("id").getIntValue();
+    }
+
+    /**
+     * @return the service types name
+     */
+    public List<String> getTypes() {
+        // this is not a proper JSON array (https://issues.apache.org/jira/browse/FELIX-5762)
+        return ServicesInfo.splitPseudoJsonValueArray(service.get("types").getTextValue());
+    }
+
+    public String getPid() {
+        return service.get("pid").getTextValue();
+    }
+
+    /**
+     * @return the bundle id of the bundle exposing the service
+     */
+    public int getBundleId() {
+        return service.get("bundleId").getIntValue();
+    }
+
+    /**
+     * @return the bundle symbolic name of bundle implementing the service
+     */
+    public String getBundleSymbolicName() {
+        return service.get("bundleSymbolicName").getTextValue();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/ServicesInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/ServicesInfo.java
new file mode 100644
index 0000000..8ec3bbd
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/ServicesInfo.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sling.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A simple Wrapper around the returned JSON when requesting the status of /system/console/services
+ */
+public class ServicesInfo {
+
+    private JsonNode root = null;
+
+    /**
+     * The only constructor.
+     * 
+     * @param root the root JSON node of the bundles info.
+     * @throws ClientException if the json does not contain the proper info
+     */
+    public ServicesInfo(JsonNode root) throws ClientException {
+        this.root = root;
+        // some simple sanity checks
+        if(root.get("status") == null)
+            throw new ClientException("No Status returned!");
+        if(root.get("serviceCount") == null)
+            throw new ClientException("No serviceCount returned!");
+    }
+
+    /**
+     * @return total number of bundles.
+     */
+    public int getTotalNumOfServices() {
+        return root.get("serviceCount").getIntValue();
+    }
+
+    /**
+     * Return service info for a service with given id
+     *
+     * @param id the id of the service
+     * @return the BundleInfo
+     * @throws ClientException if the info could not be retrieved
+     */
+    public ServiceInfo forId(String id) throws ClientException {
+        JsonNode serviceInfo = findBy("id", id);
+        return (serviceInfo != null) ? new ServiceInfo(serviceInfo) : null;
+    }
+
+    /**
+     * Return service infos for a bundle with name {@code name}
+     *
+     * @param type the type of the service
+     * @return a Collection of {@link ServiceInfo}s of all services with the given type. Might be empty, never {@code null}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public Collection<ServiceInfo> forType(String type) throws ClientException {
+        List<ServiceInfo> results = new LinkedList<>();
+        List<JsonNode> serviceInfoNodes = findAllContainingValueInArray("types", type);
+        for (JsonNode serviceInfoNode : serviceInfoNodes) {
+            results.add(new ServiceInfo(serviceInfoNode));
+        }
+        return results;
+    }
+
+    private JsonNode findBy(String key, String value) {
+        List<JsonNode> result = findBy(key, value, true, false);
+        if (result.isEmpty()) {
+            return null;
+        } else {
+            return result.get(0);
+        }
+    }
+
+    private List<JsonNode> findAllContainingValueInArray(String key, String value) {
+        return findBy(key, value, false, true);
+    }
+    
+    private List<JsonNode> findBy(String key, String value, boolean onlyReturnFirstMatch, boolean arrayContainingMatch) {
+        Iterator<JsonNode> nodes = root.get("data").getElements();
+        List<JsonNode> results = new LinkedList<>();
+        while(nodes.hasNext()) {
+            JsonNode node = nodes.next();
+            if ((null != node.get(key)) && (node.get(key).isValueNode())) {
+                final String valueNode = node.get(key).getTextValue();
+                if (arrayContainingMatch) {
+                    if (splitPseudoJsonValueArray(valueNode).contains(value)) {
+                        results.add(node);
+                    }
+                } else {
+                    if (valueNode.equals(value)) {
+                        results.add(node);
+                    }
+                }
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Array values are not returned as proper JSON array for Apache Felix.
+     * Therefore we need this dedicated split method, which extracts the individual values from this "pseudo" JSON array.
+     * Example value: 
+     * <pre>
+     * [java.lang.Runnable, org.apache.sling.event.impl.jobs.queues.QueueManager, org.osgi.service.event.EventHandler]
+     * </pre>
+     * @param value the value to split
+     * @return the list of the individual values in the given array.
+     * @see <a href="https://issues.apache.org/jira/browse/FELIX-5762">FELIX-5762</a>
+     */
+    static final List<String> splitPseudoJsonValueArray(String value) {
+        // is this an array?
+        if (value.startsWith("[") && value.length() >= 2) {
+            // strip of first and last character
+           String pureArrayValues = value.substring(1, value.length() - 1);
+           String[] resultArray = pureArrayValues.split(", |,");
+           return Arrays.asList(resultArray);
+        }
+        return Collections.singletonList(value);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 2b92f60..fa38949 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,7 +19,7 @@
 /**
  * OSGI testing tools.
  */
-@Version("1.3.0")
+@Version("1.4.0")
 package org.apache.sling.testing.clients.osgi;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java b/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
index c57018d..064bbcc 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
@@ -121,7 +121,7 @@ public class Polling implements Callable<Boolean> {
     /**
      * Returns the string to be used in the {@code TimeoutException}, if needed.
      * The string is passed to {@code String.format(message(), timeout, delay)}, so it can be a format
-     * including {@code %1$} and {@code %2$}. The field {@code lastException} is also available for logging
+     * including {@code %1$d} and {@code %2$d}. The field {@code lastException} is also available for logging
      *
      * @return the format string
      */
diff --git a/src/test/java/org/apache/sling/testing/clients/osgi/ServicesInfoTest.java b/src/test/java/org/apache/sling/testing/clients/osgi/ServicesInfoTest.java
new file mode 100644
index 0000000..b74bb31
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/osgi/ServicesInfoTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.sling.testing.clients.osgi;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ServicesInfoTest {
+
+    @Test
+    public void testSplitPseudoJsonValueArray() {
+        Assert.assertThat(ServicesInfo.splitPseudoJsonValueArray("test"), Matchers.contains("test"));
+        Assert.assertThat(ServicesInfo.splitPseudoJsonValueArray("[]"), Matchers.contains(""));
+        Assert.assertThat(ServicesInfo.splitPseudoJsonValueArray("[one, two]"), Matchers.contains("one", "two"));
+        Assert.assertThat(ServicesInfo.splitPseudoJsonValueArray("[one,two]"), Matchers.contains("one", "two"));
+        Assert.assertThat(ServicesInfo.splitPseudoJsonValueArray("[java.lang.Runnable, org.apache.sling.event.impl.jobs.queues.QueueManager, org.osgi.service.event.EventHandler]"),
+                Matchers.contains("java.lang.Runnable", "org.apache.sling.event.impl.jobs.queues.QueueManager", "org.osgi.service.event.EventHandler"));
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.