Initial unit tests for the docker local binding.
diff --git a/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java b/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
index 5413008..bf48d57 100644
--- a/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
+++ b/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
@@ -19,6 +19,7 @@
 package org.apache.aries.containers;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -128,6 +129,72 @@
         return serviceName;
     }
 
+
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(commandLine);
+        result = prime * result + ((containerImage == null) ? 0 : containerImage.hashCode());
+        result = prime * result + ((containerPorts == null) ? 0 : containerPorts.hashCode());
+        result = prime * result + ((entryPoint == null) ? 0 : entryPoint.hashCode());
+        result = prime * result + ((envVars == null) ? 0 : envVars.hashCode());
+        long temp;
+        temp = Double.doubleToLongBits(requestedCPUunits);
+        result = prime * result + (int) (temp ^ (temp >>> 32));
+        result = prime * result + requestedInstances;
+        temp = Double.doubleToLongBits(requestedMemory);
+        result = prime * result + (int) (temp ^ (temp >>> 32));
+        result = prime * result + ((serviceName == null) ? 0 : serviceName.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ServiceConfig other = (ServiceConfig) obj;
+        if (!Arrays.equals(commandLine, other.commandLine))
+            return false;
+        if (containerImage == null) {
+            if (other.containerImage != null)
+                return false;
+        } else if (!containerImage.equals(other.containerImage))
+            return false;
+        if (containerPorts == null) {
+            if (other.containerPorts != null)
+                return false;
+        } else if (!containerPorts.equals(other.containerPorts))
+            return false;
+        if (entryPoint == null) {
+            if (other.entryPoint != null)
+                return false;
+        } else if (!entryPoint.equals(other.entryPoint))
+            return false;
+        if (envVars == null) {
+            if (other.envVars != null)
+                return false;
+        } else if (!envVars.equals(other.envVars))
+            return false;
+        if (Double.doubleToLongBits(requestedCPUunits) != Double.doubleToLongBits(other.requestedCPUunits))
+            return false;
+        if (requestedInstances != other.requestedInstances)
+            return false;
+        if (Double.doubleToLongBits(requestedMemory) != Double.doubleToLongBits(other.requestedMemory))
+            return false;
+        if (serviceName == null) {
+            if (other.serviceName != null)
+                return false;
+        } else if (!serviceName.equals(other.serviceName))
+            return false;
+        return true;
+    }
+
     /**
      * Obtain a service configuration builder.
      * @param serviceName The name for the service. This name should be unique in the
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java
index b4f4bb3..9bf3353 100644
--- a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java
@@ -111,7 +111,6 @@
 
     @Override
     public String toString() {
-        return "ContainerImpl [id=" + id + ", ip=" + ip + ", ports=" + ports +
-                ", service=" + service.getConfiguration().getServiceName() + "]";
+        return "ContainerImpl [id=" + id + ", ip=" + ip + ", ports=" + ports + "]";
     }
 }
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManager.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManager.java
index c64e99a..feced50 100644
--- a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManager.java
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManager.java
@@ -46,7 +46,7 @@
 
 public class LocalDockerServiceManager implements ServiceManager {
     static final Logger LOG = LoggerFactory.getLogger(LocalDockerServiceManager.class);
-    private static final String SERVICE_NAME = "org.apache.aries.containers.service.name";
+    static final String SERVICE_NAME_LABEL = "org.apache.aries.containers.service.name";
 
     private static final String DOCKER_MACHINE_VM_NAME = System.getenv("DOCKER_MACHINE_NAME");
     private static final boolean CHECK_DOCKER_MACHINE = Stream
@@ -55,13 +55,13 @@
             .anyMatch(path -> Files.exists(path.resolve("docker-machine")));
 
     private static final boolean USE_DOCKER_MACHINE = (DOCKER_MACHINE_VM_NAME != null) && CHECK_DOCKER_MACHINE;
-    private static final String CONTAINER_HOST = USE_DOCKER_MACHINE
+    static final String CONTAINER_HOST = USE_DOCKER_MACHINE
             ? ProcessRunner.waitFor(ProcessRunner.run("docker-machine", "ip", DOCKER_MACHINE_VM_NAME))
             : "localhost";
 
 
     private final LocalDockerController docker;
-    private final ConcurrentMap<String, Service> services =
+    final ConcurrentMap<String, Service> services =
             new ConcurrentHashMap<>();
 
     public LocalDockerServiceManager() {
@@ -73,7 +73,7 @@
     }
 
     List<String> getDockerIDs(ServiceConfig config) {
-        return docker.ps(SERVICE_NAME + "=" + config.getServiceName());
+        return docker.ps(SERVICE_NAME_LABEL + "=" + config.getServiceName());
     }
 
     @Override
@@ -110,7 +110,7 @@
         List<String> command = new ArrayList<>();
         command.add("-d");
         command.add("-l");
-        command.add(SERVICE_NAME + "=" + config.getServiceName());
+        command.add(SERVICE_NAME_LABEL + "=" + config.getServiceName());
 
         String ep = config.getEntryPoint();
         if (ep != null) {
@@ -217,7 +217,7 @@
     @SuppressWarnings("rawtypes")
     public Set<String> listServices() throws Exception {
         Set<String> res = new HashSet<>();
-        List<String> ids = docker.ps(SERVICE_NAME);
+        List<String> ids = docker.ps(SERVICE_NAME_LABEL);
 
         for (Service svc : services.values()) {
             res.add(svc.getConfiguration().getServiceName());
@@ -237,7 +237,7 @@
             if (cd instanceof Map) {
                 Object ld = ((Map) cd).get("Labels");
                 if (ld instanceof Map) {
-                    Object serviceName = ((Map) ld).get(SERVICE_NAME);
+                    Object serviceName = ((Map) ld).get(SERVICE_NAME_LABEL);
                     if (serviceName instanceof String) {
                         res.add((String) serviceName);
                     }
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java
index 94aba89..bf0a229 100644
--- a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java
@@ -47,7 +47,6 @@
     @Override
     public int getActualInstanceCount() {
         return factory.getDockerIDs(config).size();
-        // TODO test
     }
 
     @Override
@@ -94,4 +93,35 @@
             containers.add(c);
         }
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((config == null) ? 0 : config.hashCode());
+        result = prime * result + ((containers == null) ? 0 : containers.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ServiceImpl other = (ServiceImpl) obj;
+        if (config == null) {
+            if (other.config != null)
+                return false;
+        } else if (!config.equals(other.config))
+            return false;
+        if (containers == null) {
+            if (other.containers != null)
+                return false;
+        } else if (!containers.equals(other.containers))
+            return false;
+        return true;
+    }
 }
diff --git a/containers-docker-local/src/test/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManagerTest.java b/containers-docker-local/src/test/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManagerTest.java
new file mode 100644
index 0000000..e5c3ac7
--- /dev/null
+++ b/containers-docker-local/src/test/java/org/apache/aries/containers/docker/local/impl/LocalDockerServiceManagerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.aries.containers.docker.local.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.aries.containers.Container;
+import org.apache.aries.containers.Service;
+import org.apache.aries.containers.ServiceConfig;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+
+public class LocalDockerServiceManagerTest {
+    private String INSPECT_JSON1 = "[{\"Config\": {\"Labels\": {\""
+            + LocalDockerServiceManager.SERVICE_NAME_LABEL + "\": \"svc1\"}}},"
+            + "{\"Config\": {\"Labels\": {\""
+            + LocalDockerServiceManager.SERVICE_NAME_LABEL + "\": \"svc2\"}}}]";
+    private String INSPECT_JSON2 =
+            "[{\"Id\": \"c2\","
+            + "\"NetworkSettings\": {\"Ports\": {\"80/tcp\": [{\"HostPort\": 14524}]}}},"
+            + "{\"Id\": \"c1\","
+            + "\"NetworkSettings\": {\"Ports\": {\"8080/tcp\": [{\"HostPort\": 63758}],"
+            + "\"90/udp\": [{\"HostPort\": 32768}]}}}]";
+
+
+    @Test
+    public void testGetServiceExisting() throws Exception {
+        LocalDockerServiceManager sm = new LocalDockerServiceManager();
+        ServiceConfig gooCfg = ServiceConfig.builder("goo", "myimg").build();
+        ServiceImpl gooSvc = new ServiceImpl(gooCfg, sm, Collections.emptyList());
+        sm.services.put("goo", gooSvc);
+        ServiceConfig baaCfg = ServiceConfig.builder("baa", "myimg").build();
+        ServiceImpl baaSvc = new ServiceImpl(baaCfg, sm, Arrays.asList(
+                new ContainerImpl("aabbcc", "myhost", Collections.singletonMap(80, 12345)),
+                new ContainerImpl("ddeeff", "myhost", Collections.emptyMap())));
+        sm.services.put("baa", baaSvc);
+
+        assertEquals(gooSvc, sm.getService(gooCfg));
+        assertEquals(baaSvc, sm.getService(baaCfg));
+    }
+
+    @Test
+    public void testGetServiceDiscover() throws Exception {
+        List<String> ids = Arrays.asList("c1", "c2");
+
+        LocalDockerController dc = Mockito.mock(LocalDockerController.class);
+        Mockito.when(dc.ps(LocalDockerServiceManager.SERVICE_NAME_LABEL + "=lalala")).
+            thenReturn(ids);
+        Mockito.when(dc.inspect(ids)).thenReturn(INSPECT_JSON2);
+
+        LocalDockerServiceManager sm = new LocalDockerServiceManager(dc);
+
+        ServiceConfig lalaCfg = ServiceConfig.builder("lalala", "myimg").build();
+        Service lalaSvc = sm.getService(lalaCfg);
+        assertEquals(lalaCfg, lalaSvc.getConfiguration());
+
+        Map<Integer, Integer> ports = new HashMap<>();
+        ports.put(90, 32768);
+        ports.put(8080, 63758);
+        Set<Container> expectedContainers = new HashSet<>(Arrays.asList(
+                new ContainerImpl("c1", LocalDockerServiceManager.CONTAINER_HOST, ports),
+                new ContainerImpl("c2", LocalDockerServiceManager.CONTAINER_HOST,
+                        Collections.singletonMap(80, 14524))));
+        assertEquals(expectedContainers, new HashSet<>(lalaSvc.listContainers()));
+    }
+
+    @Test
+    public void testListServices() throws Exception {
+        LocalDockerController dc = Mockito.mock(LocalDockerController.class);
+        Mockito.when(dc.ps(LocalDockerServiceManager.SERVICE_NAME_LABEL)).
+            thenReturn(new ArrayList<>(Arrays.asList("a1", "b2", "c3", "d4")));
+        Mockito.when(dc.inspect(Arrays.asList("a1", "b2"))).thenReturn(INSPECT_JSON1);
+
+        LocalDockerServiceManager sm = new LocalDockerServiceManager(dc);
+        ServiceConfig config = ServiceConfig.builder("svc3", "myimg").build();
+        sm.services.putIfAbsent("c3", new ServiceImpl(config, sm, Arrays.asList(
+                new ContainerImpl("c3", "localhost", Collections.emptyMap()),
+                new ContainerImpl("d4", "localhost", Collections.emptyMap()))));
+
+        assertEquals(new HashSet<>(Arrays.asList("svc1", "svc2", "svc3")), sm.listServices());
+    }
+}