Merge branch 'merge-test-master'
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..3fbb103
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,13 @@
+*/src/main/resources/META-INF/MANIFEST.MF
+*/src/test/resources/META-INF/TEST.MF
+target
+integration-repo
+ivy-cache
+.ant-targets-build.xml
+user-ivy.properties
+bin/
+build/
+.settings/
+.project
+.classpath
+
diff --git a/test/CONTRIBUTING.md b/test/CONTRIBUTING.md
new file mode 100644
index 0000000..e3a5790
--- /dev/null
+++ b/test/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+Contributing to Virgo
+=====================
+
+Thanks for your interest in this project.
+
+Project description:
+--------------------
+
+The Virgo Web Server from EclipseRT is a completely module-based Java application server that is designed to run enterprise Java applications and Spring-powered applications with a high degree of flexibility and reliability. It offers a simple yet comprehensive platform to develop, deploy, and service enterprise Java applications.
+The Virgo kernel supports the core concepts of Virgo and is not biased towards the web server, thus enabling other types of server to be created. The kernel can also be used stand-alone as a rich OSGi application platform. A server runtime can easily be constructed by deploying suitable bundles on top of the kernel.
+
+- https://projects.eclipse.org/projects/rt.virgo
+
+Developer resources:
+--------------------
+
+Information regarding source code management, builds, coding standards, and more.
+
+- https://projects.eclipse.org/projects/rt.virgo/developer
+
+Contributor License Agreement:
+------------------------------
+
+Before your contribution can be accepted by the project, you need to create and electronically sign the Eclipse Foundation Contributor License Agreement (CLA).
+
+- http://www.eclipse.org/legal/CLA.php
+
+Contact:
+--------
+
+Contact the project developers via the project's "dev" list.
+
+- https://dev.eclipse.org/mailman/listinfo/virgo-dev
+
+Search for bugs:
+----------------
+
+This project uses Bugzilla to track ongoing development and issues.
+
+- https://bugs.eclipse.org/bugs/buglist.cgi?product=Virgo
+
+Create a new bug:
+-----------------
+
+Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome!
+
+- https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Virgo
\ No newline at end of file
diff --git a/test/build.gradle b/test/build.gradle
new file mode 100644
index 0000000..dc96e11
--- /dev/null
+++ b/test/build.gradle
@@ -0,0 +1,34 @@
+project(':test:org.eclipse.virgo.test.stubs') {
+	dependencies {
+		compile group: "org.eclipse.virgo.mirrored", name: "org.eclipse.osgi", version: equinoxVersion, configuration: "compile", ext: "jar"
+		compile group: "org.eclipse.virgo.mirrored", name: "org.eclipse.osgi.services", version: osgiServicesVersion, configuration: "compile", ext: "jar"
+		compile group: "org.eclipse.virgo.mirrored", name: "org.eclipse.equinox.region", version: equinoxRegionVersion, configuration: "compile", ext: "jar"
+	}
+}
+
+project(':test:org.eclipse.virgo.test.tools') {
+	dependencies {
+		compile project(':util:org.eclipse.virgo.util.io')
+		compile group: "org.apache.httpcomponents", name: "httpcore", version: httpcomponentsCoreVersion
+		compile group: "org.apache.httpcomponents", name: "httpclient", version: httpcomponentsClientVersion
+	}
+}
+
+project(':test:org.eclipse.virgo.test.launcher') {
+	dependencies {
+		compile project(':util:org.eclipse.virgo.util.parser.launcher')
+		compile group: "org.eclipse.virgo.mirrored", name: "org.eclipse.osgi", version: equinoxVersion, configuration: "compile", ext: "jar"
+		testCompile group: 'junit', name: 'junit', version: junitVersion, configuration: "compile", ext: "jar"
+	}
+}
+
+project(':test:org.eclipse.virgo.test.framework') {
+	dependencies {
+		compile project(':util:org.eclipse.virgo.util.common')
+        compile project(':test:org.eclipse.virgo.test.stubs')
+        compile project(':test:org.eclipse.virgo.test.launcher')
+		compile group: "org.eclipse.virgo.mirrored", name: "org.eclipse.osgi", version: equinoxVersion, configuration: "compile", ext: "jar"
+		testCompile group: 'junit', name: 'junit', version: junitVersion, configuration: "compile", ext: "jar"
+	}
+}
+
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleDependencies.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleDependencies.java
new file mode 100644
index 0000000..e3b899e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleDependencies.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * BundleDependencies defines class-level metadata which can be used to instruct test framework with extra bundles to
+ * append to framework (system bundle) during start up.
+ * <p />
+ * This set of bundles is considered to be <em>transitively complete</em>, that is all dependencies must be provided.
+ * 
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface BundleDependencies {
+
+    
+    /**
+     * Bundle entries to load as part of the test framework.
+     * 
+     * @return
+     */
+    BundleEntry[] entries() default {};
+    
+    /**
+     * Whether or not {@link #entries() entries} from superclasses should be <em>inherited</em>.
+     * <p>Default value is <code>true</code>, which means that entries from subclass will be
+     * <em>appended</em> to the list of entries from superclass.
+     * 
+     * @return
+     */
+    boolean inheritDependencies() default true;
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleEntry.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleEntry.java
new file mode 100644
index 0000000..6f257e3
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleEntry.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Eclipse Virgo project.
+ *
+ * Copyright (c) 2010 Chariot Solutions
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    dsklyut - initial contribution
+ */
+
+package org.eclipse.virgo.test.framework;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 
+ * BundleEntry defines a URI of the bundle to install and autoStart flag that signals to framework that particular entry
+ * must be started as well as installed.
+ * <p />
+ * Note: This annotation is used by {@link BundleDependencies} and not designed to be used stand-alone
+ */
+@Target({})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BundleEntry {
+
+    /**
+     * URI of the bundle to install.  Must be a valid {@link URI} format.
+     * @return
+     */
+    String value();
+
+    /**
+     * (* Optional *)
+     * Flag to auto start bundle after install into framework.
+     * By default it is true.
+     * 
+     * For fragment bundles - make sure to set it to false
+     * @return
+     */
+    boolean autoStart() default true;
+}
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleLocationLocator.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleLocationLocator.java
new file mode 100644
index 0000000..3744000
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/BundleLocationLocator.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import java.security.ProtectionDomain;
+
+final class BundleLocationLocator {
+
+	public static String determineBundleLocation(Class<?> clazz) {
+		ProtectionDomain pd = clazz.getProtectionDomain();
+		String location = pd.getCodeSource().getLocation().toString();
+        return location;
+	}
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigLocation.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigLocation.java
new file mode 100644
index 0000000..e4e50ef
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigLocation.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * <code>ConfigLocation</code> can be used to annotate a test class to specify the location from which
+ * the test framework should load its configuration. In the absence of the annotation the default
+ * location of <code>META-INF/test.config.properties</code> is used.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface ConfigLocation {
+    
+    String value();
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoader.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoader.java
new file mode 100644
index 0000000..356330c
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoader.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import org.eclipse.virgo.util.common.PropertyPlaceholderResolver;
+
+public class ConfigurationPropertiesLoader {
+
+    private static final String DEFAULT_USER_CONFIG_LOCATION = "META-INF/test.config.properties";
+
+    private static final String DEFAULT_CONFIG_LOCATION = "org/eclipse/virgo/test/framework/base.configuration.properties";
+
+    private static final String PROP_PROPERTIES_INCLUDE = "org.eclipse.virgo.test.properties.include";
+
+    private static final String PROP_BASEDIR = "basedir";
+
+    public Properties loadConfigurationProperties(Class<?> testClass) throws IOException {
+        Properties config = new Properties();
+        loadProperties(config, getClass().getClassLoader(), DEFAULT_CONFIG_LOCATION, true);
+
+        String userConfigLocation = determineUserConfigLocation(testClass);
+
+        loadProperties(config, testClass.getClassLoader(), userConfigLocation, false);
+        loadConfiguredProperties(config);
+        addInBuiltProperties(config);
+
+        PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver();
+        return resolver.resolve(config);
+    }
+
+    private String determineUserConfigLocation(Class<?> testClass) {
+        ConfigLocation configLocation = testClass.getAnnotation(ConfigLocation.class);
+        if (configLocation != null) {
+            return configLocation.value();
+        } else {
+            return DEFAULT_USER_CONFIG_LOCATION;
+        }
+    }
+
+    private void loadConfiguredProperties(Properties config) throws IOException {
+        String includeProperties = config.getProperty(PROP_PROPERTIES_INCLUDE);
+        if (includeProperties != null && includeProperties.trim().length() > 0) {
+            String[] includes = includeProperties.split(",");
+            for (String include : includes) {
+                URL url = new URL(include.trim());
+                InputStream s = url.openStream();
+                try {
+                    config.load(s);
+                } finally {
+                    s.close();
+                }
+            }
+        }
+
+    }
+
+    private void addInBuiltProperties(Properties config) {
+        if (!config.containsKey(PROP_BASEDIR)) {
+            config.setProperty(PROP_BASEDIR, System.getProperty("user.dir"));
+        }
+    }
+
+    protected final void loadProperties(Properties properties, ClassLoader classLoader, String path, boolean required) throws IOException {
+        InputStream stream = classLoader.getResourceAsStream(path);
+
+        if (required && stream == null) {
+            throw new IllegalStateException("Unable to locate configuration file '" + path + "' from '" + classLoader + "'");
+        }
+        if (stream != null) {
+            try {
+                properties.load(stream);
+            } finally {
+                stream.close();
+            }
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/OsgiTestRunner.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/OsgiTestRunner.java
new file mode 100644
index 0000000..f63a26d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/OsgiTestRunner.java
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.virgo.test.launcher.FrameworkBuilder;
+import org.eclipse.virgo.test.launcher.FrameworkBuilder.FrameworkCustomizer;
+import org.eclipse.virgo.test.framework.plugin.PluginManager;
+import org.eclipse.virgo.util.common.PropertyPlaceholderResolver;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JUnit TestRunner for running OSGi integration tests on the Equinox framework.
+ * <p/>
+ *
+ * <strong>Concurrent Semantics</strong><br />
+ * TODO Document concurrent semantics of OsgiTestRunner
+ */
+public class OsgiTestRunner extends BlockJUnit4ClassRunner {
+    
+    private static final int DEFAULT_BUNDLE_START_LEVEL = 4;
+
+    private final ConfigurationPropertiesLoader loader = new ConfigurationPropertiesLoader();
+
+    private final PluginManager pluginManager;
+
+    private final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+    private final ObjectName searchObjectName;
+
+    public OsgiTestRunner(Class<?> klass) throws InitializationError, MalformedObjectNameException {
+        super(klass);
+        this.pluginManager = new PluginManager(klass);
+        this.searchObjectName = new ObjectName("org.eclipse.virgo.*:*");
+    }
+
+    private void stupidEquinoxHack() {
+        try {
+            Field field = FrameworkProperties.class.getDeclaredField("properties");
+            synchronized (FrameworkProperties.class) {
+                field.setAccessible(true);
+                field.set(null, null);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to hack Equinox", e);
+        }
+    }
+
+    @Override
+    public final void run(RunNotifier notifier) {
+
+        Framework framework = null;
+
+        try {
+            stupidEquinoxHack();
+
+            // Preserve and re-instate the context classloader since tests can sometimes leave it in a strange state.
+            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+            try {
+                final Properties configurationProperties = createConfigurationProperties();
+                framework = launchOsgi(configurationProperties);
+                BundleContext targetBundleContext = getTargetBundleContext(framework.getBundleContext());
+                postProcessTargetBundleContext(targetBundleContext, configurationProperties);
+                Bundle testBundle = installAndStartTestBundle(targetBundleContext);
+                Class<?> osgiTestClass = createOsgiTestClass(testBundle);
+                // create the real runner, dispatch it against the class loaded from OSGi
+                BlockJUnit4ClassRunner realRunner = new BlockJUnit4ClassRunner(osgiTestClass);
+                realRunner.run(notifier);
+            } finally {
+                Thread.currentThread().setContextClassLoader(classLoader);
+            }
+        } catch (Throwable e) {
+            notifier.fireTestFailure(new Failure(getDescription(), e));
+        } finally {
+            if (framework != null) {
+                try {
+                    framework.stop();
+                    framework.waitForStop(30000);
+                } catch (Throwable e) {
+                    e.printStackTrace();
+                }
+            }
+            unregisterVirgoMBeans(notifier);
+            // TODO confirm that this can be removed as nested frameworks are no longer used
+            //BundleFileClosingBundleFileWrapperFactoryHook.getInstance().cleanup();
+        }
+    }
+
+    private void unregisterVirgoMBeans(RunNotifier notifier) {
+        Set<ObjectName> objectNames = this.server.queryNames(this.searchObjectName, null);
+
+        if (objectNames.size() == 0) {
+            return;
+        }
+
+        for (ObjectName objectName : objectNames) {
+            try {
+                this.server.unregisterMBean(objectName);
+            } catch (JMException jme) {
+                jme.printStackTrace();
+            }
+        }
+
+        notifier.fireTestFailure(new Failure(getDescription(),
+                                             new IllegalStateException("The mBeans " + objectNames +
+                                                                       " were not unregistered.")));
+    }
+
+    private Bundle installAndStartTestBundle(BundleContext targetBundleContext) throws BundleException {
+        Bundle testBundle = targetBundleContext.installBundle(getTestBundleLocation());
+        testBundle.start();
+        return testBundle;
+    }
+
+    /**
+     * Returns the {@link BundleContext} that should be used to install the test bundle
+     *
+     * @return the target <code>BundleContext</code>.
+     */
+    protected BundleContext getTargetBundleContext(BundleContext bundleContext) {
+        return bundleContext;
+    }
+
+    protected void postProcessTargetBundleContext(BundleContext bundleContext, Properties frameworkProperties) throws Exception {
+        // nothing for this implementation...
+    }
+
+    // load the test class from within OSGi
+    private Class<?> createOsgiTestClass(Bundle testBundle) throws ClassNotFoundException {
+        Class<?> osgiJavaTestClass = testBundle.loadClass(getTestClass().getName());
+        Class<?> osgiTestClass = osgiJavaTestClass;
+        return osgiTestClass;
+    }
+
+    // launch the OSGi framework. will also install the test bundle
+    private Framework launchOsgi(Properties frameworkProperties) throws Exception {
+        final Properties configurationProperties = new Properties(frameworkProperties);
+        FrameworkBuilder builder = new FrameworkBuilder(configurationProperties, new FrameworkCustomizer() {
+
+            public void beforeInstallBundles(Framework framework) {
+
+                /*
+                 * Use the same default start level as the user region bundles. Program the framework start level
+                 * instance defensively to allow for stubs which don't understand adapt.
+                 */
+                FrameworkStartLevel frameworkStartLevel = (FrameworkStartLevel) framework.getBundleContext().getBundle(0).adapt(
+                    FrameworkStartLevel.class);
+                if (frameworkStartLevel != null) {
+                    final CountDownLatch latch = new CountDownLatch(1);
+                    frameworkStartLevel.setStartLevel(DEFAULT_BUNDLE_START_LEVEL, new FrameworkListener() {
+
+                        @Override
+                        public void frameworkEvent(FrameworkEvent event) {
+                            if (FrameworkEvent.STARTLEVEL_CHANGED == event.getType()) {
+                                latch.countDown();
+                            }
+                            
+                        }});
+                    try {
+                        latch.await(30000, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException("Start level latch interrupted", e);
+                    }
+                    frameworkStartLevel.setInitialBundleStartLevel(DEFAULT_BUNDLE_START_LEVEL);
+                }
+
+                OsgiTestRunner.this.pluginManager.getPluginDelegate().beforeInstallBundles(framework, configurationProperties);
+            }
+
+            public void afterInstallBundles(Framework framework) {
+
+            }
+        });
+        addUserConfiguredBundles(builder, configurationProperties);
+        return builder.start();
+    }
+
+    private void addUserConfiguredBundles(FrameworkBuilder builder, Properties configurationProperties) throws Exception {
+        Class<BundleDependencies> annotationType = BundleDependencies.class;
+        Class<?> annotationDeclaringClazz = TestFrameworkUtils.findAnnotationDeclaringClass(annotationType, getTestClass().getJavaClass());
+
+        if (annotationDeclaringClazz == null) {
+            // could not find an 'annotation declaring class' for annotation + annotationType + and targetType +
+            // startFromClazz
+            return;
+        }
+
+        List<BundleEntry> bundleEntries = new ArrayList<BundleEntry>();
+
+        while (annotationDeclaringClazz != null) {
+            BundleDependencies dependencies = annotationDeclaringClazz.getAnnotation(annotationType);
+            BundleEntry[] entries = dependencies.entries();
+
+            bundleEntries.addAll(0, Arrays.<BundleEntry> asList(entries));
+            annotationDeclaringClazz = dependencies.inheritDependencies() ? TestFrameworkUtils.findAnnotationDeclaringClass(annotationType,
+                annotationDeclaringClazz.getSuperclass()) : null;
+        }
+        PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver();
+        for (BundleEntry entry : bundleEntries) {
+            final String formattedPath = resolver.resolve(entry.value(), configurationProperties);
+            builder.addBundle(new URI(formattedPath), entry.autoStart());
+        }
+
+    }
+
+    private String getTestBundleLocation() {
+        return BundleLocationLocator.determineBundleLocation(getTestClass().getJavaClass());
+    }
+
+    private Properties createConfigurationProperties() throws Exception {
+        return this.loader.loadConfigurationProperties(getTestClass().getJavaClass());
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/TestFrameworkUtils.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/TestFrameworkUtils.java
new file mode 100644
index 0000000..7d29353
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/TestFrameworkUtils.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import java.lang.annotation.Annotation;
+
+import org.eclipse.virgo.util.common.Assert;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleReference;
+import org.osgi.framework.Constants;
+
+public final class TestFrameworkUtils {
+
+    private TestFrameworkUtils() {
+        // do not new me
+    }
+
+    public static BundleContext getBundleContextForTestClass(Class<?> testClass) {
+        ClassLoader classLoader = testClass.getClassLoader();
+        if (classLoader instanceof BundleReference) {
+            return ((BundleReference) classLoader).getBundle().getBundleContext();
+        } else {
+            throw new IllegalArgumentException("Class '" + testClass.getName() + "' does not appear to be a valid test class.");
+        }
+    }
+
+    /**
+     * Checks {@link Constants#FRAGMENT_HOST} header to determine if bundle is a fragment
+     * 
+     * @param bundle
+     * @return
+     */
+    public static boolean isFragment(Bundle bundle) {
+        Assert.notNull(bundle, "bundle is required");
+        return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null;
+    }
+
+    public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotation, Class<?> clazzToStart) {
+        Assert.notNull(annotation, "Annotation is required");
+        // stop on object
+        if (clazzToStart == null || clazzToStart.equals(Object.class)) {
+            return null;
+        }
+
+        return clazzToStart.getAnnotation(annotation) != null ? clazzToStart : findAnnotationDeclaringClass(annotation, clazzToStart.getSuperclass());
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunner.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunner.java
new file mode 100644
index 0000000..1aae215
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunner.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.dmkernel;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.virgo.test.framework.BundleEntry;
+import org.eclipse.virgo.test.framework.OsgiTestRunner;
+import org.eclipse.virgo.test.framework.TestFrameworkUtils;
+import org.eclipse.virgo.util.common.CollectionUtils;
+import org.eclipse.virgo.util.common.PropertyPlaceholderResolver;
+import org.junit.runners.model.InitializationError;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+
+import javax.management.MalformedObjectNameException;
+
+/**
+ * JUnit TestRunner for running OSGi integration tests on the dm Kernel.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * As thread-safe as OsgiTestRunner
+ * 
+ */
+public class DmKernelTestRunner extends OsgiTestRunner {
+
+    private static final int DEFAULT_BUNDLE_START_LEVEL = 4;
+
+    private static final long DEFAULT_USER_REGION_START_WAIT_TIME = 60000;
+
+    private final long userRegionStartWaitTime;
+
+    public DmKernelTestRunner(Class<?> klass) throws InitializationError, MalformedObjectNameException {
+        this(klass, DEFAULT_USER_REGION_START_WAIT_TIME);
+    }
+
+    protected DmKernelTestRunner(Class<?> klass, long userRegionStartWaitTime) throws InitializationError, MalformedObjectNameException {
+        super(klass);
+        this.userRegionStartWaitTime = userRegionStartWaitTime;
+    }
+
+    @Override
+    protected BundleContext getTargetBundleContext(BundleContext bundleContext) {
+        Collection<ServiceReference<BundleContext>> serviceReferences = getUserRegionBundleContextServiceReferences(bundleContext);
+
+        if (serviceReferences != null) {
+            if (serviceReferences.size() != 1) {
+                throw new IllegalStateException("There must be exactly one user region bundle context in the service registry. "
+                    + serviceReferences.size() + " were found.");
+            } else {
+                BundleContext targetBundleContext = (BundleContext) bundleContext.getService(serviceReferences.iterator().next());
+                if (targetBundleContext != null) {
+                    return targetBundleContext;
+                }
+            }
+        }
+        throw new IllegalStateException("User region's bundle context was not available from the service registry within "
+            + (this.userRegionStartWaitTime / 1000) + " seconds.");
+    }
+
+    /**
+     * Installs additional user region bundles based on {@link RegionBundleDependencies}
+     */
+    @Override
+    protected void postProcessTargetBundleContext(BundleContext bundleContext, Properties frameworkConfigurationProperties) throws Exception {
+
+        /*
+         * Use the same default start level for user region bundles as the user region factory. Program the framework
+         * start level instance defensively to allow for stubs which don't understand adapt.
+         */
+        FrameworkStartLevel frameworkStartLevel = (FrameworkStartLevel) bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
+        if (frameworkStartLevel != null) {
+            frameworkStartLevel.setInitialBundleStartLevel(DEFAULT_BUNDLE_START_LEVEL);
+        }
+
+        final Properties configurationProperties = new Properties(frameworkConfigurationProperties);
+
+        // This list is installed post installation of user region bundle that includes bundles listed in the
+        // user region configuration file as implemented in kernel RegionManager
+        Class<RegionBundleDependencies> annotationType = RegionBundleDependencies.class;
+        Class<?> annotationDeclaringClazz = TestFrameworkUtils.findAnnotationDeclaringClass(annotationType, getTestClass().getJavaClass());
+
+        if (annotationDeclaringClazz == null) {
+            // could not find an 'annotation declaring class' for annotation + annotationType + and targetType +
+            // startFromClazz
+            return;
+        }
+
+        final List<Bundle> bundlesToStart = new ArrayList<Bundle>();
+
+        List<BundleEntry> bundleEntries = new ArrayList<BundleEntry>();
+
+        while (annotationDeclaringClazz != null) {
+            RegionBundleDependencies dependencies = annotationDeclaringClazz.getAnnotation(annotationType);
+            BundleEntry[] entries = dependencies.entries();
+
+            bundleEntries.addAll(0, Arrays.<BundleEntry> asList(entries));
+            annotationDeclaringClazz = dependencies.inheritDependencies() ? TestFrameworkUtils.findAnnotationDeclaringClass(annotationType,
+                annotationDeclaringClazz.getSuperclass()) : null;
+        }
+        PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver();
+
+        if (!CollectionUtils.isEmpty(bundleEntries)) {
+
+            for (BundleEntry bundleEntry : bundleEntries) {
+
+                String path = bundleEntry.value();
+                boolean autoStart = bundleEntry.autoStart();
+
+                String formattedPath = resolver.resolve(path, configurationProperties);
+                Bundle bundle = bundleContext.installBundle(new URI(formattedPath).toString());
+
+                if (autoStart && !TestFrameworkUtils.isFragment(bundle)) {
+                    bundlesToStart.add(bundle);
+                }
+            }
+        }
+
+        for (Bundle bundle : bundlesToStart) {
+            try {
+                bundle.start();
+            } catch (BundleException e) {
+                throw new BundleException("Failed to start bundle " + bundle.getSymbolicName() + " " + bundle.getVersion(), e);
+            }
+        }
+    }
+
+    private Collection<ServiceReference<BundleContext>> getUserRegionBundleContextServiceReferences(BundleContext bundleContext) {
+
+        long startTime = System.currentTimeMillis();
+
+        Collection<ServiceReference<BundleContext>> serviceReferences = doGetUserRegionBundleContextServiceReferences(bundleContext);
+
+        while (serviceReferences.size() == 0) {
+            if (System.currentTimeMillis() < (this.userRegionStartWaitTime + startTime)) {
+                try {
+                    Thread.sleep(500);
+                    serviceReferences = doGetUserRegionBundleContextServiceReferences(bundleContext);
+                } catch (InterruptedException ie) {
+                    throw new RuntimeException(ie);
+                }
+            } else {
+                break;
+            }
+        }
+        return serviceReferences;
+    }
+
+    private Collection<ServiceReference<BundleContext>> doGetUserRegionBundleContextServiceReferences(BundleContext bundleContext) {
+        try {
+            return bundleContext.getServiceReferences(BundleContext.class, "(org.eclipse.virgo.kernel.regionContext=true)");
+        } catch (InvalidSyntaxException e) {
+            throw new RuntimeException("Unexpected InvalidSyntaxException when looking up the user region's BundleContext", e);
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/RegionBundleDependencies.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/RegionBundleDependencies.java
new file mode 100644
index 0000000..3654c25
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/dmkernel/RegionBundleDependencies.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the Eclipse Virgo project.
+ *
+ * Copyright (c) 2010 Chariot Solutions, LLC
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    dsklyut - initial contribution
+ */
+
+package org.eclipse.virgo.test.framework.dmkernel;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.eclipse.virgo.test.framework.BundleEntry;
+
+
+/**
+ * UserRegionBundleDependencies defines class-level metadata which can be used to instruct test framework with extra bundles to
+ * append to <a href="http://wiki.eclipse.org/Virgo/Concepts#Regions"> UserRegion</a> during start up.
+ * <p />
+ * This set of bundles is considered to be "transitively complete", that is all dependencies must be provided.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface RegionBundleDependencies {
+    
+    /**
+     * Bundle entries to load as part of the test framework.
+     * 
+     * @return
+     */
+    BundleEntry[] entries() default {};
+    
+    /**
+     * Whether or not {@link #entries() entries} from superclasses should be <em>inherited</em>.
+     * <p>Default value is <code>true</code>, which means that entries from subclass will be
+     * <em>appended</em> to the list of entries from superclass.
+     * 
+     * @return
+     */
+    boolean inheritDependencies() default true;
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/EmptyPlugin.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/EmptyPlugin.java
new file mode 100644
index 0000000..7290915
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/EmptyPlugin.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+import java.util.Properties;
+
+import org.osgi.framework.launch.Framework;
+
+public abstract class EmptyPlugin implements Plugin{
+
+	public void beforeInstallBundles(Framework framework, Properties configurationProperties) {
+	}
+
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugin.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugin.java
new file mode 100644
index 0000000..87f02a9
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugin.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+import java.util.Properties;
+
+import org.osgi.framework.launch.Framework;
+
+public interface Plugin {
+
+	void beforeInstallBundles(Framework framework, Properties configurationProperties);
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginException.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginException.java
new file mode 100644
index 0000000..2b30453
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginException.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+public class PluginException extends RuntimeException {
+
+	private static final long serialVersionUID = -2589928580612707135L;
+
+	public PluginException() {
+		super();
+	}
+
+	public PluginException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PluginException(String message) {
+		super(message);
+	}
+
+	public PluginException(Throwable cause) {
+		super(cause);
+	}
+
+	
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginManager.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginManager.java
new file mode 100644
index 0000000..b5e2dd8
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/PluginManager.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class PluginManager {
+
+    private final Plugin[] plugins;
+
+    private final Plugin pluginDelegate;
+
+    public PluginManager(Class<?> testClass) {
+        this.plugins = createPlugins(testClass);
+        this.pluginDelegate = (Plugin) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Plugin.class },
+            new PluginDelegateInvocationHandler(this.plugins));
+    }
+
+    public Plugin[] getConfiguredPlugins() {
+        Plugin[] copy = new Plugin[this.plugins.length];
+        System.arraycopy(this.plugins, 0, copy, 0, copy.length);
+        return copy;
+    }
+
+    public Plugin getPluginDelegate() {
+        return this.pluginDelegate;
+    }
+
+    private Plugin[] createPlugins(Class<?> testClass) {
+        Plugins annotation = testClass.getAnnotation(Plugins.class);
+        if (annotation == null) {
+            return new Plugin[0];
+        } else {
+            return doCreatePlugins(annotation.value());
+        }
+
+    }
+
+    private Plugin[] doCreatePlugins(Class<? extends Plugin>[] pluginClasses) {
+        Plugin[] result = new Plugin[pluginClasses.length];
+        for (int x = 0; x < result.length; x++) {
+            Class<? extends Plugin> cls = pluginClasses[x];
+            try {
+                result[x] = cls.newInstance();
+            } catch (InstantiationException e) {
+                throw new PluginException("Unable to instantiate plugin '" + cls.getName() + "'", e);
+            } catch (IllegalAccessException e) {
+                throw new PluginException("Unable to create plugin '" + cls.getName() + "'. Constructor not accessible.", e);
+            }
+        }
+        return result;
+    }
+
+    private static class PluginDelegateInvocationHandler implements InvocationHandler {
+
+        private final Plugin[] plugins;
+
+        public PluginDelegateInvocationHandler(Plugin[] plugins) {
+            this.plugins = plugins;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            for (Plugin p : this.plugins) {
+                try {
+                    method.invoke(p, args);
+                } catch (InvocationTargetException e) {
+                    Throwable targetException = e.getTargetException();
+                    throw targetException;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugins.java b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugins.java
new file mode 100644
index 0000000..52de87b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/java/org/eclipse/virgo/test/framework/plugin/Plugins.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Plugins {
+
+	Class<? extends Plugin>[] value();
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/main/resources/org/eclipse/virgo/test/framework/base.configuration.properties b/test/org.eclipse.virgo.test.framework/src/main/resources/org/eclipse/virgo/test/framework/base.configuration.properties
new file mode 100644
index 0000000..ee7950b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/main/resources/org/eclipse/virgo/test/framework/base.configuration.properties
@@ -0,0 +1,35 @@
+# Configure JUnit to be in the system package list
+org.osgi.framework.system.packages.extra=junit.extensions;version="4.5.0", \
+ junit.framework;version="4.5.0", \
+ junit.runner;version="4.5.0", \
+ junit.textui;version="4.5.0", \
+ org.hamcrest;version="4.5.0", \
+ org.hamcrest.core;version="4.5.0", \
+ org.hamcrest.internal;version="4.5.0", \
+ org.junit;version="4.7.0", \
+ org.junit.experimental.results;version="4.7.0", \
+ org.junit.experimental.runners;version="4.7.0", \
+ org.junit.experimental.theories;version="4.7.0", \
+ org.junit.experimental.theories.internal;version="4.7.0", \
+ org.junit.experimental.theories.suppliers;version="4.7.0", \
+ org.junit.internal;version="4.7.0", \
+ org.junit.internal.builders;version="4.7.0", \
+ org.junit.internal.matchers;version="4.7.0", \
+ org.junit.internal.requests;version="4.7.0", \
+ org.junit.internal.runners;version="4.7.0", \
+ org.junit.internal.runners.model;version="4.7.0", \
+ org.junit.internal.runners.statements;version="4.7.0", \
+ org.junit.matchers;version="4.7.0", \
+ org.junit.runner;version="4.7.0", \
+ org.junit.runner.manipulation;version="4.7.0", \
+ org.junit.runner.notification;version="4.7.0", \
+ org.junit.runners;version="4.7.0", \
+ org.junit.runners.model;version="4.7.0", \
+ org.eclipse.virgo.test.framework, \
+ org.eclipse.virgo.test.framework.plugin, \
+ org.eclipse.virgo.test.framework.url
+
+ # Equinox-specific parameters
+ osgi.clean=true
+
+ 
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/BundleLocationLocatorTests.java b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/BundleLocationLocatorTests.java
new file mode 100644
index 0000000..d59b99e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/BundleLocationLocatorTests.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.URI;
+
+import org.eclipse.virgo.test.framework.BundleLocationLocator;
+import org.junit.Test;
+import org.osgi.framework.launch.Framework;
+
+public class BundleLocationLocatorTests {
+
+	@Test
+	public void testFileLocation() throws Exception {
+		String location = BundleLocationLocator.determineBundleLocation(BundleLocationLocatorTests.class);
+		URI uri = new URI(location);
+		assertTrue(new File(uri).exists());
+	}
+	
+	@Test
+	public void testJarLocation() throws Exception {
+		String location = BundleLocationLocator.determineBundleLocation(Framework.class);
+		URI uri = new URI(location);
+		assertTrue(new File(uri).exists());
+	}
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoaderTests.java b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoaderTests.java
new file mode 100644
index 0000000..0384220
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/ConfigurationPropertiesLoaderTests.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.eclipse.virgo.test.framework.ConfigLocation;
+import org.eclipse.virgo.test.framework.ConfigurationPropertiesLoader;
+import org.junit.Test;
+
+public class ConfigurationPropertiesLoaderTests {
+
+    @Test
+    public void testLoadProperties() throws IOException {
+        ConfigurationPropertiesLoader loader = new ConfigurationPropertiesLoader();
+        Properties properties = loader.loadConfigurationProperties(getClass());
+        
+        assertNotNull(properties);
+        
+        assertEquals("true", properties.getProperty("user"));
+        assertEquals("true", properties.getProperty("extra"));
+    }
+    
+    @Test
+    public void testInBuiltProperties() throws IOException {
+        ConfigurationPropertiesLoader loader = new ConfigurationPropertiesLoader();
+        Properties properties = loader.loadConfigurationProperties(getClass());
+        
+        assertNotNull(properties);
+        
+        assertNotNull(properties.getProperty("basedir"));
+    }
+    
+    @Test
+    public void testResolve() throws IOException {
+        ConfigurationPropertiesLoader loader = new ConfigurationPropertiesLoader();
+        Properties properties = loader.loadConfigurationProperties(getClass());
+        
+        assertNotNull(properties);
+        
+        assertEquals("true", properties.getProperty("replace"));
+    }
+    
+    @Test
+    public void customConfigLocation() throws IOException {
+        ConfigurationPropertiesLoader loader = new ConfigurationPropertiesLoader();
+        Properties properties = loader.loadConfigurationProperties(CustomConfigLocation.class);
+        assertEquals("true", properties.getProperty("custom"));
+    }
+    
+    @ConfigLocation("META-INF/custom.config.properties")
+    private static final class CustomConfigLocation {
+        
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunnerTests.java b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunnerTests.java
new file mode 100644
index 0000000..e615f5f
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/DmKernelTestRunnerTests.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.dmkernel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.eclipse.virgo.test.framework.dmkernel.DmKernelTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runners.model.InitializationError;
+import org.osgi.framework.BundleContext;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+import org.eclipse.virgo.test.stubs.support.TrueFilter;
+
+import javax.management.MalformedObjectNameException;
+
+public class DmKernelTestRunnerTests {
+
+    private DmKernelTestRunner testRunner;
+
+    private final StubBundleContext kernelBundleContext = new StubBundleContext();
+
+    @Before
+    public void setup() throws InitializationError, MalformedObjectNameException {
+        this.testRunner = new DmKernelTestRunner(getClass(), 1000);
+        this.kernelBundleContext.addFilter("(org.eclipse.virgo.kernel.regionContext=true)", new TrueFilter());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void failureWhenUserRegionBundleContextIsNotPresent() {
+        long start = System.currentTimeMillis();
+        try {
+            this.testRunner.getTargetBundleContext(this.kernelBundleContext);
+        } finally {
+            assertTrue(System.currentTimeMillis() - start >= 1000);
+        }
+    }
+
+    @Test
+    public void userRegionBundleContextRetrievedFromServiceRegistry() {
+        StubBundleContext userRegionBundleContext = new StubBundleContext();
+
+        Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        properties.put("org.eclipse.virgo.kernel.regionContext", true);
+        this.kernelBundleContext.registerService(BundleContext.class.getName(), userRegionBundleContext, properties);
+
+        assertEquals(userRegionBundleContext, this.testRunner.getTargetBundleContext(this.kernelBundleContext));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void failureWhenMultipleUserRegionBundleContextsAreAvailable() {
+        StubBundleContext userRegionBundleContext = new StubBundleContext();
+
+        Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        properties.put("org.eclipse.virgo.kernel.regionContext", true);
+        this.kernelBundleContext.registerService(BundleContext.class.getName(), userRegionBundleContext, properties);
+        this.kernelBundleContext.registerService(BundleContext.class.getName(), userRegionBundleContext, properties);
+
+        assertEquals(userRegionBundleContext, this.testRunner.getTargetBundleContext(this.kernelBundleContext));
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/RegionDependenciesDmKernelRunnerTests.java b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/RegionDependenciesDmKernelRunnerTests.java
new file mode 100644
index 0000000..b08caf7
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/dmkernel/RegionDependenciesDmKernelRunnerTests.java
@@ -0,0 +1,202 @@
+/*
+ * This file is part of the Eclipse Virgo project.
+ *
+ * Copyright (c) 2011 copyright_holder
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    dsklyut - initial contribution
+ */
+
+package org.eclipse.virgo.test.framework.dmkernel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.virgo.test.framework.BundleEntry;
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+import org.eclipse.virgo.test.stubs.support.TrueFilter;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runners.model.InitializationError;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Version;
+
+/**
+ * TODO Document RegionDependenciesDmKernelRunnerTests
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * TODO Document concurrent semantics of RegionDependenciesDmKernelRunnerTests
+ */
+public class RegionDependenciesDmKernelRunnerTests {
+
+    @RegionBundleDependencies(entries = { @BundleEntry(value = "file:./src/test/resources/test-bundle2", autoStart = false) })
+    public static class WithRegionDependenciesTest {
+
+        @Test
+        public void stubTest1() {
+        }
+    }
+
+    @RegionBundleDependencies(entries = { @BundleEntry(value = "file:./src/test/resources/test-bundle1", autoStart = false) }, inheritDependencies = true)
+    public static class WithRegionDependenciesAndInheritance extends WithRegionDependenciesTest {
+
+        @Test
+        public void stubTest3() {
+
+        }
+    }
+
+    @RegionBundleDependencies(entries = { @BundleEntry("file:./src/test/resources/test-bundle1") }, inheritDependencies = false)
+    public static class WithRegionDependenciesAndNoInheritance extends WithRegionDependenciesAndInheritance {
+
+        @Test
+        public void stubTest3() {
+
+        }
+    }
+
+    private DmKernelTestRunner testRunner;
+
+    private final StubBundleContext kernelBundleContext = new StubBundleContext();
+
+    @Before
+    public void setup() throws InitializationError {
+        // this.testRunner = new DmKernelTestRunner(getClass(), 1000);
+        this.kernelBundleContext.addFilter("(org.eclipse.virgo.kernel.regionContext=true)", new TrueFilter());
+
+        // set-up user region bundle context
+        StubBundleContext userRegionBundleContext = new StubBundleContext();
+        
+        StubBundle stubSystemBundle = new StubBundle(0L, "system-bundle", Version.emptyVersion, "system.bundle.location");
+        userRegionBundleContext.addInstalledBundle(stubSystemBundle);
+
+        Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        properties.put("org.eclipse.virgo.kernel.regionContext", true);
+        this.kernelBundleContext.registerService(BundleContext.class.getName(), userRegionBundleContext, properties);
+
+    }
+
+    @Test
+    public void testRegionBundleDependenciesUsage() {
+        try {
+            this.testRunner = new DmKernelTestRunner(WithRegionDependenciesTest.class, 1000);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail(ex.getMessage());
+        }
+        BundleContext userRegionBundleContext = testRunner.getTargetBundleContext(kernelBundleContext);
+        assertNotNull(userRegionBundleContext);
+        TestBundleListener listener = new TestBundleListener();
+        userRegionBundleContext.addBundleListener(listener);
+
+        try {
+            this.testRunner.postProcessTargetBundleContext(userRegionBundleContext, new Properties());
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+
+        assertTrue(listener.getCalled());
+        assertEquals(1, listener.getEvents().length);
+        Bundle dep = listener.getEvents()[0].getBundle();
+        assertEquals("file:./src/test/resources/test-bundle2", dep.getLocation());
+        // autoStart == false
+        assertEquals(Bundle.INSTALLED, dep.getState());
+    }
+
+    @Test
+    public void testWithRegionDependenciesAndInheritance() {
+        try {
+            this.testRunner = new DmKernelTestRunner(WithRegionDependenciesAndInheritance.class, 1000);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail(ex.getMessage());
+        }
+        BundleContext userRegionBundleContext = testRunner.getTargetBundleContext(kernelBundleContext);
+        assertNotNull(userRegionBundleContext);
+        TestBundleListener listener = new TestBundleListener();
+        userRegionBundleContext.addBundleListener(listener);
+
+        try {
+            this.testRunner.postProcessTargetBundleContext(userRegionBundleContext, new Properties());
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+
+        // should have 2 bundles. Both of them autoStart == false
+        // provided
+        assertTrue(listener.getCalled());
+        assertEquals(2, listener.getEvents().length);
+
+        for (BundleEvent e : listener.getEvents()) {
+            assertEquals(Bundle.INSTALLED, e.getBundle().getState());
+        }
+    }
+
+    @Test
+    public void testWithRegionDependenciesAndNoInheritance() {
+        try {
+            this.testRunner = new DmKernelTestRunner(WithRegionDependenciesAndNoInheritance.class, 1000);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail(ex.getMessage());
+        }
+        BundleContext userRegionBundleContext = testRunner.getTargetBundleContext(kernelBundleContext);
+        assertNotNull(userRegionBundleContext);
+        TestBundleListener listener = new TestBundleListener();
+        userRegionBundleContext.addBundleListener(listener);
+
+        try {
+            this.testRunner.postProcessTargetBundleContext(userRegionBundleContext, new Properties());
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+
+        // should have 1 bundle. and it should be started
+        assertTrue(listener.getCalled());
+        assertEquals(1, listener.getEvents().length);
+        Bundle installedBundle = listener.getEvents()[0].getBundle();
+
+        assertEquals(Bundle.ACTIVE, installedBundle.getState());
+        assertEquals("file:./src/test/resources/test-bundle1", installedBundle.getLocation());
+
+    }
+
+    private static class TestBundleListener implements BundleListener {
+
+        private boolean called = false;
+
+        private List<BundleEvent> events = new ArrayList<BundleEvent>();
+
+        public void bundleChanged(BundleEvent event) {
+            this.called = true;
+            this.events.add(event);
+        }
+
+        public boolean getCalled() {
+            return called;
+        }
+
+        public BundleEvent[] getEvents() {
+            return events.toArray(new BundleEvent[events.size()]);
+        }
+
+    }
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/plugin/PluginManagerTests.java b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/plugin/PluginManagerTests.java
new file mode 100644
index 0000000..8b1a876
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/java/org/eclipse/virgo/test/framework/plugin/PluginManagerTests.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.framework.plugin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.virgo.test.framework.plugin.EmptyPlugin;
+import org.eclipse.virgo.test.framework.plugin.Plugin;
+import org.eclipse.virgo.test.framework.plugin.PluginException;
+import org.eclipse.virgo.test.framework.plugin.PluginManager;
+import org.eclipse.virgo.test.framework.plugin.Plugins;
+import org.junit.Test;
+import org.osgi.framework.launch.Framework;
+
+public class PluginManagerTests {
+
+	@Test
+	public void testConstructPluginManager() {
+
+		PluginManager manager = new PluginManager(BasicConfiguration.class);
+		Plugin[] configuredPlugins = manager.getConfiguredPlugins();
+		assertNotNull(configuredPlugins);
+		assertEquals(2, configuredPlugins.length);
+		assertTrue(configuredPlugins[0] instanceof DummyPlugin);
+		assertTrue(configuredPlugins[1] instanceof SecondDummyPlugin);
+	}
+	
+	@Test
+	public void testConstructPluginManagerWithNoPlugins() {
+		PluginManager manager = new PluginManager(EmptyConfiguration.class);
+		Plugin[] configuredPlugins = manager.getConfiguredPlugins();
+		assertNotNull(configuredPlugins);
+		assertEquals(0, configuredPlugins.length);
+	}
+
+	@Test(expected=PluginException.class)
+	public void testConstructWithError() {
+		new PluginManager(ErrorConfiguration.class);
+	}
+	
+	@Test
+	public void testDelegate() {
+
+		PluginManager manager = new PluginManager(CounterConfiguration.class);
+		Plugin delegate = manager.getPluginDelegate();
+		assertEquals(0, CountingPlugin.counter.get());
+		delegate.beforeInstallBundles(null, null);
+		assertEquals(1, CountingPlugin.counter.get());
+	}
+	
+	public static class EmptyConfiguration {
+		
+	}
+	
+	@Plugins( { DummyPlugin.class, SecondDummyPlugin.class })
+	public static class BasicConfiguration {
+
+	}
+	
+	@Plugins(InvalidPlugin.class)
+	public static class ErrorConfiguration {
+		
+	}
+
+	@Plugins(CountingPlugin.class)
+    public static class CounterConfiguration {
+        
+    }
+	
+	public static class DummyPlugin extends EmptyPlugin {
+
+	}
+
+	public static class SecondDummyPlugin extends EmptyPlugin {
+
+	}
+	
+	public static class CountingPlugin implements Plugin {
+
+	    static final AtomicInteger counter = new AtomicInteger();
+	    
+        public void beforeInstallBundles(Framework framework, Properties configurationProperties) {
+            counter.incrementAndGet();
+        }
+	    
+	}
+	
+	public static class InvalidPlugin extends EmptyPlugin {
+		private InvalidPlugin() {
+			
+		}
+	}
+}
diff --git a/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/custom.config.properties b/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/custom.config.properties
new file mode 100644
index 0000000..920f577
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/custom.config.properties
@@ -0,0 +1 @@
+custom=true
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/test.config.properties b/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/test.config.properties
new file mode 100644
index 0000000..17614d2
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/resources/META-INF/test.config.properties
@@ -0,0 +1,3 @@
+user=true
+
+org.eclipse.virgo.test.properties.include=file:src/test/resources/extra.properties
diff --git a/test/org.eclipse.virgo.test.framework/src/test/resources/extra.properties b/test/org.eclipse.virgo.test.framework/src/test/resources/extra.properties
new file mode 100644
index 0000000..a5515dd
--- /dev/null
+++ b/test/org.eclipse.virgo.test.framework/src/test/resources/extra.properties
@@ -0,0 +1,2 @@
+extra=true
+replace=${user}
diff --git a/test/org.eclipse.virgo.test.launcher/.springBeans b/test/org.eclipse.virgo.test.launcher/.springBeans
new file mode 100644
index 0000000..8058887
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/.springBeans
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beansProjectDescription>
+	<version>1</version>
+	<pluginVersion><![CDATA[2.2.6.200908051215-RELEASE]]></pluginVersion>
+	<configSuffixes>
+		<configSuffix><![CDATA[xml]]></configSuffix>
+	</configSuffixes>
+	<enableImports><![CDATA[false]]></enableImports>
+	<configs>
+	</configs>
+	<configSets>
+	</configSets>
+</beansProjectDescription>
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkBuilder.java b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkBuilder.java
new file mode 100644
index 0000000..808794b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkBuilder.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.eclipse.virgo.util.parser.launcher.ArgumentParser;
+import org.eclipse.virgo.util.parser.launcher.BundleEntry;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+
+/**
+ * Simple builder API for creating instances of {@link Framework}.
+ * <p/>
+ * Created <code>Frameworks</code> are running when returned from {@link #start()}.
+ * <p/>
+ * Bundles can be installed automatically when starting using {@link #addBundle} and framework properties can be added
+ * using {@link #addFrameworkProperty(String, String)}.
+ * <p/>
+ * Bundles and framework properties <strong>must</strong> must be added before calling <code>start</code>. Any changes
+ * to the configuration of the builder is <strong>not</strong> applied to already running <code>Frameworks</code> .
+ * <p/>
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Not threadsafe. Intended for single-threaded usage.
+ */
+public class FrameworkBuilder {
+
+    private static final String PROP_LAUNCHER_BUNDLES = "launcher.bundles";
+
+    private final ArgumentParser parser = new ArgumentParser();
+
+    private final List<BundleEntry> bundleEntries = new ArrayList<BundleEntry>();
+
+    private final FrameworkCustomizer customizer;
+
+    private final Properties configuration;
+
+    /**
+     * Creates a new <code>FrameworkBuilder</code>.
+     * 
+     * @param frameworkProperties the initial set of framework properties.
+     */
+    public FrameworkBuilder(Properties frameworkProperties) {
+        this(frameworkProperties, new NoOpFrameworkCustomizer());
+    }
+    
+    /**
+     * Creates a new <code>FrameworkBuilder</code>.
+     * 
+     * @param frameworkProperties the initial set of framework properties.
+     * @param customizer the {@link FrameworkCustomizer} to use for customization
+     */
+    public FrameworkBuilder(Properties frameworkProperties, FrameworkCustomizer customizer) {
+        this.customizer = customizer;
+        this.configuration = (frameworkProperties == null ? new Properties() : frameworkProperties);
+        parseLauncherBundlesProperty(this.configuration);
+    }
+
+    /**
+     * Adds a bundle to the launch configuration. The bundle is not automatically started.
+     * 
+     * @param uri the <code>URI</code> of the bundle.
+     * @return the <code>FrameworkBuilder</code>.
+     */
+    public final FrameworkBuilder addBundle(URI uri) {
+        return addBundle(uri, false);
+    }
+
+    /**
+     * Adds a bundle to the launch configuration. The bundle is not automatically started.
+     * 
+     * @param file a <code>File</code> pointing to the bundle.
+     * @return the <code>FrameworkBuilder</code>
+     */
+    public final FrameworkBuilder addBundle(File file) {
+        return addBundle(file.toURI(), false);
+    }
+
+    /**
+     * Adds a bundle to the launch configuration.
+     * 
+     * @param uri the <code>URI</code> of the bundle.
+     * @param autoStart flag signifying whether or not the bundle should be started automatically
+     * @return the <code>FrameworkBuilder</code>.
+     */
+    public final FrameworkBuilder addBundle(URI uri, boolean autoStart) {
+        BundleEntry entry = new BundleEntry(uri, autoStart);
+        this.bundleEntries.add(entry);
+        return this;
+    }
+
+    /**
+     * Adds a bundle to the launch configuration.
+     * 
+     * @param file a <code>File</code> pointing to the bundle.
+     * @param autoStart flag signifying whether or not the bundle should be started automatically
+     * @return the <code>FrameworkBuilder</code>
+     */
+    public final FrameworkBuilder addBundle(File file, boolean autoStart) {
+        return addBundle(file.toURI(), autoStart);
+    }
+
+    /**
+     * Adds a bundle to the launch configuration. The declaration string is of the form:
+     * <code>&lt;path&gt;[@start]</code> where path is either a URI or a file path.
+     * 
+     * @param declaration the bundle declaration string
+     * @return the <code>FrameworkBuilder</code>
+     */
+    public final FrameworkBuilder addBundle(String declaration) {
+        BundleEntry entry = this.parser.parseBundleEntry(declaration);
+        return addBundle(entry.getURI(), entry.isAutoStart());
+    }
+
+    public final FrameworkBuilder addFrameworkProperty(String key, String value) {
+        this.configuration.put(key, value);
+        return this;
+    }
+
+    /**
+     * Starts the {@link Framework}. Installs the configured set of <code>Bundles</code>. Starts <code>Bundles</code>
+     * that have the <code>start</code> flag set.
+     * 
+     * @return the running <code>Framework</code>
+     * @throws BundleException if any bundles fail to install or start.
+     */
+    public final Framework start() throws BundleException {
+        FrameworkFactory frameworkFactory = FrameworkFactoryLocator.createFrameworkFactory();
+
+        Properties resolvedConfiguration = new PropertyPlaceholderResolver().resolve(this.configuration);
+        Map<String, String> resolvedConfigurationMap = convertPropertiesToMap(resolvedConfiguration);
+        Framework fwk = frameworkFactory.newFramework(resolvedConfigurationMap);
+
+        fwk.start();
+        this.customizer.beforeInstallBundles(fwk);
+        installAndStartBundles(fwk.getBundleContext());
+        this.customizer.afterInstallBundles(fwk);
+
+        return fwk;
+    }
+
+	private Map<String, String> convertPropertiesToMap(
+			Properties props) {
+		Map<String, String> map = new HashMap<String,String>();
+        Set<String> stringPropertyNames = props.stringPropertyNames();
+        for (String propName : stringPropertyNames) {
+			map.put(propName, props.getProperty(propName));
+		}
+		return map;
+	}
+
+    private void installAndStartBundles(BundleContext bundleContext) throws BundleException {
+        List<Bundle> bundlesToStart = new ArrayList<Bundle>();
+        for (BundleEntry entry : this.bundleEntries) {
+            Bundle bundle = bundleContext.installBundle(entry.getURI().toString());
+
+            if (entry.isAutoStart()) {
+                bundlesToStart.add(bundle);
+            }
+        }
+
+        for (Bundle bundle : bundlesToStart) {
+            try {
+                bundle.start();
+            } catch (BundleException be) {
+                throw new BundleException("Bundle " + bundle.getSymbolicName() + " " + bundle.getVersion() + " failed to start.", be);
+            }
+        }
+    }
+
+    private void parseLauncherBundlesProperty(Properties configuration) {
+        String launcherBundlesProp = configuration.getProperty(PROP_LAUNCHER_BUNDLES);
+        if (launcherBundlesProp != null) {
+            BundleEntry[] entries = this.parser.parseBundleEntries(launcherBundlesProp);
+            Collections.addAll(this.bundleEntries, entries);
+
+        }
+    }
+
+    /**
+     * Simple callback interface that allows calling code to customize the created {@link Framework} instance.
+     * 
+     * @see FrameworkBuilder
+     */
+    public static interface FrameworkCustomizer {
+
+        /**
+         * Called before the {@link FrameworkBuilder} installs any bundles into the {@link Framework}.
+         * 
+         * @param framework the newly created framework.
+         */
+        void beforeInstallBundles(Framework framework);
+
+        /**
+         * Called after the {@link FrameworkBuilder} has installed the bundle into the {@link Framework}.
+         * 
+         * @param framework the created framework.
+         */
+        void afterInstallBundles(Framework framework);
+    }
+
+    private static class NoOpFrameworkCustomizer implements FrameworkCustomizer {
+
+        public void afterInstallBundles(Framework framework) {
+        }
+
+        public void beforeInstallBundles(Framework framework) {
+        }
+
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocator.java b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocator.java
new file mode 100644
index 0000000..cf48ef2
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocator.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import java.util.Set;
+
+import org.osgi.framework.launch.FrameworkFactory;
+
+/**
+ * Utility class for locating the {@link FrameworkFactory} for the current running VM.
+ * <p/>
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe.
+ */
+final class FrameworkFactoryLocator {
+
+    /**
+     * Creates an instance of {@link FrameworkFactory}. The <code>FrameworkFactory</code> is loaded using the
+     * {@link ServiceLoader} as per the standard conventions.
+     * 
+     * @return the <code>FrameworkFactory</code> instance.
+     * @see FrameworkFactory
+     * @see ServiceLoader
+     * @throws IllegalStateException if no <code>FrameworkFactory</code> is found, or if more than one is present.
+     */
+    public static FrameworkFactory createFrameworkFactory() {
+        ServiceLoader<FrameworkFactory> factoryLoader = ServiceLoader.load(FrameworkFactory.class);
+        return uniqueServiceFromLoader(factoryLoader);
+    }
+
+    private static <T> T uniqueServiceFromLoader(ServiceLoader<T> factoryLoader) {
+        Set<T> services = factoryLoader.get(FrameworkFactory.class.getClassLoader());
+
+        if (services.isEmpty()) {
+            throw new IllegalStateException("No FrameworkFactory services found.");
+        } else if (services.size() > 1) {
+            throw new IllegalStateException("Unable to locate unique FrameworkFactory. Found " + services.size()
+                + " factories. Do you have multiple OSGi implementations on your classpath?");
+        } else {
+            return services.iterator().next();
+        }
+
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/Launcher.java b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/Launcher.java
new file mode 100644
index 0000000..3ea61cc
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/Launcher.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.virgo.util.parser.launcher.ArgumentParser;
+import org.eclipse.virgo.util.parser.launcher.BundleEntry;
+import org.eclipse.virgo.util.parser.launcher.LaunchCommand;
+import org.osgi.framework.BundleException;
+
+
+public class Launcher {
+
+	private static final char UNRECOGNIZED_ARGUMENT_SEPARATOR = ',';
+	
+	private static final String FRAMEWORK_PROPERTY_UNRECOGNIZED_ARGUMENTS = "org.eclipse.virgo.osgi.launcher.unrecognizedArguments";
+	
+	private static final String SYSTEM_PROPERTY_TMPDIR = "java.io.tmpdir";
+
+	public static void main(String[] args) throws IOException {
+	    ensureTmpDirExists();
+	    
+		ArgumentParser parser = new ArgumentParser();
+		LaunchCommand command = parser.parse(args);
+		
+		FrameworkBuilder builder = new FrameworkBuilder(command.getConfigProperties());
+		
+		BundleEntry[] bundleDeclarations = command.getBundleEntries();
+		for (BundleEntry bundleDeclaration : bundleDeclarations) {
+			builder.addBundle(bundleDeclaration.getURI());
+		}
+		
+		Map<String, String> declaredProperties = command.getDeclaredProperties();
+		for (Map.Entry<String, String> entry : declaredProperties.entrySet()) {
+			builder.addFrameworkProperty(entry.getKey(), entry.getValue());
+		}
+		
+		builder.addFrameworkProperty(FRAMEWORK_PROPERTY_UNRECOGNIZED_ARGUMENTS, createUnrecognizedArgumentsProperty(command));
+
+        try {
+            builder.start();
+        } catch (BundleException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+	
+	static void ensureTmpDirExists() throws IOException {
+	    String tmpDirProperty = System.getProperty(SYSTEM_PROPERTY_TMPDIR);
+	    if (tmpDirProperty != null) {
+	        File tmpDir = new File(tmpDirProperty);
+	        if (!tmpDir.isDirectory() && !tmpDir.mkdirs()) {
+	            throw new IOException("Failed to create tmp directory '" + tmpDir.getAbsolutePath() + "'");
+	        }
+	    }
+	}
+	
+	private static String createUnrecognizedArgumentsProperty(LaunchCommand launchCommand) {
+		List<String> unrecognizedArguments = launchCommand.getUnrecognizedArguments();
+		
+		StringBuilder propertyBuilder = new StringBuilder();
+		
+		for (int i = 0; i < unrecognizedArguments.size(); i++) {
+			propertyBuilder.append(unrecognizedArguments.get(i));
+			if ((i + 1) < unrecognizedArguments.size()) {
+				propertyBuilder.append(UNRECOGNIZED_ARGUMENT_SEPARATOR);
+			}
+		}
+		
+		return propertyBuilder.toString();
+	}
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolver.java b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolver.java
new file mode 100644
index 0000000..6aff419
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolver.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Stack;
+import java.util.UUID;
+
+/**
+ * Utility class for resolving placeholders inside a {@link Properties} instance. These placeholders can refer to other
+ * properties in the <code>Properties</code> instance. The place holders may also have a modifier in them
+ * 
+ * <pre>
+ * ${com.springsource:modifier}
+ * </pre>
+ * 
+ * where everything after the colon is considered the modifier. This class does not interpret these modifiers but
+ * rather delegates to a {@link PlaceholderValueTransformer} for processing.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe.
+ */
+public final class PropertyPlaceholderResolver {
+    
+    private static final Pattern PATTERN = Pattern.compile("\\$\\{([^:\\}]*):?([^\\}]*)?\\}");
+
+    private static final PlaceholderValueTransformer IDENTITY_TRANSFORMER = new PlaceholderValueTransformer() {
+
+        public String transform(String propertyName, String propertyValue, String modifier) {
+            return propertyValue;
+        }
+
+    };
+
+    /**
+     * Resolves all placeholders in the supplied {@link Properties} instance.
+     * 
+     * @param input the properties to resolve.
+     * @return the resolved properties.
+     */
+    public Properties resolve(Properties input) {
+        return resolve(input, IDENTITY_TRANSFORMER);
+    }
+
+    /**
+     * Resolves all placeholders in the supplied {@link Properties} instance and transform any based on their modifiers.
+     * 
+     * @param input the properties to resolve.
+     * @param transformer a transformer for handling property modifiers
+     * @return the resolved properties.
+     */
+    public Properties resolve(Properties input, PlaceholderValueTransformer transformer) {
+        Properties result = new Properties();
+        Enumeration<?> propertyNames = input.propertyNames();
+
+        while (propertyNames.hasMoreElements()) {
+            String propertyName = (String) propertyNames.nextElement();
+            result.setProperty(propertyName, resolveProperty(propertyName, input, transformer));
+            
+        }
+
+        return result;
+    }
+
+    /**
+     * Resolves all placeholders in the supplied string with values from a {@link Properties} instance.
+     * 
+     * @param input the string to resolve
+     * @param props the properties to use for resolution
+     * @return the resolved string
+     */
+    public String resolve(String input, Properties props) {
+        return resolve(input, props, IDENTITY_TRANSFORMER);
+    }
+
+    /**
+     * Resolves all placeholders in the supplied string with values from a {@link Properties} instance and transform any
+     * based on their modifiers.
+     * 
+     * @param input the string to resolve
+     * @param props the properties to use for resolution
+     * @param transformer a transformer for handling property modifiers
+     * @return the resolved string
+     */
+    public String resolve(String input, Properties props, PlaceholderValueTransformer transformer) {
+        String key = UUID.randomUUID().toString();
+        props.put(key, input);
+        String value = resolveProperty(key, props, transformer);
+        props.remove(key);
+        return value;
+    }
+
+    private String resolveProperty(String name, Properties props, PlaceholderValueTransformer transformer) {
+        Stack<String> visitState = new Stack<String>();
+        return resolve(name, props, transformer, visitState);
+    }
+
+    private String resolve(String name, Properties props, PlaceholderValueTransformer transformer, Stack<String> visitState) {
+        visitState.push(name);
+
+        String initialValue = props.getProperty(name);
+        if(initialValue == null) {
+            throw new RuntimeException("No value found for placeholder '" + name + "'");
+        }
+
+        Matcher matcher = PATTERN.matcher(initialValue);
+
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            String propName = matcher.group(1);
+            if (visitState.contains(propName)) {
+                throw new IllegalArgumentException(formatPropertyCycleMessage(visitState));
+            }
+
+            String value = resolve(propName, props, transformer, visitState);
+            if (matcher.group(2).length() > 0) {
+                value = transformer.transform(propName, value, matcher.group(2));
+            }
+            matcher.appendReplacement(sb, escapeBackslashes(value));
+        }
+        matcher.appendTail(sb);
+
+        visitState.pop();
+        return sb.toString();
+    }
+
+    private static String escapeBackslashes(String string) {
+        StringBuffer sb = new StringBuffer(string.length());
+        int bsIndex = string.indexOf("\\");
+        int pos = 0;
+        while (bsIndex != -1) {
+            sb.append(string.substring(pos,bsIndex+1));
+            sb.append("\\"); // another backslash
+            pos = bsIndex+1;
+            bsIndex = string.indexOf("\\",pos);
+        }
+        sb.append(string.substring(pos, string.length()));
+        return new String(sb);
+    }
+    
+    private String formatPropertyCycleMessage(Stack<String> visitState) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Circular reference in property definitions: ");
+        for (String name : visitState) {
+            sb.append(name).append(" -> ");
+        }
+
+        sb.append(visitState.iterator().next());
+        return sb.toString();
+    }
+
+    /**
+     * An interface for property placeholder modifiers. Implementations of this interface are called when a property
+     * placeholder modifier is detected on a class.
+     * <p />
+     * 
+     * <strong>Concurrent Semantics</strong><br />
+     * 
+     * Implementations must be threadsafe.
+     * 
+     */
+    public static interface PlaceholderValueTransformer {
+
+        /**
+         * Transforms a property from its initial value to some other value
+         * 
+         * @param propertyName the name of the property being transformed
+         * @param propertyValue the original value of the property
+         * @param modifier the modifer string attached to the placeholder
+         * @return A string that has been modified by this transformer and to be used in place of the original value
+         */
+        String transform(String propertyName, String propertyValue, String modifier);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/ServiceLoader.java b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/ServiceLoader.java
new file mode 100644
index 0000000..5a65927
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/java/org/eclipse/virgo/test/launcher/ServiceLoader.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.osgi.framework.launch.FrameworkFactory;
+
+/**
+ * Simple service loader that follows the same location convention as the Java 6 <a
+ * href="http://java.sun.com/javase/6/docs/api/java/util/ServiceLoader.html">ServiceLoader</a>. This implementation is
+ * designed to run on on Java 5.
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Thread-safe.
+ * @param <T> service type
+ */
+public final class ServiceLoader<T> {
+
+    private static final String CONFIG_FILE_PREFIX = "META-INF/services/";
+
+    private final Class<T> serviceClass;
+
+    private ServiceLoader(Class<T> serviceClass) {
+        this.serviceClass = serviceClass;
+    }
+
+    public static <T> ServiceLoader<T> load(Class<T> serviceClass) {
+        return new ServiceLoader<T>(serviceClass);
+    }
+
+    /**
+     * Gets all known implementations of the service interface. Eqivalent to calling {@link #get(ClassLoader)} with
+     * {@link ClassLoader#getSystemClassLoader()}.
+     * @return set of implementation types
+     * 
+     * @see #get(ClassLoader)
+     */
+    public Set<T> get() {
+        return get(ClassLoader.getSystemClassLoader());
+    }
+
+    /**
+     * Gets all known implementations configured and visible in the supplied {@link ClassLoader}.
+     * @param classLoader from which to look
+     * @return set of implementation types
+     */
+    public Set<T> get(ClassLoader classLoader) {
+        Set<T> results = new HashSet<T>();
+        try {
+            Enumeration<URL> serviceFiles = findServiceFiles(classLoader);
+            Set<Class<?>> implTypes = new HashSet<Class<?>>();
+            while (serviceFiles.hasMoreElements()) {
+                URL url = (URL) serviceFiles.nextElement();
+                String implName = readImplementationClassName(url);
+                Class<?> cl = loadImplType(classLoader, implName);
+                if (implTypes.add(cl)) {
+                    results.add(createInstance(cl));
+                }
+            }
+        } catch (IOException e) {
+            throw new ServiceLoaderError("Unable to read config locations", e);
+        }
+        return results;
+    }
+
+    @SuppressWarnings("unchecked")
+    private T createInstance(Class<?> cl) {
+        try {
+            return (T) cl.newInstance();
+        } catch (Exception e) {
+            throw new ServiceLoaderError("Unable to instantiate service provider class '" + cl.getName() + "'", e);
+        }
+    }
+
+    private Class<?> loadImplType(ClassLoader classLoader, String implName) {
+        try {
+            return classLoader.loadClass(implName);
+        } catch (ClassNotFoundException e) {
+            throw new ServiceLoaderError("Unable to load service class '" + implName + "' from '" + classLoader + "'", e);
+        }
+    }
+
+    private Enumeration<URL> findServiceFiles(ClassLoader classLoader) throws IOException {
+        String concreteConfigName = CONFIG_FILE_PREFIX + this.serviceClass.getName();
+        return classLoader.getResources(concreteConfigName);
+    }
+
+    private String readImplementationClassName(URL input) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(input.openStream()));
+        String name = null;
+        String line = null;
+        try {
+            while ((line = reader.readLine()) != null) {
+                String trimmed = line.trim();
+                if (trimmed.length() > 0) {
+                    String nonComment = readNonCommentContent(trimmed);
+                    if (nonComment.length() > 0) {
+                        name = nonComment;
+                    }
+                }
+            }
+        } finally {
+            reader.close();
+        }
+        return name;
+    }
+
+    private String readNonCommentContent(String trimmed) {
+        int index = trimmed.indexOf("#");
+        if (index > -1) {
+            return trimmed.substring(0, index);
+        } else {
+            return trimmed;
+        }
+    }
+
+    public static void main(String[] args) {
+        ServiceLoader<FrameworkFactory> factory = ServiceLoader.load(FrameworkFactory.class);
+        Set<FrameworkFactory> set = factory.get(FrameworkFactory.class.getClassLoader());
+        System.out.println(set);
+    }
+
+    public static final class ServiceLoaderError extends Error {
+
+        private static final long serialVersionUID = 6134843287168204658L;
+
+        public ServiceLoaderError() {
+            super();
+        }
+
+        public ServiceLoaderError(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public ServiceLoaderError(String message) {
+            super(message);
+        }
+
+        public ServiceLoaderError(Throwable cause) {
+            super(cause);
+        }
+
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/resources/.gitignore b/test/org.eclipse.virgo.test.launcher/src/main/resources/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/resources/.gitignore
diff --git a/test/org.eclipse.virgo.test.launcher/src/main/resources/about.html b/test/org.eclipse.virgo.test.launcher/src/main/resources/about.html
new file mode 100644
index 0000000..c258ef5
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/main/resources/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>June 5, 2006</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise 
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was 
+provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkBuilderTests.java b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkBuilderTests.java
new file mode 100644
index 0000000..1d9c9a1
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkBuilderTests.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.virgo.test.launcher.FrameworkBuilder;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+/**
+ */
+public class FrameworkBuilderTests {
+
+    @Test
+    public void testSimpleStart() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+            assertEquals(0, framework.getBundleId());
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testStartWithProperties() throws BundleException {
+        String name = "Rob Harrop";
+        String age = "26";
+        String ageOverride = "27";
+
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        frameworkProperties.setProperty("name", name);
+        frameworkProperties.setProperty("age", age);
+
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        builder.addFrameworkProperty("age", ageOverride);
+
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+
+            BundleContext bundleContext = framework.getBundleContext();
+
+            assertNotNull(bundleContext);
+
+            assertEquals(name, bundleContext.getProperty("name"));
+            assertEquals(ageOverride, bundleContext.getProperty("age"));
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testStartWithBundle() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        builder.addFrameworkProperty("osgi.clean", "true");
+        builder.addBundle(new File("src/test/resources/test-bundle"));
+
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+
+            BundleContext bundleContext = framework.getBundleContext();
+
+            assertNotNull(bundleContext);
+
+            Bundle[] bundles = bundleContext.getBundles();
+
+            assertEquals(2, bundles.length);
+
+            Bundle testBundle = bundles[1];
+            assertEquals("test.bundle", testBundle.getSymbolicName());
+            assertEquals(Bundle.INSTALLED, testBundle.getState());
+
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testStartWithBundleAutostart() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        builder.addFrameworkProperty("osgi.clean", "true");
+        builder.addBundle(new File("src/test/resources/test-bundle"), true);
+
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+
+            BundleContext bundleContext = framework.getBundleContext();
+
+            assertNotNull(bundleContext);
+
+            Bundle[] bundles = bundleContext.getBundles();
+
+            assertEquals(2, bundles.length);
+
+            Bundle testBundle = bundles[1];
+            assertEquals("test.bundle", testBundle.getSymbolicName());
+            assertEquals(Bundle.ACTIVE, testBundle.getState());
+
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testStartWithLauncherBundles() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        frameworkProperties.put("launcher.bundles", "src/test/resources/test-bundle@start");
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        builder.addFrameworkProperty("osgi.clean", "true");
+
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+
+            BundleContext bundleContext = framework.getBundleContext();
+
+            assertNotNull(bundleContext);
+
+            Bundle[] bundles = bundleContext.getBundles();
+
+            assertEquals(2, bundles.length);
+
+            Bundle testBundle = bundles[1];
+            assertEquals("test.bundle", testBundle.getSymbolicName());
+            assertEquals(Bundle.ACTIVE, testBundle.getState());
+
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testStartWithBundleDeclaration() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties);
+        builder.addBundle("src/test/resources/test-bundle@start");
+        builder.addFrameworkProperty("osgi.clean", "true");
+
+        Framework framework = builder.start();
+
+        try {
+            assertNotNull(framework);
+
+            BundleContext bundleContext = framework.getBundleContext();
+
+            assertNotNull(bundleContext);
+
+            Bundle[] bundles = bundleContext.getBundles();
+
+            assertEquals(2, bundles.length);
+
+            Bundle testBundle = bundles[1];
+            assertEquals("test.bundle", testBundle.getSymbolicName());
+            assertEquals(Bundle.ACTIVE, testBundle.getState());
+
+        } finally {
+            framework.stop();
+        }
+    }
+
+    @Test
+    public void testCustomizer() throws BundleException {
+        Properties frameworkProperties = basicFrameworkProperties();
+
+        final AtomicInteger calls = new AtomicInteger();
+
+        FrameworkBuilder builder = new FrameworkBuilder(frameworkProperties, new FrameworkBuilder.FrameworkCustomizer() {
+
+            public void beforeInstallBundles(Framework framework) {
+                assertEquals(1, framework.getBundleContext().getBundles().length);
+                calls.incrementAndGet();
+            }
+
+            public void afterInstallBundles(Framework framework) {
+                assertEquals(2, framework.getBundleContext().getBundles().length);
+                calls.incrementAndGet();
+            }
+        });
+        builder.addBundle("src/test/resources/test-bundle@start");
+        builder.addFrameworkProperty("osgi.clean", "true");
+
+        Framework framework = builder.start();
+        assertEquals(2, calls.get());
+
+        framework.stop();
+
+    }
+    
+    private static Properties basicFrameworkProperties() {
+        Properties fp = new Properties();
+        fp.put("osgi.configuration.area", "build");
+        return fp;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocatorTests.java b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocatorTests.java
new file mode 100644
index 0000000..9db0852
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/FrameworkFactoryLocatorTests.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.virgo.test.launcher.FrameworkFactoryLocator;
+import org.junit.Test;
+import org.osgi.framework.launch.FrameworkFactory;
+
+
+public class FrameworkFactoryLocatorTests {
+
+    @Test
+    public void testLocateFrameworkFactory() {
+        FrameworkFactory frameworkFactory = FrameworkFactoryLocator.createFrameworkFactory();
+        assertNotNull(frameworkFactory);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/LauncherTests.java b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/LauncherTests.java
new file mode 100644
index 0000000..5a8d13b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/LauncherTests.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.virgo.test.launcher.Launcher;
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+
+
+/**
+ */
+public class LauncherTests {
+    
+    private static final String SYSTEM_PROPERTY_TMPDIR = "java.io.tmpdir";
+    private static final String TMPDIR = "tmp";
+    
+    private static final File TMP_DIR = new File(TMPDIR);
+    
+    @Before
+    public void setSystemProperty() {
+        System.setProperty(SYSTEM_PROPERTY_TMPDIR, TMPDIR);
+    }
+
+    @After
+    public void deleteTmpDir() {
+        if (TMP_DIR.exists()) {
+            assumeTrue(TMP_DIR.delete());
+        }
+    }
+    
+    @Test
+    public void creationOfTmpDir() throws IOException {
+        assumeTrue(!TMP_DIR.exists());
+        // assertFalse(TMP_DIR.exists());
+        Launcher.ensureTmpDirExists();
+        assertTrue(TMP_DIR.exists());
+    }
+    
+    @Test
+    public void noFailureIfTmpDirAlreadyExists() throws IOException {
+        TMP_DIR.mkdirs();
+        assumeTrue(TMP_DIR.exists());
+        // assertTrue(TMP_DIR.exists());
+        Launcher.ensureTmpDirExists();
+        assertTrue(TMP_DIR.exists());
+    }
+    
+    @Test(expected=IOException.class)
+    public void failureIfTmpDirCannotBeCreated() throws IOException {
+        assumeTrue(TMP_DIR.createNewFile());
+        // assertTrue(TMP_DIR.createNewFile());
+        Launcher.ensureTmpDirExists();       
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolverTests.java b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolverTests.java
new file mode 100644
index 0000000..3cc66a1
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/java/org/eclipse/virgo/test/launcher/PropertyPlaceholderResolverTests.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.launcher;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.eclipse.virgo.test.launcher.PropertyPlaceholderResolver;
+import org.junit.Test;
+
+
+public class PropertyPlaceholderResolverTests {
+
+    private final PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver();
+    
+    @Test
+    public void testSimpleResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "bar");
+        p.setProperty("other", "Hello ${foo}");
+        
+        p = resolver.resolve(p);
+        
+        assertEquals("Hello bar", p.getProperty("other"));
+    }
+    
+    @Test
+    public void testDoubleResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "bar");
+        p.setProperty("bar", "baz");
+        p.setProperty("other", "Hello ${foo} and ${bar}");
+        
+        p = resolver.resolve(p);
+        
+        assertEquals("Hello bar and baz", p.getProperty("other"));
+    }
+    
+    @Test
+    public void testChainResolve() {
+        Properties p = new Properties();
+        p.setProperty("config.dir", "${server.home}/config");
+        p.setProperty("server.home", "/opt/dms");
+        p.setProperty("repo.config", "${config.dir}/repo.config");
+        
+        p = resolver.resolve(p);
+        
+        assertEquals("/opt/dms/config", p.getProperty("config.dir"));
+        assertEquals("/opt/dms/config/repo.config", p.getProperty("repo.config"));
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testCircularResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "${bar}");
+        p.setProperty("bar", "${foo}");
+        
+        p = resolver.resolve(p);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testTightCircularResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "${foo}");
+        
+        p = resolver.resolve(p);
+    }
+    
+    @Test
+    public void testBackslashInValuesResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "a\\b\\d\\");
+        p.setProperty("bar", "[${foo}]");
+        p.setProperty("vartochange", "here is ${foo} and ${bar} aha!");
+
+        p = resolver.resolve(p);
+
+        assertEquals("Backslashes not substituted properly", "here is a\\b\\d\\ and [a\\b\\d\\] aha!", p.getProperty("vartochange"));
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testLongerCircularResolve() {
+        Properties p = new Properties();
+        p.setProperty("foo", "${bar}");
+        p.setProperty("bar", "${baz}");
+        p.setProperty("baz", "${foo}");
+        
+        p = resolver.resolve(p);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/resources/config.properties b/test/org.eclipse.virgo.test.launcher/src/test/resources/config.properties
new file mode 100644
index 0000000..c9f0304
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/resources/config.properties
@@ -0,0 +1 @@
+foo=bar
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.launcher/src/test/resources/test-bundle/META-INF/MANIFEST.MF b/test/org.eclipse.virgo.test.launcher/src/test/resources/test-bundle/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..49b0fa5
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/src/test/resources/test-bundle/META-INF/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: test.bundle
+Bundle-Vendor: SpringSource Inc.
diff --git a/test/org.eclipse.virgo.test.launcher/template.mf b/test/org.eclipse.virgo.test.launcher/template.mf
new file mode 100644
index 0000000..e2cc013
--- /dev/null
+++ b/test/org.eclipse.virgo.test.launcher/template.mf
@@ -0,0 +1,7 @@
+Bundle-ManifestVersion: 2
+Bundle-Name: Equinox Specific OSGi Extensions
+Bundle-SymbolicName: org.eclipse.virgo.test.launcher
+Bundle-Version: ${version}
+Main-Class: org.eclipse.virgo.test.launcher.Launcher
+Import-Template: 
+ org.osgi.framework.*;version="0"
diff --git a/test/org.eclipse.virgo.test.stubs/.springBeans b/test/org.eclipse.virgo.test.stubs/.springBeans
new file mode 100644
index 0000000..5152991
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/.springBeans
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beansProjectDescription>
+	<version>1</version>
+	<pluginVersion><![CDATA[2.2.5.M1]]></pluginVersion>
+	<configSuffixes>
+		<configSuffix><![CDATA[xml]]></configSuffix>
+	</configSuffixes>
+	<enableImports><![CDATA[false]]></enableImports>
+	<configs>
+	</configs>
+	<configSets>
+	</configSets>
+</beansProjectDescription>
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/FindEntriesDelegate.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/FindEntriesDelegate.java
new file mode 100644
index 0000000..3e36053
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/FindEntriesDelegate.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * An interface to allow delegation of calls to {@link Bundle#findEntries(String, String, boolean)} to be serviced in a
+ * test environment.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Implementations must be threadsafe
+ * 
+ */
+public interface FindEntriesDelegate {
+
+    /**
+     * @param path The root to search from
+     * @param filePattern The pattern to search for
+     * @param recurse Whether to recurse
+     * @return An enumeration of the files found
+     * @see Bundle#findEntries(String, String, boolean)
+     */
+    Enumeration<URL> findEntries(String path, String filePattern, boolean recurse);
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/OSGiAssert.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/OSGiAssert.java
new file mode 100644
index 0000000..f71152a
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/OSGiAssert.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.junit.Assert.assertEquals;
+
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A set of assertion methods for testing the state of the Stub OSGi Framework classes. This class makes its assertions
+ * using JUnit4 assertion classes.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class OSGiAssert {
+
+    /**
+     * Asserts that this {@link StubBundleContext} in is a clean state. A clean consists of the following
+     * 
+     * <ul>
+     * <li>0 {@link BundleListener}s</li>
+     * <li>0 {@link FrameworkListener}s</li>
+     * <li>0 {@link ServiceListener}s</li>
+     * <li>0 {@link ServiceRegistration}s</li>
+     * </ul>
+     * 
+     * @param bundleContext The context to assert against
+     * @see #assertBundleListenerCount(StubBundleContext, int)
+     * @see #assertFrameworkListenerCount(StubBundleContext, int)
+     * @see #assertServiceListenerCount(StubBundleContext, int)
+     * @see #assertServiceRegistrationCount(StubBundleContext, int)
+     */
+    public static void assertCleanState(StubBundleContext bundleContext) {
+        assertBundleListenerCount(bundleContext, 0);
+        assertFrameworkListenerCount(bundleContext, 0);
+        assertServiceListenerCount(bundleContext, 0);
+        assertServiceRegistrationCount(bundleContext, 0);
+    }
+
+    /**
+     * Asserts that a number of {@link BundleListener}s are currently registered
+     * 
+     * @param bundleContext The context to assert against
+     * @param count The number of listeners to assert
+     */
+    public static void assertBundleListenerCount(StubBundleContext bundleContext, int count) {
+        assertEquals("Invalid number of BundleListeners", count, bundleContext.getBundleListeners().size());
+    }
+
+    /**
+     * Asserts that a number of {@link FrameworkListener}s are currently registered
+     * 
+     * @param bundleContext The context to assert against
+     * @param count The number of listeners to assert
+     */
+    public static void assertFrameworkListenerCount(StubBundleContext bundleContext, int count) {
+        assertEquals("Invalid number of FrameworkListeners", count, bundleContext.getFrameworkListeners().size());
+    }
+
+    /**
+     * Asserts that a number of {@link ServiceListener}s are currently registered
+     * 
+     * @param bundleContext The context to assert against
+     * @param count The number of listeners to assert
+     */
+    public static void assertServiceListenerCount(StubBundleContext bundleContext, int count) {
+        assertEquals("Invalid number of ServiceListeners", count, bundleContext.getServiceListeners().size());
+    }
+
+    /**
+     * Asserts that a number of {@link ServiceRegistration}s of are currently registered
+     * 
+     * @param bundleContext The context to assert against
+     * @param count The number of registered services to assert
+     */
+    public static void assertServiceRegistrationCount(StubBundleContext bundleContext, int count) {
+        assertEquals("Invalid number of ServiceRegistrations", count, bundleContext.getServiceRegistrations().size());
+    }
+
+    /**
+     * Asserts that a number of {@link ServiceRegistration}s of are currently registered
+     * 
+     * @param bundleContext The context to assert against
+     * @param type The {@link Constants#OBJECTCLASS} of the services to assert
+     * @param count The number of registered services to assert
+     */
+    public static void assertServiceRegistrationCount(StubBundleContext bundleContext, Class<?> type, int count) {
+        String typeName = type.getName();
+        int found = 0;
+        for (ServiceRegistration<?> serviceRegistration : bundleContext.getServiceRegistrations()) {
+            String[] objectClasses = (String[]) serviceRegistration.getReference().getProperty(Constants.OBJECTCLASS);
+            for (String objectClass : objectClasses) {
+                if (typeName.equals(objectClass)) {
+                    found++;
+                    break;
+                }
+            }
+        }
+        assertEquals(String.format("Invalid number of ServiceRegistrations of type %s", typeName), count, found);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundle.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundle.java
new file mode 100644
index 0000000..e677321
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundle.java
@@ -0,0 +1,741 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+import static org.eclipse.virgo.test.stubs.internal.Duplicator.shallowCopy;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+/**
+ * A stub testing implementation of {@link Bundle} as defined in section 6.1.4 of the OSGi Service Platform Core
+ * Specification.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class StubBundle implements Bundle {
+
+    private static final Long DEFAULT_BUNDLE_ID = Long.valueOf(1);
+
+    private static final String DEFAULT_SYMBOLIC_NAME = "org.eclipse.virgo.test.stubs.testbundle";
+
+    private static final Version DEFAULT_VERSION = Version.emptyVersion;
+
+    private static final String DEFAULT_LOCATION = "/";
+
+    private final Long bundleId;
+
+    private final String symbolicName;
+
+    private final Version version;
+
+    private final String location;
+
+    private volatile int state;
+
+    private final Object stateMonitor = new Object();
+
+    private volatile long lastModified;
+
+    private final Object lastModifiedMonitor = new Object();
+
+    private volatile StubBundleContext bundleContext;
+
+    private final Object bundleContextMonitor = new Object();
+
+    private volatile Dictionary<String, String> headers = new Hashtable<String, String>();
+
+    private final Object headersMonitor = new Object();
+
+    private volatile Dictionary<String, String> localizedHeaders = new Hashtable<String, String>();
+
+    private final Object localizedHeadersMonitor = new Object();
+
+    private final Map<String, Class<?>> loadClasses = new HashMap<String, Class<?>>();
+
+    private final Object loadClassesMonitor = new Object();
+
+    private final Map<String, URL> entries = new HashMap<String, URL>();
+
+    private final Object entriesMonitor = new Object();
+
+    private final Map<String, Enumeration<String>> entryPaths = new HashMap<String, Enumeration<String>>();
+
+    private final Object entryPathsMonitor = new Object();
+
+    private final Map<Object, Boolean> permissions = new HashMap<Object, Boolean>();
+
+    private final Object permissionsMonitor = new Object();
+
+    private final Map<String, URL> resource = new HashMap<String, URL>();
+
+    private final Object resourceMonitor = new Object();
+
+    private final Map<String, Enumeration<URL>> resources = new HashMap<String, Enumeration<URL>>();
+
+    private final Object resourcesMonitor = new Object();
+
+    private volatile FindEntriesDelegate findEntriesDelegate;
+
+    private final Object findEntriesMonitor = new Object();
+
+    private final List<StubServiceReference<Object>> registeredServices = new ArrayList<StubServiceReference<Object>>();
+
+    private final Object registeredServicesMonitor = new Object();
+
+    private final List<StubServiceReference<Object>> servicesInUse = new ArrayList<StubServiceReference<Object>>();
+
+    private final Object servicesInUseMonitor = new Object();
+
+    private volatile UpdateDelegate updateDelegate;
+
+    private final Object updateDelegateMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubServiceRegistration} and sets its initial state. This constructor sets
+     * <code>bundleId</code> to <code>1</code>, <code>symbolicName</code> to
+     * <code>org.eclipse.virgo.test.stubs.testbundle</code>, <code>version</code> to <code>0.0.0</code>, and
+     * <code>location</code> to <code>/</code>.
+     */
+    public StubBundle() {
+        this(DEFAULT_SYMBOLIC_NAME, DEFAULT_VERSION);
+    }
+
+    /**
+     * Creates a new {@link StubServiceRegistration} and sets its initial state. This constructor sets
+     * <code>bundleId</code> to <code>1</code> and <code>location</code> to <code>/</code>.
+     * 
+     * @param symbolicName The symbolic name of this bundle
+     * @param version The version of this bundle
+     */
+    public StubBundle(String symbolicName, Version version) {
+        this(DEFAULT_BUNDLE_ID, symbolicName, version, DEFAULT_LOCATION);
+    }
+
+    /**
+     * Creates a new {@link StubBundle} and sets its initial state
+     * 
+     * @param bundleId The id of this bundle
+     * @param symbolicName The symbolic name of this bundle
+     * @param version The version of this bundle
+     * @param location The location of this bundle
+     */
+    public StubBundle(Long bundleId, String symbolicName, Version version, String location) {
+        assertNotNull(bundleId, "bundleId");
+        assertNotNull(symbolicName, "symbolicName");
+        assertNotNull(version, "version");
+        assertNotNull(location, "location");
+
+        this.bundleId = bundleId;
+        this.symbolicName = symbolicName;
+        this.version = version;
+        this.location = location;
+        this.bundleContext = new StubBundleContext(this);
+        this.state = STARTING;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+        synchronized (this.findEntriesMonitor) {
+            if (this.findEntriesDelegate != null) {
+                return this.findEntriesDelegate.findEntries(path, filePattern, recurse);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Sets the {@link FindEntriesDelegate} to use for all subsequent calls to
+     * {@link #findEntries(String, String, boolean)}.
+     * 
+     * @param delegate the delegate to use
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setFindEntriesDelegate(FindEntriesDelegate delegate) {
+        synchronized (this.findEntriesMonitor) {
+            this.findEntriesDelegate = delegate;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public BundleContext getBundleContext() {
+        synchronized (this.bundleContextMonitor) {
+            return this.bundleContext;
+        }
+    }
+
+    /**
+     * Sets the {@link BundleContext} to return for all subsequent calls to {@link #getBundleContext()}.
+     * 
+     * @param bundleContext The @{link BundleContext} to return
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setBundleContext(StubBundleContext bundleContext) {
+        assertNotNull(bundleContext, "bundleContext");
+        synchronized (bundleContextMonitor) {
+            this.bundleContext = bundleContext;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getBundleId() {
+        return this.bundleId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public URL getEntry(String path) {
+        synchronized (this.entriesMonitor) {
+            return this.entries.get(path);
+        }
+    }
+
+    /**
+     * Adds a mapping from a path to a {@link URL} for all subsequent calls to {@link #getEntry(String)}.
+     * 
+     * @param path The path to map from
+     * @param url The {@link URL} to map to
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addEntry(String path, URL url) {
+        synchronized (this.entriesMonitor) {
+            this.entries.put(path, url);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration<String> getEntryPaths(String path) {
+        synchronized (this.entryPathsMonitor) {
+            return this.entryPaths.get(path);
+        }
+    }
+
+    /**
+     * Adds a mapping from a path to a {@link Enumeration} for all subsequent calls to {@link #getEntryPaths(String)}.
+     * 
+     * @param path The path to map from
+     * @param paths The {@link Enumeration} to map to
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addEntryPaths(String path, Enumeration<String> paths) {
+        synchronized (this.entryPathsMonitor) {
+            this.entryPaths.put(path, paths);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary<String, String> getHeaders() {
+        synchronized (this.headersMonitor) {
+            return shallowCopy(this.headers);
+        }
+    }
+
+    /**
+     * Adds a header mapping for all subsequent calls to {@link #getHeaders()}.
+     * 
+     * @param key The key to map from
+     * @param value The value to map to
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addHeader(String key, String value) {
+        synchronized (this.headersMonitor) {
+            this.headers.put(key, value);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary<String, String> getHeaders(String locale) {
+        synchronized (this.localizedHeadersMonitor) {
+            return shallowCopy(this.localizedHeaders);
+        }
+    }
+
+    /**
+     * Sets the localized headers to return for all subsequent calls to {@link #getHeaders(String)}.
+     * 
+     * @param dictionary The headers to return
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setLocalizedHeaders(Dictionary<String, String> dictionary) {
+        synchronized (this.localizedHeadersMonitor) {
+            this.localizedHeaders = dictionary;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getLastModified() {
+        synchronized (this.lastModifiedMonitor) {
+            return this.lastModified;
+        }
+    }
+
+    /**
+     * Sets the last modified date to return for all subsequent calls to {@link #getLastModified()}. A call to any other
+     * modifying method will update this.
+     * 
+     * @param lastModified The new last modified date
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setLastModified(long lastModified) {
+        synchronized (this.lastModifiedMonitor) {
+            this.lastModified = lastModified;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLocation() {
+        return this.location;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<?>[] getRegisteredServices() {
+        synchronized (this.registeredServicesMonitor) {
+            if (this.registeredServices.isEmpty()) {
+                return null;
+            }
+            return this.registeredServices.toArray(new ServiceReference[this.registeredServices.size()]);
+        }
+    }
+
+    /**
+     * Adds a {@link ServiceReference} for all subsequent calls to {@link #getRegisteredServices()}.
+     * 
+     * @param serviceReference The {@link ServiceReference}
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addRegisteredService(StubServiceReference<Object> serviceReference) {
+        synchronized (this.registeredServicesMonitor) {
+            serviceReference.setBundle(this);
+            this.registeredServices.add(serviceReference);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public URL getResource(String name) {
+        synchronized (this.resourceMonitor) {
+            return this.resource.get(name);
+        }
+    }
+
+    /**
+     * Adds a mapping from a name to a {@link URL} for all subsequent calls to {@link #getResource(String)}.
+     * 
+     * @param name The name to map from
+     * @param url The {@link URL} to map to
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addResource(String name, URL url) {
+        synchronized (this.resourceMonitor) {
+            this.resource.put(name, url);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration<URL> getResources(String name) throws IOException {
+        synchronized (this.resourcesMonitor) {
+            return this.resources.get(name);
+        }
+    }
+
+    /**
+     * Adds a mapping from a name to a {@link Enumeration} for all subsequent calls to {@link #getResources(String)}.
+     * 
+     * @param name The name to map from
+     * @param resources The {@link Enumeration} to map to
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addResources(String name, Enumeration<URL> resources) {
+        synchronized (this.resourcesMonitor) {
+            this.resources.put(name, resources);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<?>[] getServicesInUse() {
+        synchronized (this.servicesInUseMonitor) {
+            if (this.servicesInUse.isEmpty()) {
+                return null;
+            }
+            return this.servicesInUse.toArray(new ServiceReference[this.servicesInUse.size()]);
+        }
+    }
+
+    /**
+     * Adds a {@link ServiceReference} for all subsequent calls to {@link #getServicesInUse()}.
+     * 
+     * @param serviceReference The {@link ServiceReference}
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addServiceInUse(StubServiceReference<Object> serviceReference) {
+        synchronized (this.servicesInUseMonitor) {
+            serviceReference.addUsingBundles(this);
+            this.servicesInUse.add(serviceReference);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getState() {
+        synchronized (this.stateMonitor) {
+            return this.state;
+        }
+    }
+
+    /**
+     * Sets the state to return for all subsequent calls to {@link #getState()}. A call to any state modifying method
+     * will change this value.
+     * 
+     * @param state The state to return
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setState(int state) {
+        synchronized (this.stateMonitor) {
+            this.state = state;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getSymbolicName() {
+        return this.symbolicName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Version getVersion() {
+        return this.version;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasPermission(Object permission) {
+        synchronized (this.permissionsMonitor) {
+            if (this.permissions.containsKey(permission)) {
+                return this.permissions.get(permission);
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Adds a mapping from a permission to a {@link Boolean} of whether that permission is valid for all subsequent
+     * calls to {@link #hasPermission(Object)}.
+     * 
+     * @param permission the permission to add
+     * @param hasPermission whether this permission is valid
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addPermission(Object permission, boolean hasPermission) {
+        synchronized (this.permissionsMonitor) {
+            this.permissions.put(permission, hasPermission);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        synchronized (this.loadClassesMonitor) {
+            if (this.loadClasses.containsKey(name)) {
+                return this.loadClasses.get(name);
+            }
+            throw new ClassNotFoundException("'" + name + "' cannot be loaded");
+        }
+    }
+
+    /**
+     * Adds a mapping from a class name to a {@link Class} for all subsequent calls to {@link #loadClass(String)}.
+     * 
+     * @param name The name of the class to map from
+     * @param clazz The class to map to
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle addLoadClass(String name, Class<?> clazz) {
+        synchronized (this.loadClassesMonitor) {
+            this.loadClasses.put(name, clazz);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void start() throws BundleException {
+        start(0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void start(int options) throws BundleException {
+        synchronized (this.stateMonitor) {
+            if (this.getState() == ACTIVE) {
+                return;
+            }
+            setState(RESOLVED);
+            setState(STARTING);
+            setState(ACTIVE);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stop() throws BundleException {
+        stop(0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stop(int options) throws BundleException {
+        synchronized (this.stateMonitor) {
+            if (this.getState() != ACTIVE) {
+                return;
+            }
+
+            setState(STOPPING);
+            synchronized (this.registeredServicesMonitor) {
+                for (StubServiceReference<Object> serviceReference : this.registeredServices) {
+                    serviceReference.setBundle(null);
+                }
+                this.registeredServices.clear();
+            }
+
+            synchronized (this.servicesInUseMonitor) {
+                for (StubServiceReference<Object> serviceReference : this.servicesInUse) {
+                    serviceReference.removeUsingBundles(this);
+                }
+                this.servicesInUse.clear();
+            }
+
+            setState(RESOLVED);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void uninstall() throws BundleException {
+        synchronized (this.stateMonitor) {
+            int initialState = getState();
+            stopBundleIfNeeded(initialState);
+            setState(UNINSTALLED);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void update() throws BundleException {
+        synchronized (this.updateDelegateMonitor) {
+            synchronized (this.stateMonitor) {
+                int initialState = getState();
+                stopBundleIfNeeded(initialState);
+
+                if (this.updateDelegate != null) {
+                    this.updateDelegate.update(this);
+                }
+
+                setState(INSTALLED);
+                startBundleIfNeeded(initialState);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void update(InputStream in) throws BundleException {
+        update();
+    }
+
+    /**
+     * Sets the {@link UpdateDelegate} to use for all subsequent calls to {@link #update()} or
+     * {@link #update(InputStream)}.
+     * 
+     * @param delegate the delegate to use
+     * 
+     * @return <code>this</code> instance of the {@link StubBundle}
+     */
+    public StubBundle setUpdateDelegate(UpdateDelegate delegate) {
+        synchronized (this.updateDelegateMonitor) {
+            this.updateDelegate = delegate;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + bundleId.hashCode();
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        StubBundle other = (StubBundle) obj;
+        if (!bundleId.equals(other.bundleId)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("id: %d, symbolic name: %s, version: %s, state: %d", this.bundleId, this.symbolicName, this.version, this.state);
+    }
+
+    private void startBundleIfNeeded(int initialState) throws BundleException {
+        if (initialState == ACTIVE) {
+            this.start();
+        }
+    }
+
+    private void stopBundleIfNeeded(int initialState) throws BundleException {
+        if (initialState == Bundle.ACTIVE || initialState == Bundle.STARTING || initialState == Bundle.STOPPING) {
+            this.stop();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(Bundle o) {
+        int bundleIdCompare = Long.valueOf(o.getBundleId()).compareTo(this.bundleId);
+        if (bundleIdCompare != 0) {
+            return bundleIdCompare;
+        }
+        int symbolicNameCompare = o.getSymbolicName().compareTo(this.symbolicName);
+        if (symbolicNameCompare != 0) {
+            return symbolicNameCompare;
+        }
+        int bundleVersionCompare = o.getVersion().compareTo(this.version);
+        if (bundleVersionCompare != 0) {
+            return bundleVersionCompare;
+        }
+        int bundleLocationCompare = o.getLocation().compareTo(this.location);
+        if (bundleLocationCompare != 0) {
+            return bundleLocationCompare;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+        return new HashMap<X509Certificate, List<X509Certificate>>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <A> A adapt(Class<A> type) {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getDataFile(String filename) {
+        return null;
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundleContext.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundleContext.java
new file mode 100644
index 0000000..237e0c9
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubBundleContext.java
@@ -0,0 +1,563 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+import static org.eclipse.virgo.test.stubs.internal.Duplicator.shallowCopy;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
+
+import org.eclipse.virgo.test.stubs.support.TrueFilter;
+
+/**
+ * A stub testing implementation of {@link BundleContext} as defined in section 6.1.6 of the OSGi Service Platform Core
+ * Specification.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class StubBundleContext implements BundleContext {
+
+    private final StubBundle bundle;
+
+    private final Object bundleMonitor = new Object();
+
+    private final List<BundleListener> bundleListeners = new ArrayList<BundleListener>();
+
+    private final Object bundleListenersMonitor = new Object();
+
+    private final List<FrameworkListener> frameworkListeners = new ArrayList<FrameworkListener>();
+
+    private final Object frameworkListenersMonitor = new Object();
+
+    private final List<ServiceListener> serviceListeners = new ArrayList<ServiceListener>();
+
+    private final Object serviceListenersMonitor = new Object();
+
+    private volatile long installedBundleId = Long.valueOf(2);
+
+    private final Map<Long, StubBundle> installedBundles = new HashMap<Long, StubBundle>();
+
+    private final Object installedBundlesMonitor = new Object();
+
+    private final List<StubServiceRegistration<Object>> serviceRegistrations = new ArrayList<StubServiceRegistration<Object>>();
+
+    private final Map<StubServiceReference<Object>, Object> services = new HashMap<StubServiceReference<Object>, Object>();
+
+    private final Object servicesMonitor = new Object();
+
+    private final Map<String, File> dataFiles = new HashMap<String, File>();
+
+    private final Object dataFilesMonitor = new Object();
+
+    private final Map<String, String> properties = new HashMap<String, String>();
+
+    private final Object propertiesMonitor = new Object();
+
+    private final Map<String, Filter> filters = new HashMap<String, Filter>();
+
+    private final Object filtersMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubBundleContext} and sets its initial state
+     */
+    public StubBundleContext() {
+        this(new StubBundle());
+    }
+
+    /**
+     * Creates a new {@link StubBundleContext} and sets its initial state
+     * 
+     * @param bundle The context bundle
+     */
+    public StubBundleContext(StubBundle bundle) {
+        assertNotNull(bundle, "bundle");
+        this.bundle = bundle;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addBundleListener(BundleListener listener) {
+        synchronized (this.bundleListenersMonitor) {
+            this.bundleListeners.add(listener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addFrameworkListener(FrameworkListener listener) {
+        synchronized (this.frameworkListenersMonitor) {
+            this.frameworkListeners.add(listener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addServiceListener(ServiceListener listener) {
+        try {
+            addServiceListener(listener, null);
+        } catch (InvalidSyntaxException e) {
+            // In theory this exception can never be thrown
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+        synchronized (this.serviceListenersMonitor) {
+            this.serviceListeners.add(listener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Filter createFilter(String filter) throws InvalidSyntaxException {
+        synchronized (this.filtersMonitor) {
+            if (filter == null) {
+                throw new NullPointerException();
+            }
+            if (!this.filters.containsKey(filter)) {
+                throw new InvalidSyntaxException(String.format(
+                    "You must first add a filter mapping for '%s' with the addFilter(String, Filter) method", filter), filter);
+            }
+            return this.filters.get(filter);
+        }
+    }
+
+    /**
+     * Adds a mapping from a filter string to a {@link Filter} for all subsequent calls to {@link #createFilter(String)}
+     * 
+     * @param filterString The filterString to map from
+     * @param filter The {@link Filter} to map to
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext addFilter(String filterString, Filter filter) {
+        synchronized (this.filtersMonitor) {
+            this.filters.put(filterString, filter);
+            return this;
+        }
+    }
+
+    /**
+     * Adds filters for all subsequent calls to {@link #createFilter(String)}
+     * 
+     * @param filters The {@link Filter}s to add
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext addFilter(Filter... filters) {
+        for (Filter filter : filters) {
+            addFilter(filter.toString(), filter);
+        }
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<?>[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+        return getServiceReferences(clazz, filter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle getBundle() {
+        synchronized (this.bundleMonitor) {
+            return this.bundle;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle getBundle(long id) {
+        synchronized (this.installedBundlesMonitor) {
+            return this.installedBundles.get(id);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle[] getBundles() {
+        synchronized (this.installedBundlesMonitor) {
+            return this.installedBundles.values().toArray(new Bundle[this.installedBundles.values().size()]);
+        }
+    }
+
+    /**
+     * Adds a collection of {@link Bundle}s to this {@link BundleContext} to be returned for all subsequent calls to
+     * {@link #getBundle(long)} or {@link #getBundles()}.
+     * 
+     * @param bundles The bundles to add
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext addInstalledBundle(StubBundle... bundles) {
+        synchronized (this.installedBundlesMonitor) {
+            for (StubBundle bundle : bundles) {
+                this.installedBundles.put(bundle.getBundleId(), bundle);
+            }
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getDataFile(String filename) {
+        synchronized (this.dataFilesMonitor) {
+            return this.dataFiles.get(filename);
+        }
+    }
+
+    /**
+     * Adds a mapping from a filename to a {@link File} for all subsequent calls to {@link #getDataFile(String)}.
+     * 
+     * @param filename The filename to map from
+     * @param file The {@link File} to map to
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext addDataFile(String filename, File file) {
+        synchronized (this.dataFilesMonitor) {
+            this.dataFiles.put(filename, file);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getProperty(String key) {
+        synchronized (this.propertiesMonitor) {
+            return this.properties.get(key);
+        }
+    }
+
+    /**
+     * Adds a mapping from a key to a value for all subsequent calls to {@link #getProperty(String)}.
+     * 
+     * @param key The key to map from
+     * @param value The value to map to
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext addProperty(String key, String value) {
+        synchronized (this.propertiesMonitor) {
+            this.properties.put(key, value);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public <S> S getService(ServiceReference<S> reference) {
+        synchronized (this.servicesMonitor) {
+            if (serviceUnregistered(reference)) {
+                return null;
+            }
+
+            return (S) this.services.get(reference);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<?> getServiceReference(String clazz) {
+        ServiceReference<?>[] serviceReferences = null;
+        try {
+            serviceReferences = getServiceReferences(clazz, null);
+        } catch (InvalidSyntaxException e) {
+            // In theory this exception can never be thrown
+        }
+
+        if (serviceReferences == null) {
+            return null;
+        } else if (serviceReferences.length == 1) {
+            return serviceReferences[0];
+        } else {
+            Arrays.sort(serviceReferences);
+            return serviceReferences[serviceReferences.length - 1];
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<?>[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+        synchronized (this.servicesMonitor) {
+            List<ServiceReference<?>> candidateReferences = new ArrayList<ServiceReference<?>>();
+
+            Filter f = getFilter(filter);
+            for (ServiceReference<?> serviceReference : this.services.keySet()) {
+                String[] objectClasses = (String[]) serviceReference.getProperty(Constants.OBJECTCLASS);
+                if (f.match(serviceReference) && matchesClass(clazz, objectClasses)) {
+                    candidateReferences.add(serviceReference);
+                }
+            }
+
+            if (candidateReferences.isEmpty()) {
+                return null;
+            } else {
+                return candidateReferences.toArray(new ServiceReference[candidateReferences.size()]);
+            }
+        }
+    }
+
+    private Filter getFilter(String filter) throws InvalidSyntaxException {
+        if (filter == null) {
+            return new TrueFilter();
+        } else {
+            return createFilter(filter);
+        }
+    }
+
+    private boolean matchesClass(String clazz, String[] objectClasses) {
+        if (clazz == null) {
+            return true;
+        }
+
+        for (String objectClass : objectClasses) {
+            if (clazz.equals(objectClass)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle installBundle(String location) throws BundleException {
+        StubBundle bundle = new StubBundle(this.installedBundleId++, location, Version.emptyVersion, location);
+        bundle.setState(Bundle.INSTALLED);
+        return bundle;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle installBundle(String location, InputStream input) throws BundleException {
+        return installBundle(location);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceRegistration<?> registerService(String[] clazzes, Object service, Dictionary<String, ?> properties) {
+        StubServiceRegistration<Object> serviceRegistration = createServiceRegistration(clazzes, properties);
+        StubServiceReference<Object> serviceReference = createServiceReference(clazzes, service, serviceRegistration);
+
+        synchronized (this.servicesMonitor) {
+            this.serviceRegistrations.add(serviceRegistration);
+            this.services.put(serviceReference, service);
+        }
+
+        return serviceRegistration;
+    }
+
+    private StubServiceRegistration<Object> createServiceRegistration(String[] clazzes, Dictionary<String, ?> properties) {
+        StubServiceRegistration<Object> serviceRegistration = new StubServiceRegistration<Object>(this, clazzes);
+        serviceRegistration.setProperties(properties);
+        return serviceRegistration;
+    }
+
+    private <S> StubServiceReference<S> createServiceReference(String[] clazzes, Object service, StubServiceRegistration<S> serviceRegistration) {
+        StubServiceReference<S> serviceReference = new StubServiceReference<S>(serviceRegistration);
+        serviceRegistration.setServiceReference(serviceReference);
+        return serviceReference;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceRegistration<?> registerService(String clazz, Object service, Dictionary<String, ?> properties) {
+        return registerService(new String[] { clazz }, service, properties);
+    }
+
+    /**
+     * Gets the collection of {@link ServiceRegistration}s for this {@link BundleContext}
+     * 
+     * @return The collection of {@link ServiceRegistration}s
+     */
+    public List<StubServiceRegistration<Object>> getServiceRegistrations() {
+        synchronized (this.servicesMonitor) {
+            return shallowCopy(this.serviceRegistrations);
+        }
+    }
+
+    /**
+     * Removes a collection of {@link ServiceRegistration}s from this {@link StubBundleContext} to be returned for all
+     * subsequent calls to {@link #getServiceReference(String)} and {@link #getServiceReferences(String, String)}.
+     * 
+     * @param serviceRegistrations The service registrations to remove
+     * @return <code>this</code> instance of the {@link StubBundleContext}
+     */
+    public StubBundleContext removeRegisteredService(ServiceRegistration<?>... serviceRegistrations) {
+        synchronized (this.servicesMonitor) {
+            this.serviceRegistrations.removeAll(Arrays.asList(serviceRegistrations));
+            for (ServiceRegistration<?> registration: serviceRegistrations) {
+                this.services.remove(registration.getReference());
+            }
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeBundleListener(BundleListener listener) {
+        synchronized (this.bundleListenersMonitor) {
+            this.bundleListeners.remove(listener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeFrameworkListener(FrameworkListener listener) {
+        synchronized (this.frameworkListenersMonitor) {
+            this.frameworkListeners.remove(listener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeServiceListener(ServiceListener listener) {
+        synchronized (this.serviceListenersMonitor) {
+            this.serviceListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Gets the collection of {@link FrameworkListener}s for this {@link BundleContext}
+     * 
+     * @return The collection of {@link BundleListener}s
+     */
+    public List<FrameworkListener> getFrameworkListeners() {
+        synchronized (this.frameworkListenersMonitor) {
+            return shallowCopy(this.frameworkListeners);
+        }
+    }
+
+    /**
+     * Gets the collection of {@link BundleListener}s for this {@link BundleContext}
+     * 
+     * @return The collection of {@link BundleListener}s
+     */
+    public List<BundleListener> getBundleListeners() {
+        synchronized (this.bundleListenersMonitor) {
+            return shallowCopy(this.bundleListeners);
+        }
+    }
+
+    /**
+     * Gets the collection of {@link ServiceListener}s for this {@link BundleContext}
+     * 
+     * @return The collection of {@link ServiceListener}s
+     */
+    public List<ServiceListener> getServiceListeners() {
+        synchronized (this.serviceListenersMonitor) {
+            return shallowCopy(this.serviceListeners);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean ungetService(ServiceReference<?> reference) {
+        return !serviceUnregistered(reference);
+    }
+
+    /**
+     * @return Returns the context bundle for this {@link BundleContext}
+     */
+    public StubBundle getContextBundle() {
+        synchronized (this.bundleMonitor) {
+            return this.bundle;
+        }
+    }
+
+    private boolean serviceUnregistered(ServiceReference<?> reference) {
+        return ((StubServiceReference<?>) reference).getServiceRegistration().getUnregistered();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public <S> ServiceRegistration<S> registerService(Class<S> clazz, S service, Dictionary<String, ?> properties) {
+        return (ServiceRegistration<S>) registerService(clazz.getName(), service, properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public <S> ServiceReference<S> getServiceReference(Class<S> clazz) {
+        return (ServiceReference<S>) getServiceReference(clazz.getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> clazz, String filter) throws InvalidSyntaxException {
+        Collection<ServiceReference<S>> references = new ArrayList<ServiceReference<S>>();
+        ServiceReference<S>[] matchingReferences = (ServiceReference<S>[]) getServiceReferences(clazz.getName(), filter);
+        if (matchingReferences != null) {
+            for (ServiceReference<S> reference : matchingReferences) {
+                references.add(reference);
+            }
+        }
+        return references;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle getBundle(String location) {
+        return null;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubFilter.java
new file mode 100644
index 0000000..b8f619f
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubFilter.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import org.osgi.framework.Filter;
+
+/**
+ * An extension to the {@link Filter} interface that requires an implementation to tell you what it's filter string
+ * would be.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Implementations must be threadsafe
+ * 
+ */
+public interface StubFilter extends Filter {
+
+    /**
+     * @return The filter string for this filter
+     */
+    String getFilterString();
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceReference.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceReference.java
new file mode 100644
index 0000000..6fc1887
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceReference.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A stub testing implementation of {@link ServiceReference} as defined in section 6.1.23 of the OSGi Service Platform Core
+ * Specification.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ * @param <S> type of ServiceReference
+ * 
+ */
+public final class StubServiceReference<S> implements ServiceReference<S> {
+
+    private static final Long DEFAULT_SERVICE_ID = Long.valueOf(1);
+
+    private static final Integer DEFAULT_SERVICE_RANKING = Integer.valueOf(0);
+
+    private final Long serviceId;
+
+    private final Integer serviceRanking;
+
+    private final StubServiceRegistration<S> serviceRegistration;
+
+    private volatile StubBundle bundle;
+
+    private final Object bundleMonitor = new Object();
+
+    private final List<StubBundle> usingBundles = new ArrayList<StubBundle>();
+
+    private final Object usingBundlesMonitor = new Object();
+
+    private final Map<Bundle, List<String>> assignableTo = new HashMap<Bundle, List<String>>();
+
+    private final Object assignableToMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubServiceRegistration} and sets its initial state. This constructor sets
+     * <code>service.id</code> to <code>1</code> and <code>service.ranking</code> to <code>0</code>.
+     * 
+     * @param serviceRegistration The service registration behind this {@link ServiceReference}
+     */
+    public StubServiceReference(StubServiceRegistration<S> serviceRegistration) {
+        this(DEFAULT_SERVICE_ID, DEFAULT_SERVICE_RANKING, serviceRegistration);
+    }
+
+    /**
+     * Creates a new {@link StubServiceRegistration} and sets its initial state
+     * 
+     * @param serviceId The service id to use
+     * @param serviceRanking The service ranking to use
+     * @param serviceRegistration The service registration behind this {@link ServiceReference}
+     */
+    public StubServiceReference(Long serviceId, Integer serviceRanking, StubServiceRegistration<S> serviceRegistration) {
+        assertNotNull(serviceId, "serviceId");
+        assertNotNull(serviceRanking, "serviceRanking");
+        assertNotNull(serviceRegistration, "serviceRegistration");
+
+        this.serviceId = serviceId;
+        this.serviceRanking = serviceRanking;
+        this.serviceRegistration = serviceRegistration;
+        this.serviceRegistration.setServiceReference(this);
+        this.bundle = serviceRegistration.getBundleContext().getContextBundle();
+    }
+
+    /**
+     * Gets this {@link ServiceReference}'s <code>service.id</code>
+     * 
+     * @return This {@link ServiceReference}'s <code>service.id</code>
+     */
+    public Long getServiceId() {
+        return this.serviceId;
+    }
+
+    /**
+     * Gets this {@link ServiceReference}'s <code>service.ranking</code>
+     * 
+     * @return This {@link ServiceReference}'s <code>service.ranking</code>
+     */
+    public Integer getServiceRanking() {
+        return this.serviceRanking;
+    }
+
+    /**
+     * Gets this {@link ServiceReference}'s {@link ServiceRegistration}
+     * 
+     * @return This {@link ServiceReference}'s {@link ServiceRegistration}
+     */
+    public StubServiceRegistration<S> getServiceRegistration() {
+        return this.serviceRegistration;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(Object reference) {
+        if (reference == null) {
+            throw new IllegalArgumentException("input cannot be null");
+        }
+
+        if (!(reference instanceof StubServiceReference<?>)) {
+            throw new IllegalArgumentException("input must be StubServiceReference");
+        }
+
+        StubServiceReference<?> other = (StubServiceReference<?>) reference;
+        int idComparison = serviceId.compareTo(other.serviceId);
+        int rankingComparison = serviceRanking.compareTo(other.serviceRanking);
+
+        if (serviceId.equals(other.serviceId)) {
+            return 0;
+        } else if (rankingComparison != 0) {
+            return rankingComparison;
+        } else {
+            return idComparison;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle getBundle() {
+        synchronized (this.bundleMonitor) {
+            return this.bundle;
+        }
+    }
+
+    /**
+     * Sets the {@link Bundle} to return for all subsequent calls to {@link #getBundle()}.
+     * 
+     * @param bundle The bundle to return
+     * 
+     * @return <code>this</code> instance of the {@link StubServiceReference}
+     */
+    public StubServiceReference<S> setBundle(StubBundle bundle) {
+        synchronized (this.bundleMonitor) {
+            this.bundle = bundle;
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object getProperty(String key) {
+        return this.serviceRegistration.getProperties().get(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getPropertyKeys() {
+        List<String> properties = new ArrayList<String>();
+        Enumeration<String> keys = this.serviceRegistration.getProperties().keys();
+        while (keys.hasMoreElements()) {
+            properties.add((String) keys.nextElement());
+        }
+
+        return properties.toArray(new String[properties.size()]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Bundle[] getUsingBundles() {
+        synchronized (this.usingBundlesMonitor) {
+            if (this.usingBundles.isEmpty()) {
+                return null;
+            }
+            return this.usingBundles.toArray(new Bundle[this.usingBundles.size()]);
+        }
+    }
+
+    /**
+     * Adds a collection of {@link Bundle}s to this {@link ServiceReference} to be returned for all subsequent calls to
+     * {@link #getUsingBundles()}.
+     * 
+     * @param bundles The bundles to add
+     * @return <code>this</code> instance of the {@link StubServiceReference}
+     */
+    public StubServiceReference<S> addUsingBundles(StubBundle... bundles) {
+        synchronized (this.usingBundlesMonitor) {
+            this.usingBundles.addAll(Arrays.asList(bundles));
+            return this;
+        }
+    }
+
+    /**
+     * Removes a collection of {@link Bundle}s from this {@link ServiceReference} to be returned for all subsequent
+     * calls to {@link #getUsingBundles()}.
+     * 
+     * @param bundles The bundles to remove
+     * @return <code>this</code> instance of the {@link StubServiceReference}
+     */
+    public StubServiceReference<S> removeUsingBundles(StubBundle... bundles) {
+        synchronized (this.usingBundlesMonitor) {
+            this.usingBundles.removeAll(Arrays.asList(bundles));
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isAssignableTo(Bundle bundle, String className) {
+        synchronized (this.assignableToMonitor) {
+            if (!this.assignableTo.containsKey(bundle)) {
+                return false;
+            }
+            return this.assignableTo.get(bundle).contains(className);
+        }
+    }
+
+    /**
+     * Adds a mapping from a {@link Bundle} to a collection of class names that the bundle is assignable to for all
+     * subsequent calls to {@link #isAssignableTo(Bundle, String)}.
+     * 
+     * @param bundle The bundle that the class names will be assignable to
+     * @param classNames The class names that this bundle is assignable from
+     * @return <code>this</code> instance of the {@link StubServiceReference}
+     */
+    public StubServiceReference<S> putAssignableTo(Bundle bundle, String... classNames) {
+        synchronized (this.assignableToMonitor) {
+            this.assignableTo.put(bundle, Arrays.asList(classNames));
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + serviceRegistration.hashCode();
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        StubServiceReference<?> other = (StubServiceReference<?>) obj;
+
+        if (!serviceRegistration.equals(other.serviceRegistration)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("id: %d, ranking: %d", this.serviceId, this.serviceRanking);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistration.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistration.java
new file mode 100644
index 0000000..7aa1af9
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistration.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+import static org.eclipse.virgo.test.stubs.internal.Duplicator.shallowCopy;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A stub testing implementation of {@link ServiceRegistration} as defined in section 6.1.24 of the OSGi Service Platform Core
+ * Specification.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ * @param <S> ServiceRegistration type
+ * 
+ */
+public final class StubServiceRegistration<S> implements ServiceRegistration<S> {
+
+    private volatile Dictionary<String, Object> properties = new Hashtable<String, Object>();
+
+    private final Object propertiesMonitor = new Object();
+
+    private volatile boolean unregistered = false;
+
+    private final Object unregisteredMonitor = new Object();
+
+    private final StubBundleContext bundleContext;
+
+    private final String[] objectClasses;
+
+    private volatile StubServiceReference<S> serviceReference;
+
+    private final Object serviceReferenceMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubServiceRegistration} and sets its initial state
+     * 
+     * @param bundleContext The bundle context that created this {@link ServiceRegistration}
+     * @param objectClasses The classes that this service is registered under
+     */
+    public StubServiceRegistration(StubBundleContext bundleContext, String... objectClasses) {
+        assertNotNull(bundleContext, "bundleContext");
+
+        this.bundleContext = bundleContext;
+        this.objectClasses = objectClasses;
+        this.serviceReference = new StubServiceReference<S>(this);
+        populateDefaultProperties(this.properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceReference<S> getReference() {
+        synchronized (this.serviceReferenceMonitor) {
+            return this.serviceReference;
+        }
+    }
+
+    /**
+     * Sets the service reference to return for all subsequent calls to {@link #getReference()}.
+     * 
+     * @param serviceReference The service reference to return
+     * 
+     * @return <code>this</code> instance of the {@link StubServiceRegistration}
+     */
+    public StubServiceRegistration<S> setServiceReference(StubServiceReference<S> serviceReference) {
+        assertNotNull(serviceReference, "serviceReference");
+        synchronized (this.serviceReferenceMonitor) {
+            this.serviceReference = serviceReference;
+
+            synchronized (this.propertiesMonitor) {
+                populateDefaultProperties(this.properties);
+            }
+
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public void setProperties(Dictionary<String, ?> properties) {
+        if (properties == null) {
+            return;
+        }
+
+        synchronized (this.propertiesMonitor) {
+            this.properties = (Dictionary<String, Object>) properties;
+            populateDefaultProperties(this.properties);
+        }
+    }
+
+    /**
+     * Gets the properties that were last set with a call to {@link #setProperties(Dictionary)}.
+     * 
+     * @return The properties last passed in with a call to {@link #setProperties(Dictionary)} or <code>null</code> if
+     *         {@link #setProperties(Dictionary)} has not been called
+     */
+    public Dictionary<String, Object> getProperties() {
+        synchronized (this.propertiesMonitor) {
+            return shallowCopy(this.properties);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unregister() {
+        synchronized (this.unregisteredMonitor) {
+            this.bundleContext.removeRegisteredService(this);
+            this.unregistered = true;
+
+            synchronized (this.serviceReferenceMonitor) {
+                this.serviceReference.setBundle(null);
+            }
+        }
+    }
+
+    /**
+     * Gets whether this {@link ServiceRegistration} has been unregistered with a call to {@link #unregister()}.
+     * 
+     * @return Whether or not this {@link StubServiceRegistration} has been unregistered
+     */
+    public boolean getUnregistered() {
+        synchronized (this.unregisteredMonitor) {
+            return this.unregistered;
+        }
+    }
+
+    /**
+     * Gets the {@link BundleContext} that created this registration
+     * 
+     * @return The {@link BundleContext} that created this registration
+     */
+    public StubBundleContext getBundleContext() {
+        return this.bundleContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("object classes: %s, unregistered: %b, properties: %s", Arrays.toString(this.objectClasses), this.unregistered,
+            this.properties);
+    }
+
+    private void populateDefaultProperties(Dictionary<String, Object> properties) {
+        synchronized (this.serviceReferenceMonitor) {
+            properties.put(Constants.SERVICE_ID, this.serviceReference.getServiceId());
+            properties.put(Constants.SERVICE_RANKING, this.serviceReference.getServiceRanking());
+            properties.put(Constants.OBJECTCLASS, this.objectClasses);
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/UpdateDelegate.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/UpdateDelegate.java
new file mode 100644
index 0000000..998a45b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/UpdateDelegate.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import java.io.InputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+/**
+ * An interface to allow delegation of calls to {@link Bundle#update()} and {@link Bundle#update(InputStream)} to be
+ * serviced in a test environment.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Implementations must be threadsafe
+ * 
+ */
+public interface UpdateDelegate {
+
+    /**
+     * @param bundle The {@link StubBundle} is being called upon
+     * @throws BundleException
+     * @see Bundle#update()
+     */
+    void update(StubBundle bundle) throws BundleException;
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/AspectPrecedence.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/AspectPrecedence.aj
new file mode 100644
index 0000000..9068d74
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/AspectPrecedence.aj
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+/**
+ * An aspect that defines the precendence of the aspects in this project
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect AspectPrecedence {
+
+    declare precedence: org.eclipse.virgo.test.stubs.framework.aspects.Valid*,  *, org.eclipse.virgo.test.stubs.framework.aspects.*Events;
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleEvents.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleEvents.aj
new file mode 100644
index 0000000..9ab6428
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleEvents.aj
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import java.util.List;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+
+/**
+ * Sends {@link BundleEvent}s to {@link BundleListener}s.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect BundleEvents {
+
+    /**
+     * Sends a {@link BundleEvent#INSTALLED} event to all of the {@link BundleListener}s registered with a
+     * {@link Bundle}
+     * 
+     * @param context The {@link BundleContext} to get the {@link BundleListener}s from
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundleContext context, Bundle bundle) : 
+            this(context) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundleContext.installBundle(..)) &&
+            target(bundle) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.INSTALLED)) {
+        sendEvent(context.getBundleListeners(), new BundleEvent(BundleEvent.INSTALLED, bundle));
+    }
+
+    /**
+     * Sends a {@link BundleEvent#STARTING} event to all of the {@link BundleListener}s registered with a {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.start(int)) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.STARTING)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.STARTING, bundle));
+    }
+
+    private void sendEvent(List<BundleListener> listeners, BundleEvent event) {
+        for (BundleListener listener : listeners) {
+            try {
+                listener.bundleChanged(event);
+            } catch (Exception e) {
+                // Swallow exceptions to allow all listeners to be called
+            }
+        }
+    }
+
+    /**
+     * Sends a {@link BundleEvent#STARTED} event to all of the {@link BundleListener}s registered with a {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.start(int)) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.ACTIVE)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.STARTED, bundle));
+    }
+
+    /**
+     * Sends a {@link BundleEvent#STOPPING} event to all of the {@link BundleListener}s registered with a {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.stop(int)) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.STOPPING)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.STOPPING, bundle));
+    }
+
+    /**
+     * Sends a {@link BundleEvent#STOPPED} event to all of the {@link BundleListener}s registered with a {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.stop(int)) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.RESOLVED)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.STOPPED, bundle));
+    }
+
+    /**
+     * Sends a {@link BundleEvent#UPDATED} event to all of the {@link BundleListener}s registered with a {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.update(..)) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.INSTALLED)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.UPDATED, bundle));
+    }
+
+    /**
+     * Sends a {@link BundleEvent#UNINSTALLED} event to all of the {@link BundleListener}s registered with a
+     * {@link Bundle}
+     * 
+     * @param bundle The {@link Bundle} to send the event against
+     */
+    after(StubBundle bundle) : 
+            this(bundle) &&
+            withincode(* org.eclipse.virgo.test.stubs.framework.StubBundle.uninstall()) &&
+            call(* org.eclipse.virgo.test.stubs.framework.StubBundle.setState(int)) &&
+            if(thisJoinPoint.getArgs()[0].equals(Bundle.UNINSTALLED)) {
+        StubBundleContext bundleContext = (StubBundleContext) bundle.getBundleContext();
+        sendEvent(bundleContext.getBundleListeners(), new BundleEvent(BundleEvent.UNINSTALLED, bundle));
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleModifier.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleModifier.aj
new file mode 100644
index 0000000..ee5922d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleModifier.aj
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import java.util.Date;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+
+/**
+ * Updates the <code>lastModified</code> time on a bundle when it's modified.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect BundleModifier {
+
+    private pointcut modifyingMethod(StubBundle bundle) : this(bundle) && (
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.uninstall()) || 
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.reset()) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.update(..))
+        );
+
+    /**
+     * Updates the modification timestamp on a {@link Bundle} when a modification is made to it
+     * 
+     * @param bundle the {@link Bundle} to modify
+     */
+    after(StubBundle bundle) : modifyingMethod(bundle) {
+        bundle.setLastModified(new Date().getTime());
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleValid.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleValid.aj
new file mode 100644
index 0000000..f769a68
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/BundleValid.aj
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import org.osgi.framework.Bundle;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+
+/**
+ * Ensures that a bundle has not been uninstalled before method execution
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect BundleValid {
+
+    private pointcut ensureNotUninstalledMethod(StubBundle bundle) : this(bundle) && (
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getEntry(String)) || 
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getEntryPaths(String)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getRegisteredServices()) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getResource(String)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getResources(String)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.getServicesInUse()) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.hasPermission(Object)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.loadClass(String)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.start(int)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.stop(int)) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.uninstall()) ||
+        execution(* org.eclipse.virgo.test.stubs.framework.StubBundle.update(..))
+    );
+
+    /**
+     * Ensures that a {@link Bundle} has not been uninstalled before executing a message
+     * 
+     * @param bundle The {@link Bundle} to check
+     * @throws IllegalStateException if the {@link Bundle} has been uninstalled
+     */
+    before(StubBundle bundle) : ensureNotUninstalledMethod(bundle) {
+        if (bundle.getState() == Bundle.UNINSTALLED) {
+            throw new IllegalStateException("This bundle has been uninstalled");
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ServiceEvents.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ServiceEvents.aj
new file mode 100644
index 0000000..9fc141a
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ServiceEvents.aj
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import java.util.List;
+
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceRegistration;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+import org.eclipse.virgo.test.stubs.framework.StubServiceRegistration;
+
+/**
+ * Sends {@link ServiceEvent}s to {@link ServiceListener}s.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect ServiceEvents {
+
+    /**
+     * Sends a {@link ServiceEvent#MODIFIED} event to all of the {@link ServiceListener}s registered with a
+     * {@link ServiceRegistration}
+     * 
+     * @param registration The {@link ServiceRegistration} to send the event against
+     */
+    after(StubServiceRegistration registration) : 
+            this(registration) &&
+            execution(* org.eclipse.virgo.test.stubs.framework.StubServiceRegistration.setProperties(*)) {
+        sendEvent(registration.getBundleContext().getServiceListeners(), new ServiceEvent(ServiceEvent.MODIFIED, registration.getReference()));
+    }
+
+    /**
+     * Sends a {@link ServiceEvent#REGISTERED} event to all of the {@link ServiceListener}s registered with a
+     * {@link ServiceRegistration}
+     * 
+     * @param registration The {@link ServiceRegistration} to send the event against
+     */
+    after(StubBundleContext context) returning (ServiceRegistration<?> registration) :
+            this(context) &&
+            execution(* org.eclipse.virgo.test.stubs.framework.StubBundleContext.registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)) {
+        sendEvent(context.getServiceListeners(), new ServiceEvent(ServiceEvent.REGISTERED, registration.getReference()));
+    }
+
+    /**
+     * Sends a {@link ServiceEvent#UNREGISTERING} event to all of the {@link ServiceListener}s registered with a
+     * {@link ServiceRegistration}
+     * 
+     * @param registration The {@link ServiceRegistration} to send the event against
+     */
+    before(StubServiceRegistration registration) :
+            this(registration) &&
+            execution(* org.eclipse.virgo.test.stubs.framework.StubServiceRegistration.unregister()) {
+        sendEvent(registration.getBundleContext().getServiceListeners(), new ServiceEvent(ServiceEvent.UNREGISTERING, registration.getReference()));
+    }
+
+    private void sendEvent(List<ServiceListener> listeners, ServiceEvent event) {
+        for (ServiceListener listener : listeners) {
+            try {
+                listener.serviceChanged(event);
+            } catch (Exception e) {
+                // Swallow exceptions to allow all listeners to be called
+            }
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidBundleContext.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidBundleContext.aj
new file mode 100644
index 0000000..fd73754
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidBundleContext.aj
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import org.osgi.framework.Bundle;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+
+/**
+ * Ensures that a bundle context is in {@link Bundle#STARTING}, {@link Bundle#ACTIVE}, or {@link Bundle#STOPPING} before
+ * method execution
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public aspect ValidBundleContext {
+
+    /**
+     * Ensures that a {@link BundleContext} is in a valid state (i.e. {@link Bundle.STARTING}, {@link Bundle.ACTIVE}, or
+     * {@link Bundle.STOPPING}) before allowing method invocation
+     * 
+     * @param bundleContext The {@link Bundle} to check
+     * @throws IllegalStateException if the {@link BundleContext} is not in valid state
+     */
+    before(StubBundleContext bundleContext) : 
+            this(bundleContext) &&
+            within(org.eclipse.virgo.test.stubs.framework.StubBundleContext) &&
+            execution(* org.osgi.framework.BundleContext.*(..)) {
+        int state = bundleContext.getContextBundle().getState();
+        if (state != Bundle.STARTING && state != Bundle.ACTIVE && state != Bundle.STOPPING) {
+            throw new IllegalStateException("This context is no longer valid");
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidServiceRegistration.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidServiceRegistration.aj
new file mode 100644
index 0000000..c0f809e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/framework/aspects/ValidServiceRegistration.aj
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework.aspects;
+
+import org.eclipse.virgo.test.stubs.framework.StubServiceRegistration;
+
+/**
+ * Ensures that a bundle has not been unregistered before method execution
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final aspect ValidServiceRegistration {
+
+    private pointcut ensureNotUninstalledMethod(StubServiceRegistration registration) : this(registration) && (
+        execution(* org.eclipse.virgo.test.stubs.framework.StubServiceRegistration.getReference()) || 
+        execution(* org.eclipse.virgo.test.stubs.framework.StubServiceRegistration.unregister())
+    );
+
+    /**
+     * Ensures that a {@link ServiceRegistration} is not uninstalled before executing the method
+     * 
+     * @param registration The {@link ServiceRegistration} to check
+     * @throws IllegalStateException if the {@link ServiceRegistration} has been unregistered
+     */
+    before(StubServiceRegistration registration) : ensureNotUninstalledMethod(registration) {
+        if (registration.getUnregistered()) {
+            throw new IllegalStateException("This ServiceRegistration has been unregistered.");
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Assert.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Assert.java
new file mode 100644
index 0000000..a34e1a5
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Assert.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.internal;
+
+/**
+ * A set of internal assertions for the OSGi test stubs
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class Assert {
+
+    /**
+     * Asserts that value is not <code>null</code>. If value is <code>null</null> throws an exception.
+     * 
+     * @param value The value to test
+     * @param argumentName The name of the argument to be placed in the exception message
+     * @throws IllegalArgumentException when the <code>value</code> is null
+     */
+    public static void assertNotNull(Object value, String argumentName) {
+        if (value == null) {
+            throw new IllegalArgumentException(String.format("%s cannot be null", argumentName));
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Duplicator.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Duplicator.java
new file mode 100644
index 0000000..d095680
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/internal/Duplicator.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.internal;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+/**
+ * Utility methods for stub implementations
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public class Duplicator {
+
+    /**
+     * Shallow copy the contents of a {@link Dictionary} into a new instance
+     * 
+     * @param <K> The type of keys in the {@link Dictionary}
+     * @param <V> The type of values in the {@link Dictionary}
+     * 
+     * @param in The {@link Dictionary} to copy
+     * @return A new, shallow copied, instance of {@link Dictionary}
+     */
+    public static <K, V> Dictionary<K, V> shallowCopy(Dictionary<K, V> in) {
+        Hashtable<K, V> out = new Hashtable<K, V>(in.size());
+
+        Enumeration<K> keys = in.keys();
+        while (keys.hasMoreElements()) {
+            K key = keys.nextElement();
+            V value = in.get(key);
+            out.put(key, value);
+        }
+
+        return out;
+    }
+
+    /**
+     * Shallow copy the contents of a {@link List} into a new instance
+     * 
+     * @param <T> The type of the values in the {@link List}
+     * @param in The {@link List} to copy
+     * @return A new, shallow copied, instance of the {@link List}
+     */
+    public static <T> List<T> shallowCopy(List<T> in) {
+        return new ArrayList<T>(in);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegion.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegion.java
new file mode 100644
index 0000000..6f34502
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegion.java
@@ -0,0 +1,162 @@
+/**
+ * This file is part of the Eclipse Virgo project.
+ *
+ * Copyright (c) 2011 copyright_holder
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    cgfrost - initial contribution
+ */
+package org.eclipse.virgo.test.stubs.region;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import org.eclipse.equinox.region.Region;
+import org.eclipse.equinox.region.RegionDigraph;
+import org.eclipse.equinox.region.RegionDigraphVisitor;
+import org.eclipse.equinox.region.RegionFilter;
+import org.eclipse.equinox.region.RegionDigraph.FilteredRegion;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+
+/**
+ * StubRegion
+ */
+public class StubRegion implements Region {
+
+    private final String name;
+    
+	private final RegionDigraph regionDigraph;
+
+    public StubRegion(String name, RegionDigraph regionDigraph) {
+        this.name = name;
+		this.regionDigraph = regionDigraph;
+    }
+    
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void addBundle(Bundle bundle) throws BundleException {
+
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void addBundle(long bundleId) {
+
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public Bundle installBundle(String location, InputStream input) throws BundleException {
+        return null;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public Bundle installBundle(String location) throws BundleException {
+        return null;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<Long> getBundleIds() {
+        return null;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean contains(Bundle bundle) {
+        return false;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean contains(long bundleId) {
+        return false;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public Bundle getBundle(String symbolicName, Version version) {
+        return null;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void connectRegion(Region headRegion, RegionFilter filter) throws BundleException {
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public RegionDigraph getRegionDigraph() {
+        return this.regionDigraph;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeBundle(Bundle bundle) {
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeBundle(long bundleId) {
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<FilteredRegion> getEdges() {
+        return null;
+    }
+
+    /** 
+     * {@inheritDoc}
+     */
+    @Override
+    public void visitSubgraph(RegionDigraphVisitor visitor) {
+    }
+
+	@Override
+	public Bundle installBundleAtLocation(String arg0, InputStream arg1) throws BundleException {
+		return null;
+	}
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraph.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraph.java
new file mode 100644
index 0000000..2e77f60
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraph.java
@@ -0,0 +1,147 @@
+/**
+ * This file is part of the Eclipse Virgo project.
+ *
+ * Copyright (c) 2011 copyright_holder
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    cgfrost - initial contribution
+ */
+package org.eclipse.virgo.test.stubs.region;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.equinox.region.Region;
+import org.eclipse.equinox.region.RegionDigraph;
+import org.eclipse.equinox.region.RegionDigraphPersistence;
+import org.eclipse.equinox.region.RegionDigraphVisitor;
+import org.eclipse.equinox.region.RegionFilter;
+import org.eclipse.equinox.region.RegionFilterBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.hooks.bundle.EventHook;
+import org.osgi.framework.hooks.bundle.FindHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+
+public class StubRegionDigraph implements RegionDigraph {
+
+	private final Map<String, Region> regions = new HashMap<String, Region>();
+	
+	private Region defaultRegion;
+	
+	@Override
+	public Iterator<Region> iterator() {
+		return this.regions.values().iterator();
+	}
+
+	@Override
+	public Region createRegion(String regionName) throws BundleException {
+		StubRegion stubRegion = new StubRegion(regionName, this);
+		this.regions.put(regionName, stubRegion);
+		return stubRegion;
+	}
+
+	@Override
+	public RegionFilterBuilder createRegionFilterBuilder() {
+		return null;
+	}
+
+	@Override
+	public void removeRegion(Region region) {
+		this.regions.remove(region.getName());
+	}
+
+	@Override
+	public Set<Region> getRegions() {
+		return Collections.unmodifiableSet(new HashSet<Region>(this.regions.values()));
+	}
+
+	@Override
+	public Region getRegion(String regionName) {
+		return this.regions.get(regionName);
+	}
+
+	@Override
+	public Region getRegion(Bundle bundle) {
+		return null;
+	}
+
+	@Override
+	public Region getRegion(long bundleId) {
+		return null;
+	}
+
+	@Override
+	public void connect(Region tailRegion, RegionFilter filter, Region headRegion) throws BundleException {
+
+	}
+
+	@Override
+	public Set<FilteredRegion> getEdges(Region tailRegion) {
+		return null;
+	}
+
+	@Override
+	public void visitSubgraph(Region startingRegion, RegionDigraphVisitor visitor) {
+
+	}
+
+	@Override
+	public RegionDigraphPersistence getRegionDigraphPersistence() {
+		return null;
+	}
+
+	@Override
+	public RegionDigraph copy() throws BundleException {
+		return null;
+	}
+
+	@Override
+	public void replace(RegionDigraph digraph) throws BundleException {
+
+	}
+
+	@Override
+	public ResolverHookFactory getResolverHookFactory() {
+		return null;
+	}
+
+	@Override
+	public EventHook getBundleEventHook() {
+		return null;
+	}
+
+	@Override
+	public FindHook getBundleFindHook() {
+		return null;
+	}
+
+	@Override
+	public org.osgi.framework.hooks.service.EventHook getServiceEventHook() {
+		return null;
+	}
+
+	@Override
+	public org.osgi.framework.hooks.service.FindHook getServiceFindHook() {
+		return null;
+	}
+
+	@Override
+	public void setDefaultRegion(Region defaultRegion) {
+		this.defaultRegion = defaultRegion;
+	}
+
+	@Override
+	public Region getDefaultRegion() {
+		return this.defaultRegion;
+	}
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfiguration.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfiguration.java
new file mode 100644
index 0000000..18c00fb
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfiguration.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.cm;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+import static org.eclipse.virgo.test.stubs.internal.Duplicator.shallowCopy;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * A stub testing implementation of {@link Configuration} as defined in section 104.15.2 of the OSGi Service Platform Service
+ * Compendium.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class StubConfiguration implements Configuration {
+
+    private final StubConfigurationAdmin configurationAdmin;
+
+    private final String pid;
+
+    private final String factoryPid;
+
+    private volatile boolean deleted = false;
+
+    private volatile Dictionary<String, Object> properties;
+
+    private final Object propertiesMonitor = new Object();
+
+    private volatile String bundleLocation;
+
+    private final Object bundleLocationMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubConfiguration} and sets its initial state. This constructor sets <code>factoryPid</code>
+     * to <code>null</code>.
+     * 
+     * @param pid The pid of this configuration
+     */
+    public StubConfiguration(String pid) {
+        this(pid, null);
+    }
+
+    /**
+     * Creates a new {@link StubConfiguration} and sets its initial state
+     * 
+     * @param pid The pid of this configuration
+     * @param factoryPid The factory pid of this configuration
+     */
+    public StubConfiguration(String pid, String factoryPid) {
+        this.pid = pid;
+        this.factoryPid = factoryPid;
+        this.configurationAdmin = new StubConfigurationAdmin(pid == null ? factoryPid : pid, this);
+    }
+
+    StubConfiguration(StubConfigurationAdmin configurationAdmin, String pid, String factoryPid, String bundleLocation) {
+        this.configurationAdmin = configurationAdmin;
+        this.pid = pid;
+        this.factoryPid = pid;
+        this.bundleLocation = bundleLocation;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void delete() throws IOException {
+        this.configurationAdmin.deleteConfiguration(this.pid == null ? this.factoryPid : this.pid);
+        this.deleted = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getBundleLocation() {
+        synchronized (this.bundleLocationMonitor) {
+            return this.bundleLocation;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getFactoryPid() {
+        return this.factoryPid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getPid() {
+        return this.pid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary<String, Object> getProperties() {
+        synchronized (this.propertiesMonitor) {
+            return this.properties == null ? null : shallowCopy(this.properties);
+        }
+    }
+
+    /**
+     * Adds a mapping from a key to a value for all subsequent calls to {@link #getProperties()}.
+     * 
+     * @param key The key to map from
+     * @param value The value to map to
+     * @return <code>this</code> instance of the {@link StubConfiguration}
+     */
+    public StubConfiguration addProperty(String key, Object value) {
+        synchronized (this.propertiesMonitor) {
+            if (this.properties == null) {
+                this.properties = new Hashtable<String, Object>();
+                updateSystemProperties(this.properties);
+            }
+
+            this.properties.put(key, value);
+            return this;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setBundleLocation(String bundleLocation) {
+        synchronized (this.bundleLocationMonitor) {
+            this.bundleLocation = bundleLocation;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void update() throws IOException {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public void update(Dictionary<String, ?> properties) throws IOException {
+        assertNotNull(properties, "properties");
+        synchronized (this.propertiesMonitor) {
+            Dictionary<String, Object> copy = (Dictionary<String, Object>) shallowCopy(properties);
+            updateSystemProperties(copy);
+            this.properties = copy;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        StubConfiguration other = (StubConfiguration) obj;
+        if (!this.pid.equals(other.pid)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return this.pid.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("pid: %s, factoryPid: %s, deleted: %b", this.pid, this.factoryPid, this.deleted);
+    }
+
+    /**
+     * Gets whether this {@link Configuration} has been deleted
+     * 
+     * @return Whether this {@link Configuration} has been deleted
+     */
+    public boolean getDeleted() {
+        return this.deleted;
+    }
+
+    private void updateSystemProperties(Dictionary<String, Object> properties) {
+        properties.put(Constants.SERVICE_PID, this.pid);
+        if (this.factoryPid == null) {
+            properties.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
+        } else {
+            properties.put(ConfigurationAdmin.SERVICE_FACTORYPID, this.factoryPid);
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdmin.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdmin.java
new file mode 100644
index 0000000..385509d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdmin.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.cm;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+import org.eclipse.virgo.test.stubs.support.PropertiesFilter;
+import org.eclipse.virgo.test.stubs.support.TrueFilter;
+
+/**
+ * A stub testing implementation of {@link ConfigurationAdmin} as defined in section 104.15.3 of the OSGi Service Platform
+ * Service Compendium.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class StubConfigurationAdmin implements ConfigurationAdmin {
+
+    private final Map<String, Configuration> configurations = new HashMap<String, Configuration>();
+
+    private final Object configurationsMonitor = new Object();
+
+    /**
+     * Creates a new {@link StubConfigurationAdmin} and sets its initial state
+     */
+    public StubConfigurationAdmin() {
+    }
+
+    StubConfigurationAdmin(String pid, Configuration configuration) {
+        this.configurations.put(pid, configuration);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
+        return createFactoryConfiguration(factoryPid, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
+        assertNotNull(factoryPid, "factoryPid");
+        synchronized (this.configurationsMonitor) {
+            this.configurations.put(factoryPid, new StubConfiguration(this, null, factoryPid, location));
+            return this.configurations.get(factoryPid);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Configuration getConfiguration(String pid) throws IOException {
+        return getConfiguration(pid, null);
+    }
+
+    /**
+     * Create a new configuration in this {@link ConfigurationAdmin}. This method is useful for chaining together the
+     * creation and property population of stub {@link Configuration}s
+     * 
+     * @param pid The pid of the {@link Configuration} being created
+     * @return the {@link StubConfiguration} that was created
+     * @throws IOException required by the {@link ConfigurationAdmin} specification
+     */
+    public StubConfiguration createConfiguration(String pid) throws IOException {
+        return (StubConfiguration) getConfiguration(pid);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Configuration getConfiguration(String pid, String location) throws IOException {
+        assertNotNull(pid, "pid");
+        synchronized (this.configurationsMonitor) {
+            if (!this.configurations.containsKey(pid)) {
+                this.configurations.put(pid, new StubConfiguration(this, pid, null, location));
+            }
+            return this.configurations.get(pid);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException {
+        synchronized (this.configurationsMonitor) {
+            Set<Configuration> matches = new HashSet<Configuration>();
+
+            Filter f = filter == null ? new TrueFilter() : new PropertiesFilter(filter);
+            for (Configuration configuration : this.configurations.values()) {
+                if (isCurrent(configuration) && f.match(configuration.getProperties())) {
+                    matches.add(configuration);
+                }
+            }
+
+            return matches.size() == 0 ? null : matches.toArray(new Configuration[matches.size()]);
+        }
+    }
+
+    private boolean isCurrent(Configuration configuration) {
+        return configuration.getProperties() != null;
+    }
+
+    void deleteConfiguration(String pid) {
+        synchronized (this.configurationsMonitor) {
+            this.configurations.remove(pid);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("configurations: %s", this.configurations);
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/aspects/DeletedConfiguration.aj b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/aspects/DeletedConfiguration.aj
new file mode 100644
index 0000000..b1629f9
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/cm/aspects/DeletedConfiguration.aj
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.cm.aspects;
+
+import org.osgi.service.cm.Configuration;
+
+import org.eclipse.virgo.test.stubs.service.cm.StubConfiguration;
+
+/**
+ * Ensures that a configuration has not been deleted before method execution
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+
+public final aspect DeletedConfiguration {
+
+    /**
+     * Ensures that a {@link Configuration} has not been deleted before allowing method invocation
+     * 
+     * @param configuration The {@link Configuration} to check
+     * @throws IllegalStateException if the {@link Configuration} has been deleted
+     */
+    before(StubConfiguration configuration) : 
+                this(configuration) &&
+                within(org.eclipse.virgo.test.stubs.service.cm.StubConfiguration) &&
+                execution(* org.osgi.service.cm.Configuration.*(..)) &&
+                !execution(* org.eclipse.virgo.test.stubs.service.cm.StubConfiguration.equals(Object)) &&
+                !execution(int org.eclipse.virgo.test.stubs.service.cm.StubConfiguration.hashCode()) &&
+                !execution(java.lang.String org.eclipse.virgo.test.stubs.service.cm.StubConfiguration.toString()) {
+        if (configuration.getDeleted()) {
+            throw new IllegalStateException("This configuration has been deleted");
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContext.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContext.java
new file mode 100644
index 0000000..ebb4ecc
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContext.java
@@ -0,0 +1,119 @@
+/*******************************************************************************

+ * Copyright (c) 2008, 2010 SAP AG

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *   SAP AG - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.virgo.test.stubs.service.component;

+

+import java.util.Dictionary;

+import java.util.Hashtable;

+

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.ServiceReference;

+import org.osgi.service.component.ComponentContext;

+import org.osgi.service.component.ComponentInstance;

+

+/**

+ * 

+ * A stub testing implementation of {@link ComponentContext} as defined in section 112.5.9 of the OSGi Service Platform Compendium

+ * Specification.

+ * <p />

+ *

+ * <strong>Concurrent Semantics</strong><br />

+ * Thread safe.

+ */

+public class StubComponentContext implements ComponentContext {

+

+    public final String DEFAULT_PROP_KEY = "key";

+

+    public final String DEFAULT_PROP_VALUE = "value";

+

+    private final Dictionary<String, String> props = new Hashtable<String, String>();

+

+    private final BundleContext bundleContext;

+

+    public StubComponentContext(BundleContext bundleContext) {

+        this.bundleContext = bundleContext;

+        populateDefaultProperties();

+    }

+    

+    /**

+     * {@inheritDoc}

+     */

+    public Dictionary<String, String> getProperties() {

+        return this.props;

+    }

+

+    private void populateDefaultProperties() {

+        this.props.put(this.DEFAULT_PROP_KEY, this.DEFAULT_PROP_VALUE);

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public Object locateService(String name) {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public Object locateService(String name, ServiceReference reference) {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public Object[] locateServices(String name) {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public BundleContext getBundleContext() {

+        return this.bundleContext;

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public Bundle getUsingBundle() {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public ComponentInstance getComponentInstance() {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public void enableComponent(String name) {

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public void disableComponent(String name) {

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public ServiceReference getServiceReference() {

+        throw new UnsupportedOperationException("Not implemented yet.");

+    }

+

+}

diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdmin.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdmin.java
new file mode 100644
index 0000000..117c220
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdmin.java
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.event;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+import org.eclipse.virgo.test.stubs.service.event.internal.EventUtils;
+
+/**
+ * A stub implementation of {@link EventAdmin}.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Thread-safe.
+ * 
+ */
+public class StubEventAdmin implements EventAdmin {
+
+    private final List<Event> postedEvents = new ArrayList<Event>();
+
+    private final List<Event> sentEvents = new ArrayList<Event>();
+
+    private final Object monitor = new Object();
+
+    /**
+     * {@inheritDoc}
+     */
+    public void postEvent(Event event) {
+        synchronized (this.monitor) {
+            this.postedEvents.add(event);
+            this.monitor.notifyAll();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sendEvent(Event event) {
+        synchronized (this.monitor) {
+            this.sentEvents.add(event);
+            this.monitor.notifyAll();
+        }
+    }
+
+    /**
+     * Waits for up to the supplied millisecond timeout period for the {@link EventAdmin#sendEvent(Event) sending} of an
+     * {@link Event} that {@link Event#equals equals} the supplied <code>event</code>. Successfully waiting for an
+     * <code>Event</code> removes it from the list of events such that a subsequent invocation would block until another
+     * matching event was sent.
+     * 
+     * @param event The <code>Event</code> to await.
+     * @param msTimeout The timeout period
+     * @return <code>true</code> if the <code>Event</code> has been sent, otherwise <code>false</code>.
+     */
+    public boolean awaitSendingOfEvent(Event event, long msTimeout) {
+        return awaitEvent(event, this.sentEvents, msTimeout);
+    }
+
+    /**
+     * Waits for up to the supplied millisecond timeout period for the {@link EventAdmin#postEvent(Event) posting} of an
+     * {@link Event} that {@link Event#equals equals} the supplied <code>event</code>. Successfully waiting for an
+     * <code>Event</code> removes it from the list of events such that a subsequent invocation would block until another
+     * matching event was posted.
+     * 
+     * @param event The <code>Event</code> to await.
+     * @param msTimeout The timeout period
+     * @return <code>true</code> if the <code>Event</code> has been posted, otherwise <code>false</code>.
+     */
+    public boolean awaitPostingOfEvent(Event event, long msTimeout) {
+        return awaitEvent(event, this.postedEvents, msTimeout);
+    }
+    
+    /**
+     * Waits for up to the supplied millisecond timeout period for the {@link EventAdmin#sendEvent(Event) sending} of an
+     * {@link Event} that has the supplied <code>topic</code>. Successfully waiting for an <code>Event</code> removes it
+     * from the list of events such that a subsequent invocation would block until another matching event was sent.
+     * 
+     * @param topic The <code>topic</code> to await.
+     * @param msTimeout The timeout period
+     * @return the matching <code>Event</code>, or <code>null</code> if no <code>Event</code> is received.
+     */
+    public Event awaitSendingOfEvent(String topic, long msTimeout) {
+        return awaitEventOnTopic(topic, this.sentEvents, msTimeout);
+    }
+    
+    /**
+     * Waits for up to the supplied millisecond timeout period for the {@link EventAdmin#postEvent(Event) posting} of an
+     * {@link Event} that has the supplied <code>topic</code>. Successfully waiting for an <code>Event</code> removes it
+     * from the list of events such that a subsequent invocation would block until another matching event was sent.
+     * 
+     * @param topic The <code>topic</code> to await.
+     * @param msTimeout The timeout period
+     * @return the matching <code>Event</code>, or <code>null</code> if no <code>Event</code> is received.
+     */
+    public Event awaitPostingOfEvent(String topic, long msTimeout) {
+        return awaitEventOnTopic(topic, this.postedEvents, msTimeout);
+    }
+
+    private boolean awaitEvent(Event event, List<Event> eventList, long timeout) {
+        long endTime = System.currentTimeMillis() + timeout;
+
+        synchronized (this.monitor) {
+            try {
+                while (!removeEvent(event, eventList)) {
+                    long waitTime = endTime - System.currentTimeMillis();
+
+                    if (waitTime > 0) {
+                        this.monitor.wait(waitTime);
+                    } else {
+                        return false;
+                    }
+                }
+                return true;
+            } catch (InterruptedException e) {
+                Thread.interrupted();
+            }
+
+            return false;
+        }
+    }
+    
+    private Event awaitEventOnTopic(String topic, List<Event> eventList, long timeout) {
+        long endTime = System.currentTimeMillis() + timeout;
+
+        synchronized (this.monitor) {
+            try {
+                Event event = null;
+                while (event == null) {
+                    long waitTime = endTime - System.currentTimeMillis();
+
+                    if (waitTime > 0) {
+                        this.monitor.wait(waitTime);
+                        event = removeEventOnTopic(topic, eventList);
+                    } else {
+                        break;
+                    }
+                }
+                return event;
+            } catch (InterruptedException e) {
+                Thread.interrupted();
+            }
+
+            return null;
+        }
+    }
+
+    private boolean removeEvent(Event event, List<Event> eventList) {
+        
+        Iterator<Event> candidates = eventList.iterator();
+        
+        while (candidates.hasNext()) {
+            Event candidate = candidates.next();
+            
+            if (EventUtils.eventsAreEqual(candidate, event)) {
+                candidates.remove();
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    private Event removeEventOnTopic(String topic, List<Event> eventList) {
+        
+        Iterator<Event> candidates = eventList.iterator();
+        
+        while (candidates.hasNext()) {
+            Event candidate = candidates.next();
+            
+            if (topic.equals(candidate.getTopic())) {
+                candidates.remove();
+                return candidate;
+            }            
+        }
+        
+        return null;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtils.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtils.java
new file mode 100644
index 0000000..0a0c715
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtils.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.event.internal;
+
+import java.util.Arrays;
+
+import org.osgi.service.event.Event;
+
+
+/**
+ * Utility methods for working with {@link Event Events}.
+ * 
+ * <p />
+ *
+ * <strong>Concurrent Semantics</strong><br />
+ *
+ * Thread-safe.
+ *
+ */
+public final class EventUtils {
+    
+    /**
+     * Returns <code>true</code> if the supplied Events are equal, <code>false</code>
+     * if they are not. For two Events to be equals, their topics must be 
+     * {@link String#equals(Object)} equal, and their properties must be equal.
+     * 
+     * <p/>
+     * 
+     * Note: when considering property equality, one-dimensional arrays are considered
+     * to be equal if their contents are equal. Multi-dimensional arrays are not
+     * currently considered and will always be reported as unequal.
+     * 
+     * @param event one <code>Event</code> to be tested for equality
+     * @param candidate the other <code>Event</code> to be tested for equality
+     * 
+     * @return <code>true</code> if the events are equal, otherwise <code>false</code>.
+     */
+    public static boolean eventsAreEqual(Event event, Event candidate) {
+        if (candidate.getTopic().equals(event.getTopic())) {
+            String[] propertyNames = event.getPropertyNames();
+            String[] candidatePropertyNames = candidate.getPropertyNames();
+            
+            if (Arrays.equals(propertyNames, candidatePropertyNames)) {
+                for (String propertyName : propertyNames) {
+                    Object value = event.getProperty(propertyName);
+                    Object candidateValue = candidate.getProperty(propertyName);
+                    
+                    if (value.getClass().isArray()) {
+                        if (!arraysAreEqual(value, candidateValue)) {
+                            return false;
+                        }        
+                    } else if (!value.equals(candidateValue)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    static boolean arraysAreEqual(Object value, Object candidateValue) {
+        boolean arraysAreEqual = false;
+        
+        Class<?> componentType = value.getClass().getComponentType();
+        if (!componentType.isPrimitive()) {
+            arraysAreEqual = Arrays.equals((Object[]) value, (Object[]) candidateValue);                                    
+        } else if (componentType.equals(boolean.class)) {
+            arraysAreEqual = Arrays.equals((boolean[]) value, (boolean[]) candidateValue);
+        } else if (componentType.equals(byte.class)) {
+            arraysAreEqual = Arrays.equals((byte[]) value, (byte[]) candidateValue);
+        } else if (componentType.equals(char.class)) {
+            arraysAreEqual = Arrays.equals((char[]) value, (char[]) candidateValue);
+        } else if (componentType.equals(double.class)) {
+            arraysAreEqual = Arrays.equals((double[]) value, (double[]) candidateValue);
+        } else if (componentType.equals(float.class)) {
+            arraysAreEqual = Arrays.equals((float[]) value, (float[]) candidateValue);
+        } else if (componentType.equals(int.class)) {
+            arraysAreEqual = Arrays.equals((int[]) value, (int[]) candidateValue);
+        } else if (componentType.equals(long.class)) {
+            arraysAreEqual = Arrays.equals((long[]) value, (long[]) candidateValue);
+        } else {
+            arraysAreEqual = Arrays.equals((short[]) value, (short[]) candidateValue);
+        }
+        
+        return arraysAreEqual;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/AbstractFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/AbstractFilter.java
new file mode 100644
index 0000000..2bb4738
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/AbstractFilter.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import org.osgi.framework.Filter;
+
+/**
+ * Abstract implementation of {@link Filter} that provides the required implementations of {@link Filter#hashCode}
+ * and {@link Filter#toString}
+ * 
+ * <p/>
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Thread-safe.
+ * 
+ */
+public abstract class AbstractFilter implements Filter {
+    
+    /**
+     * @return The filter string for this filter
+     */
+    protected abstract String getFilterString();
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        return getFilterString();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        return toString().hashCode();
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/FalseFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/FalseFilter.java
new file mode 100644
index 0000000..fd293de
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/FalseFilter.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * An implementation of {@link Filter} that never matches.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public class FalseFilter extends AbstractFilter {
+
+    private final String filterString;
+
+    /**
+     * Creates a new instance with an empty filter string
+     */
+    public FalseFilter() {
+        this("");
+    }
+
+    /**
+     * @param filterString the filter string for this instance
+     */
+    public FalseFilter(String filterString) {
+        this.filterString = filterString;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getFilterString() {
+        return this.filterString;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+   public boolean match(ServiceReference<?> reference) {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(Dictionary<String, ?> dictionary) {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matchCase(Dictionary<String, ?> dictionary) {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matches(Map<String, ?> map) {
+        return false;
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilter.java
new file mode 100644
index 0000000..4588603
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilter.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A filter implementation that allows you to match on a given class type.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class ObjectClassFilter extends AbstractFilter {
+
+    private static final String FILTER_STRING_FORMAT = "(objectClass=%s)";
+
+    private final String className;
+
+    /**
+     * @param clazz The {@link Class} for the filter to match on
+     */
+    public ObjectClassFilter(Class<?> clazz) {
+        assertNotNull(clazz, "clazz");
+        this.className = clazz.getName();
+    }
+
+    /**
+     * @param className The name of the {@link Class} for the filter to match on
+     */
+    public ObjectClassFilter(String className) {
+        assertNotNull(className, "className");
+        this.className = className;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(ServiceReference<?> reference) {
+        return contains((String[]) reference.getProperty(Constants.OBJECTCLASS), this.className);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(Dictionary<String, ?> dictionary) {
+        return contains((String[]) dictionary.get(Constants.OBJECTCLASS), this.className);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matchCase(Dictionary<String, ?> dictionary) {
+        return match(dictionary);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getFilterString() {
+        return String.format(FILTER_STRING_FORMAT, this.className);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matches(Map<String, ?> map) {
+        return contains((String [])map.get(Constants.OBJECTCLASS), this.className);
+    }
+
+    private boolean contains(String[] strings, String toMatch) {
+        for (String string : strings) {
+            if (toMatch.equals(string)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/PropertiesFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/PropertiesFilter.java
new file mode 100644
index 0000000..a3097e5
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/PropertiesFilter.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.eclipse.virgo.test.stubs.internal.Assert.assertNotNull;
+
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A filter implementation that allows you to match on a given set of properties.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public class PropertiesFilter extends AbstractFilter {
+
+    private static final String FILTER_STRING_FORMAT = "(%s=%s)";
+
+    private static final Pattern PROPERTIES_PATTERN = Pattern.compile("\\(([^=&\\(\\)]*)=([^=&\\(\\)]*)\\)");
+
+    private final Map<String, Object> properties;
+
+    /**
+     * Creates a new {@link PropertiesFilter} that matches on a collection of properties. You may need to pass in a
+     * {@link TreeMap} in order to preserve the property ordering so that it matches the filter string generated by your
+     * code.
+     * 
+     * @param properties The properties to match on
+     */
+    public PropertiesFilter(Map<String, Object> properties) {
+        assertNotNull(properties, "properties");
+        this.properties = properties;
+    }
+
+    /**
+     * Creates a new {@link PropertiesFilter} that matches on a set of properties defined in a filter string.
+     * 
+     * @param filterString The filter string to parse and match on
+     * @throws InvalidSyntaxException if the filterString is not of valid filter syntax
+     */
+    public PropertiesFilter(String filterString) throws InvalidSyntaxException {
+        assertNotNull(filterString, filterString);
+        this.properties = parseFilterString(filterString);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(ServiceReference<?> reference) {
+        for (Entry<String, Object> entry : this.properties.entrySet()) {
+            Object value = reference.getProperty(entry.getKey());
+            if (value == null || !value.equals(entry.getValue())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(Dictionary<String, ?> dictionary) {
+        for (Entry<String, Object> entry : this.properties.entrySet()) {
+            Object value = dictionary.get(entry.getKey());
+            if (value == null || !value.equals(entry.getValue())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matchCase(Dictionary<String, ?> dictionary) {
+        return match(dictionary);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getFilterString() {
+        if (this.properties.size() == 0) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (Entry<String, Object> entry : this.properties.entrySet()) {
+            sb.append(String.format(FILTER_STRING_FORMAT, entry.getKey(), entry.getValue()));
+        }
+
+        if (this.properties.size() > 1) {
+            sb.insert(0, "(&");
+            sb.append(")");
+        }
+
+        return sb.toString();
+    }
+
+    private Map<String, Object> parseFilterString(String filterString) throws InvalidSyntaxException {
+        Map<String, Object> properties = new TreeMap<String, Object>();
+
+        Matcher matcher = PROPERTIES_PATTERN.matcher(filterString);
+        while (matcher.find()) {
+            properties.put(matcher.group(1), matcher.group(2));
+        }
+
+        return properties;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matches(Map<String, ?> map) {
+        for (Entry<String, Object> entry : this.properties.entrySet()) {
+            Object value = map.get(entry.getKey());
+            if (value == null || !value.equals(entry.getValue())) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/TrueFilter.java b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/TrueFilter.java
new file mode 100644
index 0000000..6c00d2a
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/main/java/org/eclipse/virgo/test/stubs/support/TrueFilter.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * An implementation of {@link Filter} that always matches.
+ * <p />
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * 
+ * Threadsafe
+ * 
+ */
+public final class TrueFilter extends AbstractFilter {
+
+    private final String filterString;
+
+    /**
+     * Creates a new instance with an empty filter string
+     */
+    public TrueFilter() {
+        this("");
+    }
+
+    /**
+     * @param filterString the filter string for this instance
+     */
+    public TrueFilter(String filterString) {
+        this.filterString = filterString;
+    }
+
+   /**
+     * {@inheritDoc}
+     */
+    public String getFilterString() {
+        return this.filterString;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(ServiceReference<?> reference) {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean match(Dictionary<String, ?> dictionary) {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matchCase(Dictionary<String, ?> dictionary) {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matches(Map<String, ?> map) {
+        return true;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/AdditionalAsserts.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/AdditionalAsserts.java
new file mode 100644
index 0000000..76db35e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/AdditionalAsserts.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs;
+
+import static org.junit.Assert.assertTrue;
+
+public final class AdditionalAsserts {
+
+    public static void assertContains(String substring, String string) {
+        assertTrue(String.format("String '%s' did not contain substring '%s'", string, substring), string.contains(substring));
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/OSGiAssertTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/OSGiAssertTests.java
new file mode 100644
index 0000000..e4587e2
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/OSGiAssertTests.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.framework.OSGiAssert.assertBundleListenerCount;
+import static org.eclipse.virgo.test.stubs.framework.OSGiAssert.assertCleanState;
+import static org.eclipse.virgo.test.stubs.framework.OSGiAssert.assertFrameworkListenerCount;
+import static org.eclipse.virgo.test.stubs.framework.OSGiAssert.assertServiceListenerCount;
+import static org.eclipse.virgo.test.stubs.framework.OSGiAssert.assertServiceRegistrationCount;
+
+import org.junit.Test;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;
+
+public class OSGiAssertTests {
+
+    private final StubBundleContext bundleContext = new StubBundleContext(new StubBundle());
+
+    @Test
+    public void testAssertCleanState() {
+        assertCleanState(this.bundleContext);
+    }
+
+    @Test
+    public void testAssertBundleListenerCount() {
+        this.bundleContext.addBundleListener(new BundleListener() {
+
+            public void bundleChanged(BundleEvent event) {
+
+            }
+        });
+        assertBundleListenerCount(this.bundleContext, 1);
+    }
+
+    @Test
+    public void testAssertFrameworkListenerCount() {
+        this.bundleContext.addFrameworkListener(new FrameworkListener() {
+
+            public void frameworkEvent(FrameworkEvent event) {
+            }
+        });
+        assertFrameworkListenerCount(this.bundleContext, 1);
+    }
+
+    @Test
+    public void testAssertServiceListenerCount() {
+        this.bundleContext.addServiceListener(new ServiceListener() {
+
+            public void serviceChanged(ServiceEvent event) {
+            }
+        });
+        assertServiceListenerCount(this.bundleContext, 1);
+    }
+
+    @Test
+    public void testAssertServiceRegistrationCount() {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        this.bundleContext.registerService(Exception.class.getName(), new Object(), null);
+        assertServiceRegistrationCount(this.bundleContext, 2);
+    }
+
+    @Test
+    public void testAssertServiceRegistrationCountType() {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        this.bundleContext.registerService(Exception.class.getName(), new Object(), null);
+        assertServiceRegistrationCount(this.bundleContext, Object.class, 1);
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleContextTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleContextTests.java
new file mode 100644
index 0000000..c00bef5
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleContextTests.java
@@ -0,0 +1,421 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
+
+import org.eclipse.virgo.test.stubs.support.AbstractFilter;
+
+public class StubBundleContextTests {
+
+    private StubBundleContext bundleContext = new StubBundleContext();
+
+    @Test
+    public void initialState() {
+        assertNotNull(this.bundleContext.getBundle());
+        assertEquals(0, this.bundleContext.getBundleListeners().size());
+        assertEquals(0, this.bundleContext.getFrameworkListeners().size());
+        assertEquals(0, this.bundleContext.getServiceListeners().size());
+        assertEquals(0, this.bundleContext.getBundles().length);
+        assertNull(this.bundleContext.getDataFile("testFilename"));
+        assertEquals(0, this.bundleContext.getServiceRegistrations().size());
+        assertNull(this.bundleContext.getProperty("testKey"));
+    }
+
+    @Test
+    public void bundleListeners() {
+        TestBundleListener listener = new TestBundleListener();
+        assertEquals(0, this.bundleContext.getBundleListeners().size());
+        this.bundleContext.addBundleListener(listener);
+        assertEquals(1, this.bundleContext.getBundleListeners().size());
+        this.bundleContext.removeBundleListener(listener);
+        assertEquals(0, this.bundleContext.getBundleListeners().size());
+    }
+
+    @Test
+    public void frameworkListeners() {
+        TestFrameworkListener listener = new TestFrameworkListener();
+        assertEquals(0, this.bundleContext.getFrameworkListeners().size());
+        this.bundleContext.addFrameworkListener(listener);
+        assertEquals(1, this.bundleContext.getFrameworkListeners().size());
+        this.bundleContext.removeFrameworkListener(listener);
+        assertEquals(0, this.bundleContext.getFrameworkListeners().size());
+    }
+
+    @Test
+    public void serviceListeners() {
+        TestServiceListener listener = new TestServiceListener();
+        assertEquals(0, this.bundleContext.getServiceListeners().size());
+        this.bundleContext.addServiceListener(listener);
+        assertEquals(1, this.bundleContext.getServiceListeners().size());
+        this.bundleContext.removeServiceListener(listener);
+        assertEquals(0, this.bundleContext.getServiceListeners().size());
+    }
+
+    @Test
+    public void getBundle() {
+        assertNotNull(this.bundleContext.getBundle());
+    }
+
+    @Test
+    public void getBundleByLocation() {
+        assertNull(this.bundleContext.getBundle("testLocation"));
+    }
+
+    @Test
+    public void getBundleById() {
+        StubBundle bundle = new StubBundle(25L, "testSymbolicName", new Version(1, 0, 0), "testLocation");
+        this.bundleContext.addInstalledBundle(bundle);
+        assertSame(bundle, this.bundleContext.getBundle(25L));
+    }
+
+    @Test
+    public void getBundles() {
+        this.bundleContext.addInstalledBundle(new StubBundle());
+        assertEquals(1, this.bundleContext.getBundles().length);
+    }
+
+    @Test
+    public void getDataFile() {
+        assertNull(this.bundleContext.getDataFile("testFile"));
+        File file = new File("/");
+        this.bundleContext.addDataFile("testFile2", file);
+        assertSame(file, this.bundleContext.getDataFile("testFile2"));
+    }
+
+    @Test
+    public void installBundle() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundleContext.addBundleListener(listener);
+        Bundle bundle = this.bundleContext.installBundle("/");
+        assertNotNull(bundle);
+        assertEquals(2, bundle.getBundleId());
+        assertEquals("/", bundle.getSymbolicName());
+        assertEquals("/", bundle.getLocation());
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+        assertTrue(listener.getCalled());
+        assertEquals(1, listener.getEvents().length);
+        assertEquals(BundleEvent.INSTALLED, listener.getEvents()[0].getType());
+    }
+
+    @Test
+    public void installBundleInputStream() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundleContext.addBundleListener(listener);
+        Bundle bundle = this.bundleContext.installBundle("/", new TestInputStream());
+        assertNotNull(bundle);
+        assertEquals(2, bundle.getBundleId());
+        assertEquals("/", bundle.getSymbolicName());
+        assertEquals("/", bundle.getLocation());
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+        assertTrue(listener.getCalled());
+        assertEquals(1, listener.getEvents().length);
+        assertEquals(BundleEvent.INSTALLED, listener.getEvents()[0].getType());
+    }
+
+    @Test
+    public void registerService() {
+        Dictionary<String, String> properties = new Hashtable<>();
+        properties.put("testKey", "testValue");
+        Object service = new Object();
+        ServiceRegistration<?> serviceRegistration = this.bundleContext.registerService(Object.class.getName(), service, properties);
+        assertNotNull(serviceRegistration);
+        assertNotNull(serviceRegistration.getReference());
+        assertEquals("testValue", serviceRegistration.getReference().getProperty("testKey"));
+        assertSame(service, this.bundleContext.getService(serviceRegistration.getReference()));
+    }
+
+    @Test
+    public void registerServiceTyped() {
+        Dictionary<String, String> properties = new Hashtable<>();
+        properties.put("testKey", "testValue");
+        Object service = new Object();
+        ServiceRegistration<Object> serviceRegistration = this.bundleContext.registerService(Object.class, service, properties);
+        assertNotNull(serviceRegistration);
+        assertNotNull(serviceRegistration.getReference());
+        assertEquals("testValue", serviceRegistration.getReference().getProperty("testKey"));
+        assertSame(service, this.bundleContext.getService(serviceRegistration.getReference()));
+    }
+
+
+    @Test
+    public void getService() {
+        Object service = new Object();
+        ServiceRegistration<Object> serviceRegistration = this.bundleContext.registerService(Object.class, service, null);
+        assertSame(service, this.bundleContext.getService(serviceRegistration.getReference()));
+    }
+
+    @Test
+    public void getServiceUnregistered() {
+        Object service = new Object();
+        ServiceRegistration<Object> serviceRegistration = this.bundleContext.registerService(Object.class, service, null);
+        ServiceReference<Object> reference = serviceRegistration.getReference();
+        serviceRegistration.unregister();
+        assertNull(this.bundleContext.getService(reference));
+        assertNull(this.bundleContext.getServiceReference(Object.class.getName()));
+    }
+
+    @Test
+    public void registerServiceArray() {
+        Dictionary<String, String> properties = new Hashtable<String, String>();
+        properties.put("testKey", "testValue");
+        ServiceRegistration<?> serviceRegistration = this.bundleContext.registerService(
+            new String[] { Object.class.getName(), Exception.class.getName() }, new Object(), properties);
+        assertNotNull(serviceRegistration);
+        assertNotNull(serviceRegistration.getReference());
+        assertEquals("testValue", serviceRegistration.getReference().getProperty("testKey"));
+    }
+
+    @Test
+    public void removeRegisteredService() {
+        ServiceRegistration<Object> serviceRegistration = this.bundleContext.registerService(Object.class, new Object(), null);
+        assertEquals(1, this.bundleContext.getServiceRegistrations().size());
+        this.bundleContext.removeRegisteredService(serviceRegistration);
+        assertEquals(0, this.bundleContext.getServiceRegistrations().size());
+    }
+
+    @Test
+    public void getProperty() {
+        assertNull(this.bundleContext.getProperty("testKey"));
+        this.bundleContext.addProperty("testKey", "testValue");
+        assertEquals("testValue", this.bundleContext.getProperty("testKey"));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void createFilterNull() throws InvalidSyntaxException {
+        this.bundleContext.createFilter(null);
+    }
+
+    @Test(expected = InvalidSyntaxException.class)
+    public void createFilterMissing() throws InvalidSyntaxException {
+        this.bundleContext.createFilter("testFilter");
+    }
+
+    @Test
+    public void createFilter() throws InvalidSyntaxException {
+        this.bundleContext.addFilter("testFilter", new TestFilter());
+        assertNotNull(this.bundleContext.createFilter("testFilter"));
+    }
+
+    @Test
+    public void ungetService() {
+        StubServiceRegistration<Object> serviceRegistration = new StubServiceRegistration<Object>(this.bundleContext);
+        StubServiceReference<Object> serviceReference = new StubServiceReference<Object>(serviceRegistration);
+        assertTrue(this.bundleContext.ungetService(serviceReference));
+        serviceRegistration.unregister();
+        assertFalse(this.bundleContext.ungetService(serviceReference));
+    }
+
+    @Test
+    public void getAllServiceReferences() throws InvalidSyntaxException {
+        assertNull(this.bundleContext.getAllServiceReferences(null, null));
+    }
+
+    @Test
+    public void getServiceReferencesNullReturn() throws InvalidSyntaxException {
+        assertNull(this.bundleContext.getServiceReferences((String)null, null));
+    }
+
+    @Test
+    public void getServiceReferencesNullClassNullFilter() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertEquals(1, this.bundleContext.getServiceReferences((String)null, null).length);
+    }
+
+    @Test
+    public void getServiceReferencesNullFilter() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertEquals(1, this.bundleContext.getServiceReferences(Object.class.getName(), null).length);
+    }
+
+    @Test
+    public void getServiceReferencesWrongClass() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertNull(this.bundleContext.getServiceReferences(Exception.class.getName(), null));
+    }
+
+    @Test
+    public void getServiceReferences() throws InvalidSyntaxException {
+        this.bundleContext.addFilter(new TestFilter());
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertEquals(1, this.bundleContext.getServiceReferences(Object.class.getName(), "testFilter").length);
+    }
+
+    @Test
+    public void getServiceReferencesTyped() throws InvalidSyntaxException {
+        this.bundleContext.addFilter(new TestFilter());
+        this.bundleContext.registerService(Object.class, new Object(), null);
+        assertEquals(1, this.bundleContext.getServiceReferences(Object.class, "testFilter").size());
+    }
+
+    @Test
+    public void getServiceReferenceNoValues() throws InvalidSyntaxException {
+        assertNull(this.bundleContext.getServiceReference((String)null));
+    }
+
+    @Test
+    public void getServiceReferenceOneValue() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertNotNull(this.bundleContext.getServiceReference((String)null));
+    }
+
+    @Test
+    public void getServiceReferenceOneValueTyped() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertNotNull(this.bundleContext.getServiceReference(Object.class));
+    }
+
+    @Test
+    public void getServiceReferenceNoMatching() throws InvalidSyntaxException {
+        this.bundleContext.addFilter(new FalseTestFilter());
+        this.bundleContext.registerService(Object.class, new Object(), null);
+        assertEquals(0, this.bundleContext.getServiceReferences(Object.class, "falseTestFilter").size());
+    }
+
+    @Test
+    public void getServiceReferenceTwoValues() throws InvalidSyntaxException {
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        this.bundleContext.registerService(Object.class.getName(), new Object(), null);
+        assertNotNull(this.bundleContext.getServiceReference((String)null));
+    }
+
+    private static class TestBundleListener implements BundleListener {
+
+        private boolean called = false;
+
+        private List<BundleEvent> events = new ArrayList<BundleEvent>();
+
+        public void bundleChanged(BundleEvent event) {
+            this.called = true;
+            this.events.add(event);
+        }
+
+        public boolean getCalled() {
+            return called;
+        }
+
+        public BundleEvent[] getEvents() {
+            return events.toArray(new BundleEvent[events.size()]);
+        }
+
+    }
+
+    private static class TestFrameworkListener implements FrameworkListener {
+
+        public void frameworkEvent(FrameworkEvent event) {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static class TestServiceListener implements ServiceListener {
+
+        public void serviceChanged(ServiceEvent event) {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static class TestInputStream extends InputStream {
+
+        @Override
+        public int read() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static class TestFilter extends AbstractFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean match(ServiceReference<?> reference) {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean match(Dictionary<String, ?> dictionary) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean matchCase(Dictionary<String, ?> dictionary) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public String getFilterString() {
+            return "testFilter";
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean matches(Map<String, ?> map) {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static class FalseTestFilter extends TestFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean match(ServiceReference<?> reference) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public String getFilterString() {
+            return "falseTestFilter";
+        }
+
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleTests.java
new file mode 100644
index 0000000..6655fc4
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubBundleTests.java
@@ -0,0 +1,544 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.AdditionalAsserts.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+
+public class StubBundleTests {
+
+    private static final Long DEFAULT_BUNDLE_ID = Long.valueOf(1);
+
+    private static final String DEFAULT_SYMBOLIC_NAME = "org.eclipse.virgo.test.stubs.testbundle";
+
+    private static final Version DEFAULT_VERSION = Version.emptyVersion;
+
+    private static final String DEFAULT_LOCATION = "/";
+
+    private StubBundle bundle = new StubBundle();
+
+    @Test
+    public void initialState() throws IOException {
+        assertEquals(Bundle.STARTING, this.bundle.getState());
+        assertNotNull(this.bundle.getBundleContext());
+        assertEquals(0, this.bundle.getHeaders().size());
+        assertEquals(0, this.bundle.getHeaders("testLocale").size());
+        assertNull(this.bundle.getEntry("testPath"));
+        assertNull(this.bundle.getEntryPaths("testPath"));
+        assertTrue(this.bundle.hasPermission("testPermission"));
+        assertNull(this.bundle.getResource("testName"));
+        assertNull(this.bundle.getResources("testName"));
+        assertNull(this.bundle.findEntries("testPath", "testFilePattern", true));
+        assertNull(this.bundle.getRegisteredServices());
+        assertNull(this.bundle.getServicesInUse());
+
+        try {
+            this.bundle.loadClass("testClass");
+            fail();
+        } catch (ClassNotFoundException e) {
+
+        }
+    }
+
+    @Test
+    public void defaultIdNameVersionLocation() {
+        assertEquals(DEFAULT_BUNDLE_ID.longValue(), this.bundle.getBundleId());
+        assertEquals(DEFAULT_SYMBOLIC_NAME, this.bundle.getSymbolicName());
+        assertEquals(DEFAULT_VERSION, this.bundle.getVersion());
+        assertEquals(DEFAULT_LOCATION, this.bundle.getLocation());
+    }
+
+    @Test
+    public void nameVersion() {
+        StubBundle b1 = new StubBundle("testSymbolicName", new Version(1, 0, 0));
+        assertEquals(DEFAULT_BUNDLE_ID.longValue(), this.bundle.getBundleId());
+        assertEquals("testSymbolicName", b1.getSymbolicName());
+        assertEquals(new Version(1, 0, 0), b1.getVersion());
+        assertEquals(DEFAULT_LOCATION, this.bundle.getLocation());
+    }
+
+    @Test
+    public void idNameVersionLocation() {
+        StubBundle b1 = new StubBundle(2L, "testSymbolicName", new Version(1, 0, 0), "testLocation");
+        assertEquals(2L, b1.getBundleId());
+        assertEquals("testSymbolicName", b1.getSymbolicName());
+        assertEquals(new Version(1, 0, 0), b1.getVersion());
+        assertEquals("testLocation", b1.getLocation());
+    }
+
+    @Test
+    public void getCustomHeader() {
+        this.bundle.addHeader("testKey", "testValue");
+        assertEquals("testValue", this.bundle.getHeaders().get("testKey"));
+    }
+
+    @Test
+    public void getCustomLocalizedHeaders() {
+        Dictionary<String, String> testHeaders = new Hashtable<>();
+        this.bundle.setLocalizedHeaders(testHeaders);
+        assertEquals(testHeaders, this.bundle.getHeaders("testLocale"));
+    }
+
+    @Test
+    public void getLastModified() throws BundleException {
+        Long lastModified = this.bundle.getLastModified();
+        this.bundle.update();
+        assertTrue(lastModified < this.bundle.getLastModified());
+    }
+
+    @Test(expected = ClassNotFoundException.class)
+    public void loadClassNonExistent() throws ClassNotFoundException {
+        this.bundle.loadClass("testClass");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void loadClassUninstalled() throws BundleException, ClassNotFoundException {
+        this.bundle.addLoadClass("testClass", this.getClass());
+        this.bundle.uninstall();
+        this.bundle.loadClass("testClass");
+    }
+
+    @Test
+    public void loadClass() throws ClassNotFoundException {
+        this.bundle.addLoadClass("testClass", this.getClass());
+        assertNotNull(this.bundle.loadClass("testClass"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getEntryUninstalled() throws BundleException, MalformedURLException {
+        this.bundle.addEntry("testPath", new URL("http://localhost"));
+        this.bundle.uninstall();
+        this.bundle.getEntry("testPath");
+    }
+
+    @Test
+    public void getEntry() throws MalformedURLException {
+        assertNull(this.bundle.getEntry("testPath"));
+        this.bundle.addEntry("testPath", new URL("http://localhost"));
+        assertNotNull(this.bundle.getEntry("testPath"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getEntryPathsUninstalled() throws BundleException, MalformedURLException {
+        this.bundle.addEntryPaths("testPath", new TestEnumeration<String>());
+        this.bundle.uninstall();
+        this.bundle.getEntryPaths("testPath");
+    }
+
+    @Test
+    public void getEntryPaths() throws MalformedURLException {
+        assertNull(this.bundle.getEntryPaths("testPath"));
+        this.bundle.addEntryPaths("testPath", new TestEnumeration<String>());
+        assertNotNull(this.bundle.getEntryPaths("testPath"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void hasPermissionUninstalled() throws BundleException, MalformedURLException {
+        this.bundle.addPermission("testPermission", false);
+        this.bundle.uninstall();
+        this.bundle.hasPermission("testPermission");
+    }
+
+    @Test
+    public void hasPermission() throws MalformedURLException {
+        assertTrue(this.bundle.hasPermission("testPermission"));
+        this.bundle.addPermission("testPermission", false);
+        assertFalse(this.bundle.hasPermission("testPermission"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getResourceUninstalled() throws BundleException, MalformedURLException {
+        this.bundle.addResource("testName", new URL("http://localhost"));
+        this.bundle.uninstall();
+        this.bundle.getEntry("testName");
+    }
+
+    @Test
+    public void getResource() throws MalformedURLException {
+        assertNull(this.bundle.getResource("testName"));
+        this.bundle.addEntry("testName", new URL("http://localhost"));
+        assertNotNull(this.bundle.getEntry("testName"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getResourcesUninstalled() throws BundleException, IOException {
+        this.bundle.addResources("testName", new TestEnumeration<URL>());
+        this.bundle.uninstall();
+        this.bundle.getResources("testName");
+    }
+
+    @Test
+    public void getResources() throws IOException {
+        assertNull(this.bundle.getResources("testName"));
+        this.bundle.addResources("testName", new TestEnumeration<URL>());
+        assertNotNull(this.bundle.getResources("testName"));
+    }
+
+    @Test
+    public void findEntriesNoDelegate() {
+        assertNull(this.bundle.findEntries("testPath", "testFilePattern", true));
+    }
+
+    @Test
+    public void findEntries() {
+        this.bundle.setFindEntriesDelegate(new TestFindEntriesDelegate());
+        assertNotNull(this.bundle.findEntries("testPath", "testFilePattern", true));
+    }
+
+    @Test()
+    public void getRegisteredServicesEmpty() {
+        assertNull(this.bundle.getRegisteredServices());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getRegisteredServicesUninstalled() throws BundleException {
+        this.bundle.addRegisteredService(new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(this.bundle))));
+        this.bundle.uninstall();
+        this.bundle.getRegisteredServices();
+    }
+
+    @Test
+    public void getRegisteredServices() {
+        this.bundle.addRegisteredService(new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(this.bundle))));
+        assertNotNull(this.bundle.getRegisteredServices());
+    }
+
+    @Test()
+    public void getServicesInUseEmpty() {
+        assertNull(this.bundle.getServicesInUse());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getServicesInUseUninstalled() throws BundleException {
+        this.bundle.addServiceInUse(new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(this.bundle))));
+        this.bundle.uninstall();
+        this.bundle.getServicesInUse();
+    }
+
+    @Test
+    public void getServicesInUse() {
+        this.bundle.addServiceInUse(new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(this.bundle))));
+        assertNotNull(this.bundle.getServicesInUse());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void updateUninstall() throws BundleException {
+        this.bundle.uninstall();
+        this.bundle.update();
+    }
+
+    @Test
+    public void updateNoDelegate() throws BundleException {
+        this.bundle.setState(Bundle.STARTING);
+        this.bundle.update();
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void updateInputStreamNoDelegate() throws BundleException {
+        this.bundle.setState(Bundle.STARTING);
+        this.bundle.update(new TestInputStream());
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void updateActive() throws BundleException {
+        this.bundle.setState(Bundle.ACTIVE);
+        this.bundle.update();
+        assertEquals(Bundle.ACTIVE, this.bundle.getState());
+    }
+
+    @Test
+    public void updateStarting() throws BundleException {
+        this.bundle.setState(Bundle.STARTING);
+        this.bundle.update();
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void updateStopping() throws BundleException {
+        this.bundle.setState(Bundle.STOPPING);
+        this.bundle.update();
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+
+    }
+
+    @Test
+    public void updateInstalled() throws BundleException {
+        this.bundle.setState(Bundle.INSTALLED);
+        this.bundle.update();
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void updateResolved() throws BundleException {
+        this.bundle.setState(Bundle.RESOLVED);
+        this.bundle.update();
+        assertEquals(Bundle.INSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void update() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+        TestUpdateDelegate delegate = new TestUpdateDelegate();
+        this.bundle.setUpdateDelegate(delegate);
+        this.bundle.update();
+        assertTrue(listener.getCalled());
+        assertEquals(BundleEvent.UPDATED, listener.getEvents().get(0).getType());
+        assertTrue(delegate.updateCalled);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void uninstallUninstall() throws BundleException {
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+        this.bundle.uninstall();
+    }
+
+    @Test
+    public void uninstallActive() throws BundleException {
+        this.bundle.setState(Bundle.ACTIVE);
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void uninstallStarting() throws BundleException {
+        this.bundle.setState(Bundle.STARTING);
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void uninstallStopping() throws BundleException {
+        this.bundle.setState(Bundle.STOPPING);
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void uninstallInstalled() throws BundleException {
+        this.bundle.setState(Bundle.INSTALLED);
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void uninstallResolved() throws BundleException {
+        this.bundle.setState(Bundle.RESOLVED);
+        this.bundle.uninstall();
+        assertEquals(Bundle.UNINSTALLED, this.bundle.getState());
+    }
+
+    @Test
+    public void uninstall() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+        this.bundle.uninstall();
+        assertTrue(listener.getCalled());
+        assertEquals(BundleEvent.UNINSTALLED, listener.getEvents().get(0).getType());
+    }
+
+    @Test
+    public void stopNotActive() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+        this.bundle.stop();
+        assertEquals(Bundle.STARTING, this.bundle.getState());
+        assertFalse(listener.getCalled());
+    }
+
+    @Test
+    public void stop() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+        StubServiceReference<Object> reference = new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(this.bundle)));
+        reference.setBundle(this.bundle);
+        this.bundle.addRegisteredService(reference);
+        reference.addUsingBundles(this.bundle);
+        this.bundle.addServiceInUse(reference);
+        this.bundle.setState(Bundle.ACTIVE);
+
+        this.bundle.stop();
+
+        assertEquals(Bundle.RESOLVED, this.bundle.getState());
+        assertNull(reference.getBundle());
+        assertNull(this.bundle.getRegisteredServices());
+        assertNull(reference.getUsingBundles());
+        assertNull(this.bundle.getServicesInUse());
+        assertTrue(listener.getCalled());
+        assertEquals(BundleEvent.STOPPING, listener.getEvents().get(0).getType());
+        assertEquals(BundleEvent.STOPPED, listener.getEvents().get(1).getType());
+    }
+
+    @Test
+    public void startActive() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+        this.bundle.setState(Bundle.ACTIVE);
+        this.bundle.start();
+        assertEquals(Bundle.ACTIVE, this.bundle.getState());
+        assertFalse(listener.getCalled());
+    }
+
+    @Test
+    public void start() throws BundleException {
+        TestBundleListener listener = new TestBundleListener();
+        this.bundle.getBundleContext().addBundleListener(listener);
+
+        this.bundle.start();
+
+        assertEquals(Bundle.ACTIVE, this.bundle.getState());
+        assertTrue(listener.getCalled());
+        assertEquals(BundleEvent.STARTING, listener.getEvents().get(0).getType());
+        assertEquals(BundleEvent.STARTED, listener.getEvents().get(1).getType());
+    }
+
+    @Test
+    public void customBundleContext() {
+        StubBundleContext bundleContext = new StubBundleContext(this.bundle);
+        this.bundle.setBundleContext(bundleContext);
+        assertSame(bundleContext, this.bundle.getBundleContext());
+    }
+
+    @Test
+    public void testHashCode() {
+        StubBundle b2 = new StubBundle();
+        assertFalse(31 == b2.hashCode());
+    }
+
+    @Test
+    public void testEquals() {
+        assertTrue(this.bundle.equals(this.bundle));
+        assertFalse(this.bundle.equals(null));
+        assertFalse(this.bundle.equals(new Object()));
+
+        assertFalse(this.bundle.equals(new StubBundle(2L, DEFAULT_SYMBOLIC_NAME, DEFAULT_VERSION, DEFAULT_LOCATION)));
+        assertTrue(this.bundle.equals(new StubBundle()));
+    }
+
+    @Test
+    public void testToString() {
+        String toString = bundle.toString();
+        assertContains("id", toString);
+        assertContains("symbolic name", toString);
+        assertContains("version", toString);
+        assertContains("state", toString);
+    }
+
+    @Test
+    public void compareTo() {
+        assertEquals(0, this.bundle.compareTo(this.bundle));
+        assertEquals(0, this.bundle.compareTo(new StubBundle(DEFAULT_BUNDLE_ID, DEFAULT_SYMBOLIC_NAME, DEFAULT_VERSION, DEFAULT_LOCATION)));
+        assertTrue(0 != this.bundle.compareTo(new StubBundle(2L, DEFAULT_SYMBOLIC_NAME, DEFAULT_VERSION, DEFAULT_LOCATION)));
+        assertTrue(0 != this.bundle.compareTo(new StubBundle(DEFAULT_BUNDLE_ID, "testName", DEFAULT_VERSION, DEFAULT_LOCATION)));
+        assertTrue(0 != this.bundle.compareTo(new StubBundle(DEFAULT_BUNDLE_ID, DEFAULT_SYMBOLIC_NAME, new Version(1, 0, 0), DEFAULT_LOCATION)));
+        assertTrue(0 != this.bundle.compareTo(new StubBundle(DEFAULT_BUNDLE_ID, DEFAULT_SYMBOLIC_NAME, DEFAULT_VERSION, "testLocation")));
+        assertEquals(0, this.bundle.compareTo(new StubBundle()));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void compareToNull() {
+        this.bundle.compareTo(null);
+    }
+
+    @Test
+    public void testGetSignerCertificates() {
+        assertEquals(0, this.bundle.getSignerCertificates(0).size());
+        assertEquals(0, this.bundle.getSignerCertificates(7).size());
+    }
+
+    @Test
+    public void adapt() {
+        assertNull(this.bundle.adapt(null));
+        assertNull(this.bundle.adapt(BundleStartLevel.class));
+    }
+
+    @Test
+    public void getDataFile() {
+        assertNull(this.bundle.getDataFile(null));
+        assertNull(this.bundle.getDataFile("testFile"));
+    }
+
+    private static final class TestInputStream extends InputStream {
+
+        @Override
+        public int read() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static final class TestFindEntriesDelegate implements FindEntriesDelegate {
+
+        public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+            return new TestEnumeration<URL>();
+        }
+    }
+
+    private static class TestUpdateDelegate implements UpdateDelegate {
+
+        private volatile boolean updateCalled = false;
+
+        public void update(StubBundle bundle) throws BundleException {
+            this.updateCalled = true;
+        }
+    }
+
+    private static class TestEnumeration<S> implements Enumeration<S> {
+
+        public boolean hasMoreElements() {
+            throw new UnsupportedOperationException();
+        }
+
+        public S nextElement() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static class TestBundleListener implements BundleListener {
+
+        private List<BundleEvent> events = new ArrayList<BundleEvent>();
+
+        public void bundleChanged(BundleEvent event) {
+            this.events.add(event);
+        }
+
+        public List<BundleEvent> getEvents() {
+            return this.events;
+        }
+
+        public boolean getCalled() {
+            return this.events.size() != 0;
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceReferenceTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceReferenceTests.java
new file mode 100644
index 0000000..44302be
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceReferenceTests.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.AdditionalAsserts.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class StubServiceReferenceTests {
+
+    private StubServiceReference<Object> ref = new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+
+    @Test
+    public void initialState() {
+        assertNotNull(this.ref.getProperty(Constants.SERVICE_ID));
+        assertNotNull(this.ref.getProperty(Constants.SERVICE_RANKING));
+        assertNotNull(this.ref.getProperty(Constants.OBJECTCLASS));
+        assertEquals(3, this.ref.getPropertyKeys().length);
+        assertNull(this.ref.getUsingBundles());
+        assertFalse(this.ref.isAssignableTo(new StubBundle(), "testClassName"));
+    }
+
+    @Test
+    public void compareSameId() {
+        StubServiceReference<Object> r1 = new StubServiceReference<Object>(0L, 0, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        StubServiceReference<Object> r2 = new StubServiceReference<Object>(0L, 0, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        assertTrue(0 == r1.compareTo(r2));
+        ServiceReference<?>[] array = new ServiceReference<?>[] { r2, r1 };
+        Arrays.sort(array);
+        assertSame(r2, array[0]);
+        assertSame(r1, array[1]);
+    }
+
+    @Test
+    public void compareDifferentRanking() {
+        StubServiceReference<Object> r1 = new StubServiceReference<Object>(0L, 0, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        StubServiceReference<Object> r2 = new StubServiceReference<Object>(1L, 1, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        assertTrue(0 > r1.compareTo(r2));
+        assertTrue(0 < r2.compareTo(r1));
+
+        ServiceReference<?>[] array = new ServiceReference[] { r2, r1 };
+        Arrays.sort(array);
+        assertSame(r1, array[0]);
+        assertSame(r2, array[1]);
+    }
+
+    @Test
+    public void compareDifferentId() {
+        StubServiceReference<Object> r1 = new StubServiceReference<Object>(0L, 0, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        StubServiceReference<Object> r2 = new StubServiceReference<Object>(1L, 0, new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        assertTrue(0 > r1.compareTo(r2));
+        assertTrue(0 < r2.compareTo(r1));
+        ServiceReference<?>[] array = new ServiceReference[] { r2, r1 };
+        Arrays.sort(array);
+        assertSame(r1, array[0]);
+        assertSame(r2, array[1]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void compareNull() {
+        this.ref.compareTo(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void compareNonServiceReference() {
+        this.ref.compareTo(new Object());
+    }
+
+    @Test
+    public void defaultProperties() {
+        assertNotNull(this.ref.getProperty(Constants.SERVICE_ID));
+        assertNotNull(this.ref.getProperty(Constants.SERVICE_RANKING));
+        assertNotNull(this.ref.getProperty(Constants.OBJECTCLASS));
+        assertEquals(3, this.ref.getPropertyKeys().length);
+    }
+
+    @Test
+    public void usingBundlesZero() {
+        assertNull(this.ref.getUsingBundles());
+    }
+
+    @Test
+    public void usingBundlesNonZero() {
+        this.ref.addUsingBundles(new StubBundle());
+        assertEquals(1, this.ref.getUsingBundles().length);
+    }
+
+    @Test
+    public void isAssignableToFalse() {
+        assertFalse(this.ref.isAssignableTo(new StubBundle(), "testClass"));
+    }
+
+    @Test
+    public void isAssignableToTrue() {
+        StubBundle bundle = new StubBundle();
+        this.ref.putAssignableTo(bundle, "testClass");
+        assertTrue(this.ref.isAssignableTo(bundle, "testClass"));
+    }
+
+    @Test
+    public void removeUsingBundles() {
+        assertNull(this.ref.getUsingBundles());
+        StubBundle b = new StubBundle();
+        this.ref.addUsingBundles(b);
+        assertEquals(1, this.ref.getUsingBundles().length);
+        this.ref.removeUsingBundles(b);
+        assertNull(this.ref.getUsingBundles());
+    }
+
+    @Test
+    public void testHashCode() {
+        StubServiceReference<Object> ref2 = new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())));
+        assertFalse(31 == ref2.hashCode());
+    }
+
+    @Test
+    public void testEquals() {
+        assertTrue(this.ref.equals(this.ref));
+        assertFalse(this.ref.equals(null));
+        assertFalse(this.ref.equals(new Object()));
+
+        assertFalse(this.ref.equals(new StubServiceReference<Object>(new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle())))));
+        assertTrue(this.ref.equals(new StubServiceReference<Object>(this.ref.getServiceRegistration())));
+    }
+
+    @Test
+    public void testToString() {
+        String toString = ref.toString();
+        assertContains("id", toString);
+        assertContains("ranking", toString);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistrationTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistrationTests.java
new file mode 100644
index 0000000..56dd90d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/framework/StubServiceRegistrationTests.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.framework;
+
+import static org.eclipse.virgo.test.stubs.AdditionalAsserts.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+public class StubServiceRegistrationTests {
+
+    private StubServiceRegistration<Object> reg = new StubServiceRegistration<Object>(new StubBundleContext(new StubBundle()));
+
+    @Test
+    public void initialState() {
+        assertNotNull(this.reg.getReference().getBundle());
+        assertEquals(3, this.reg.getProperties().size());
+        assertNotNull(this.reg.getReference());
+    }
+
+    @Test
+    public void getCustomReference() {
+        StubServiceReference<Object> ref = new StubServiceReference<Object>(1L, 1, reg);
+        this.reg.setServiceReference(ref);
+        assertSame(ref, this.reg.getReference());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getReferenceUnregistered() {
+        this.reg.unregister();
+        this.reg.getReference();
+    }
+
+    @Test
+    public void getCustomProperties() {
+        TestServiceListener listener = new TestServiceListener();
+        this.reg.getBundleContext().addServiceListener(listener);
+        Dictionary<String, String> testDictionary = new Hashtable<String, String>();
+        testDictionary.put("testKey", "testValue");
+        this.reg.setProperties(testDictionary);
+        assertNotNull(listener.getEvent());
+        assertEquals(ServiceEvent.MODIFIED, listener.getEvent().getType());
+        assertEquals(4, this.reg.getProperties().size());
+        assertEquals("testValue", this.reg.getProperties().get("testKey"));
+    }
+
+    @Test
+    public void setPropertiesNull() {
+        Dictionary<String, Object> initial = this.reg.getProperties();
+        this.reg.setProperties(null);
+        assertEquals(initial, this.reg.getProperties());
+    }
+
+    @Test
+    public void unregister() throws BundleException {
+        this.reg.getBundleContext().getContextBundle().start();
+        TestServiceListener listener = new TestServiceListener();
+        this.reg.getBundleContext().addServiceListener(listener);
+        ServiceReference<?> ref = this.reg.getReference();
+        this.reg.unregister();
+        assertNotNull(listener.getEvent());
+        assertEquals(ServiceEvent.UNREGISTERING, listener.getEvent().getType());
+        assertNull(ref.getBundle());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void unregisterUnregistered() {
+        this.reg.unregister();
+        this.reg.unregister();
+    }
+
+    @Test
+    public void testToString() {
+        String toString = reg.toString();
+        assertContains("object classes", toString);
+        assertContains("unregistered", toString);
+        assertContains("properties", toString);
+    }
+
+    private static class TestServiceListener implements ServiceListener {
+
+        private ServiceEvent event = null;
+
+        public void serviceChanged(ServiceEvent event) {
+            this.event = event;
+        }
+
+        public ServiceEvent getEvent() {
+            return this.event;
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/internal/AssertTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/internal/AssertTests.java
new file mode 100644
index 0000000..fc2f00b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/internal/AssertTests.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.internal;
+
+import org.junit.Test;
+
+import org.eclipse.virgo.test.stubs.internal.Assert;
+
+public class AssertTests {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void assertNull() {
+        Assert.assertNotNull(null, "testArgument");
+    }
+
+    @Test
+    public void assertNotNull() {
+        Assert.assertNotNull(new Object(), "testArgument");
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraphTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraphTests.java
new file mode 100644
index 0000000..27e9817
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionDigraphTests.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2012 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+package org.eclipse.virgo.test.stubs.region;
+
+import org.eclipse.equinox.region.Region;
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+
+import static org.junit.Assert.assertEquals;
+
+
+public class StubRegionDigraphTests {
+
+	private StubRegionDigraph stubRegionDigraph;
+	
+	@Test
+	public void testRegionDigraaph() throws BundleException{
+		this.stubRegionDigraph = new StubRegionDigraph();
+		Region createRegion = this.stubRegionDigraph.createRegion("testRegion");
+		assertEquals(createRegion.getName(), "testRegion");
+		assertEquals(this.stubRegionDigraph, createRegion.getRegionDigraph());
+	}
+	
+	@Test
+	public void testGetRemoveRegion() throws BundleException {
+		this.stubRegionDigraph = new StubRegionDigraph();
+		this.stubRegionDigraph.createRegion("testRegion");
+		assertEquals(1, this.stubRegionDigraph.getRegions().size());
+		this.stubRegionDigraph.removeRegion(this.stubRegionDigraph.getRegion("testRegion"));
+		assertEquals(0, this.stubRegionDigraph.getRegions().size());
+	}
+	
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionTests.java
new file mode 100644
index 0000000..36af522
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/region/StubRegionTests.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2012 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+package org.eclipse.virgo.test.stubs.region;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.equinox.region.Region;
+import org.junit.Test;
+
+public class StubRegionTests {
+
+	private StubRegionDigraph stubRegionDigraph = new StubRegionDigraph();
+	
+	@Test
+	public void testRegion(){
+		Region region = new StubRegion("testRegion", this.stubRegionDigraph);
+		assertEquals("testRegion", region.getName());
+	}
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdminTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdminTests.java
new file mode 100644
index 0000000..b7d920d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationAdminTests.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.cm;
+
+import static org.eclipse.virgo.test.stubs.AdditionalAsserts.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.junit.Test;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+
+public class StubConfigurationAdminTests {
+
+    private final StubConfigurationAdmin configAdmin = new StubConfigurationAdmin();
+
+    @Test(expected = IllegalArgumentException.class)
+    public void createFactoryConfigNullInput() throws IOException {
+        configAdmin.createFactoryConfiguration(null);
+    }
+
+    @Test
+    public void createFactoryConfig() throws IOException {
+        Configuration config = configAdmin.createFactoryConfiguration("test");
+        assertNull(config.getBundleLocation());
+        assertNull(config.getProperties());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void createFactoryConfigWithLocationNullInput() throws IOException {
+        configAdmin.createFactoryConfiguration(null, null);
+    }
+
+    @Test
+    public void createFactoryConfigWithLocation() throws IOException {
+        Configuration config = configAdmin.createFactoryConfiguration("test", null);
+        assertNull(config.getBundleLocation());
+        assertNull(config.getProperties());
+
+        Configuration config1 = configAdmin.createFactoryConfiguration("test", "test");
+        assertEquals("test", config1.getBundleLocation());
+        assertNull(config1.getProperties());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void getConfigNullInput() throws IOException {
+        configAdmin.getConfiguration(null);
+    }
+
+    @Test
+    public void getConfig() throws IOException {
+        Configuration config = configAdmin.getConfiguration("test");
+        assertNull(config.getBundleLocation());
+        assertNull(config.getProperties());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void getConfigWithLocationNullInput() throws IOException {
+        configAdmin.getConfiguration(null, null);
+    }
+
+    @Test
+    public void getConfigWithLocationExists() throws IOException {
+        Configuration config = configAdmin.getConfiguration("test", null);
+        config.update(new Hashtable<String, Object>());
+
+        Configuration config1 = configAdmin.getConfiguration("test", null);
+        assertNotNull(config1.getProperties());
+    }
+
+    @Test
+    public void getConfigWithLocationDoesNotExist() throws IOException {
+        Configuration config = configAdmin.getConfiguration("test", null);
+        assertNull(config.getBundleLocation());
+        assertNull(config.getProperties());
+
+        Configuration config1 = configAdmin.getConfiguration("test1", "test");
+        assertEquals("test", config1.getBundleLocation());
+        assertNull(config1.getProperties());
+    }
+
+    @Test
+    public void listConfigurationsNullFilter() throws IOException, InvalidSyntaxException {
+        configAdmin.createConfiguration("test1").addProperty("key1", "value1");
+        configAdmin.createConfiguration("test2");
+        assertEquals(1, configAdmin.listConfigurations(null).length);
+    }
+
+    @Test
+    public void listConfigurationsNoMatches() throws IOException, InvalidSyntaxException {
+        assertNull(configAdmin.listConfigurations(null));
+    }
+
+    @Test
+    public void listConfigurations() throws IOException, InvalidSyntaxException {
+        configAdmin.createConfiguration("test1").addProperty("key", "value");
+        configAdmin.createConfiguration("test2");
+        assertEquals(1, configAdmin.listConfigurations("(key=value)").length);
+    }
+
+    @Test
+    public void testToString() {
+        StubConfigurationAdmin configAdmin1 = new StubConfigurationAdmin("test", new StubConfiguration("test"));
+        String toString = configAdmin1.toString();
+        assertContains("configurations", toString);
+        assertContains("pid", toString);
+        assertContains("factoryPid", toString);
+        assertContains("deleted", toString);
+
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationTests.java
new file mode 100644
index 0000000..0dcd2d1
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/cm/StubConfigurationTests.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.cm;
+
+import static org.eclipse.virgo.test.stubs.AdditionalAsserts.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+public class StubConfigurationTests {
+
+    private final StubConfiguration config = new StubConfiguration("test");
+
+    @Test
+    public void delete() throws IOException {
+        config.delete();
+        assertTrue(config.getDeleted());
+        StubConfiguration config1 = new StubConfiguration(null, "test");
+        config1.delete();
+        assertTrue(config1.getDeleted());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void deleteAfterDelete() throws IOException {
+        config.delete();
+        config.delete();
+    }
+
+    @Test
+    public void bundleLocation() {
+        assertNull(this.config.getBundleLocation());
+        this.config.setBundleLocation("test");
+        assertEquals("test", this.config.getBundleLocation());
+        this.config.setBundleLocation(null);
+        assertNull(this.config.getBundleLocation());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getBundleLocationAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.getBundleLocation();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void setBundleLocationAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.setBundleLocation("test");
+    }
+
+    @Test
+    public void factoryPidNull() {
+        assertNull(this.config.getFactoryPid());
+    }
+
+    @Test
+    public void factoryPid() {
+        Configuration config1 = new StubConfiguration(null, "test");
+        assertEquals("test", config1.getFactoryPid());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getFactoryPidAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.getFactoryPid();
+    }
+
+    @Test
+    public void pid() {
+        assertEquals("test", this.config.getPid());
+    }
+
+    @Test
+    public void pidNull() {
+        Configuration config1 = new StubConfiguration(null, "test");
+        assertNull(config1.getPid());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getPidAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.getPid();
+    }
+
+    @Test
+    public void addProperty() throws IOException {
+        this.config.update(new Hashtable<String, String>());
+        this.config.addProperty("test1", "test2");
+        assertEquals(2, this.config.getProperties().size());
+        assertEquals("test2", this.config.getProperties().get("test1"));
+    }
+
+    @Test
+    public void addPropertyNull() {
+        this.config.addProperty("test1", "test2");
+        assertEquals(2, this.config.getProperties().size());
+        assertEquals("test2", this.config.getProperties().get("test1"));
+    }
+
+    @Test
+    public void getProperties() throws IOException {
+        this.config.update(new Hashtable<String, String>());
+
+        Dictionary<String, Object> properties1 = this.config.getProperties();
+        Dictionary<String, Object> properties2 = this.config.getProperties();
+        assertNotSame(properties1, properties2);
+        properties2.put("test3", "test4");
+        assertFalse(properties2.equals(this.config.getProperties()));
+        assertNull(this.config.getProperties().get(ConfigurationAdmin.SERVICE_BUNDLELOCATION));
+    }
+
+    @Test
+    public void getPropertiesNull() {
+        assertNull(this.config.getProperties());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getPropertiesAfterDeleted() throws IOException {
+        this.config.delete();
+        this.config.getProperties();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void updateNull() throws IOException {
+        this.config.update(null);
+    }
+
+    @Test
+    public void updateAddProperties() throws IOException {
+        assertNull(this.config.getProperties());
+        this.config.update(new Hashtable<String, Object>());
+        assertEquals(1, this.config.getProperties().size());
+        assertEquals("test", this.config.getProperties().get(Constants.SERVICE_PID));
+
+        Configuration config1 = new StubConfiguration("test1", "test2");
+        config1.update(new Hashtable<String, Object>());
+        assertEquals(2, config1.getProperties().size());
+        assertEquals("test1", config1.getProperties().get(Constants.SERVICE_PID));
+        assertEquals("test2", config1.getProperties().get(ConfigurationAdmin.SERVICE_FACTORYPID));
+    }
+
+    @Test
+    public void updateOverwriteProperties() throws IOException {
+        assertNull(this.config.getProperties());
+
+        Hashtable<String, String> properties = new Hashtable<>();
+        properties.put(Constants.SERVICE_PID, "test2");
+        properties.put(ConfigurationAdmin.SERVICE_FACTORYPID, "test3");
+
+        this.config.update(properties);
+        assertEquals(1, this.config.getProperties().size());
+        assertEquals("test", this.config.getProperties().get(Constants.SERVICE_PID));
+
+        Configuration config1 = new StubConfiguration("test1", "test2");
+        config1.update(properties);
+        assertEquals(2, config1.getProperties().size());
+        assertEquals("test1", config1.getProperties().get(Constants.SERVICE_PID));
+        assertEquals("test2", config1.getProperties().get(ConfigurationAdmin.SERVICE_FACTORYPID));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void updatePropertiesAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.update(new Hashtable<String, Object>());
+    }
+
+    @Test
+    public void update() throws IOException {
+        this.config.update();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void updateAfterDelete() throws IOException {
+        this.config.delete();
+        this.config.update();
+    }
+
+    @Test
+    public void testEquals() {
+        assertTrue(this.config.equals(this.config));
+        assertFalse(this.config.equals(null));
+        assertFalse(this.config.equals(new Object()));
+        assertTrue(this.config.equals(new StubConfiguration("test")));
+        assertFalse(this.config.equals(new StubConfiguration("test2")));
+    }
+
+    @Test
+    public void testHashCode() {
+        assertEquals("test".hashCode(), this.config.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        String toString = config.toString();
+        assertContains("pid", toString);
+        assertContains("factoryPid", toString);
+        assertContains("deleted", toString);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContextTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContextTests.java
new file mode 100755
index 0000000..12394c0
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/component/StubComponentContextTests.java
@@ -0,0 +1,106 @@
+/*******************************************************************************

+ * Copyright (c) 2008, 2010 SAP AG

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *   SAP AG - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.virgo.test.stubs.service.component;

+

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertNotNull;

+import static org.junit.Assert.assertTrue;

+

+import org.junit.Test;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.ServiceReference;

+

+import org.eclipse.virgo.test.stubs.framework.StubBundleContext;

+

+public class StubComponentContextTests {

+

+    private StubBundleContext bundleContext = new StubBundleContext();

+

+    private StubComponentContext componentContext = new StubComponentContext(this.bundleContext);

+

+    @Test

+    public void getProperties() {

+        assertNotNull(this.componentContext.getProperties());

+        assertTrue(0 != this.componentContext.getProperties().size());

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void locateServiceWithName() {

+        this.componentContext.locateService("testName");

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void locateServiceWithNameAndServiceReference() {

+        this.componentContext.locateService("testName", new ServiceReference<Object>() {

+

+            public int compareTo(Object reference) {

+                throw new UnsupportedOperationException();

+            }

+

+            public Bundle getBundle() {

+                throw new UnsupportedOperationException();

+            }

+

+            public Object getProperty(String key) {

+                return null;

+            }

+

+            public String[] getPropertyKeys() {

+                throw new UnsupportedOperationException();

+            }

+

+            public Bundle[] getUsingBundles() {

+                throw new UnsupportedOperationException();

+            }

+

+            public boolean isAssignableTo(Bundle bundle, String className) {

+                throw new UnsupportedOperationException();

+            }

+        });

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void locateServices() {

+        this.componentContext.locateServices("testName");

+    }

+

+    @Test

+    public void getBundleContext() {

+        assertEquals(this.bundleContext, this.componentContext.getBundleContext());

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void getUsingBundle() {

+        this.componentContext.getUsingBundle();

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void getComponentInstance() {

+        this.componentContext.getComponentInstance();

+    }

+

+    @Test

+    public void enableComponent() {

+        this.componentContext.enableComponent("testName");

+    }

+

+    @Test

+    public void disableComponent() {

+        this.componentContext.disableComponent("testName");

+    }

+

+    @Test(expected = UnsupportedOperationException.class)

+    public void getServiceReference() {

+        this.componentContext.getServiceReference();

+    }

+

+}

diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdminTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdminTests.java
new file mode 100644
index 0000000..9b084fd
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/StubEventAdminTests.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.event;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.Thread.State;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+
+public class StubEventAdminTests {
+
+    private final StubEventAdmin eventAdmin = new StubEventAdmin();
+    
+    private final Dictionary<String, ?> properties = createProperties();
+    
+    private final Dictionary<String, ?> expectedProperties = createProperties();
+    
+    private Dictionary<String, ?> createProperties() {
+        Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        
+        properties.put("booleanArray", new boolean[] {false, true});
+        properties.put("byteArray", new byte[] {1, 2});
+        properties.put("charArray", new char[] {'a', 'b'});
+        properties.put("doubleArray", new double[] {1.0d});
+        properties.put("floatArray", new float[] {2.45f});
+        properties.put("intArray", new int[] {1, 2, 3});
+        properties.put("longArray", new long[] {1L, 2L});
+        properties.put("shortArray", new short[] {5, 9, 18});
+        
+        return properties;
+    }
+
+    @Test
+    public void postEvent() {
+        Event posted = new Event("topic", this.properties);
+        Event expected = new Event("topic", this.expectedProperties);
+
+        this.eventAdmin.postEvent(posted);
+        assertTrue(this.eventAdmin.awaitPostingOfEvent(expected, 1000));
+    }
+
+    @Test
+    public void sendEvent() {
+        Event sent = new Event("topic", this.properties);
+        Event expected = new Event("topic", this.expectedProperties);
+
+        this.eventAdmin.sendEvent(sent);
+        assertTrue(this.eventAdmin.awaitSendingOfEvent(expected, 1000));
+    }
+
+    @Test
+    public void awaitSendingTimeout() {
+        Event expected = new Event("topic", this.expectedProperties);
+        
+        this.eventAdmin.sendEvent(new Event("differentTopic", (Map<String,?>)null));
+        this.eventAdmin.postEvent(expected);
+        
+        long start = System.currentTimeMillis();
+        assertFalse(this.eventAdmin.awaitSendingOfEvent(expected, 500));
+        long delta = System.currentTimeMillis() - start;
+        assertTrue("Delta of " + delta + " was less than expected", delta >= 500);
+    }
+
+    @Test
+    public void awaitPostingTimeout() {
+        Event expected = new Event("topic", this.expectedProperties);
+        
+        this.eventAdmin.postEvent(new Event("differentTopic", (Map<String,?>)null));
+        this.eventAdmin.sendEvent(expected);
+        
+        long start = System.currentTimeMillis();
+        assertFalse(this.eventAdmin.awaitPostingOfEvent(expected, 500));
+        long delta = System.currentTimeMillis() - start;
+        assertTrue("Delta of " + delta + " was less than expected", delta >= 500);
+    }
+
+    @Test
+    public void postingOfEventWhileWaiting() throws InterruptedException {
+        Event posted = new Event("topic", this.properties);
+        final Event expected = new Event("topic", this.expectedProperties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                if (eventAdmin.awaitPostingOfEvent(expected, 10000)) {
+                    latch.countDown();
+                }
+            }
+        });
+
+        awaitingThread.start();
+
+        this.eventAdmin.postEvent(posted);
+
+        assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void sendingOfEventWhileWaiting() throws InterruptedException {
+        Event posted = new Event("topic", this.properties);
+        final Event expected = new Event("topic", this.expectedProperties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                if (eventAdmin.awaitSendingOfEvent(expected, 10000)) {
+                    latch.countDown();
+                }
+            }
+        });
+
+        awaitingThread.start();
+
+        this.eventAdmin.sendEvent(posted);
+
+        assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void removalOfPostedEventWhenSuccessfullyAwaited() {
+        Event posted = new Event("topic", this.properties);
+        Event expected = new Event("topic", this.expectedProperties);
+
+        this.eventAdmin.postEvent(posted);
+        assertTrue(this.eventAdmin.awaitPostingOfEvent(expected, 1000));
+        assertFalse(this.eventAdmin.awaitPostingOfEvent(expected, 1));
+    }
+
+    @Test
+    public void removalOfSentEventWhenSuccessfullyAwaited() {
+        Event sent = new Event("topic", this.properties);
+        Event expected = new Event("topic", this.expectedProperties);
+
+        this.eventAdmin.sendEvent(sent);
+        assertTrue(this.eventAdmin.awaitSendingOfEvent(expected, 1000));
+        assertFalse(this.eventAdmin.awaitSendingOfEvent(expected, 1));
+    }
+
+    @Test(timeout = 30000)
+    public void awaitingThreadsCanBeInterrupted() throws InterruptedException {
+        final Event expected = new Event("topic", this.expectedProperties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                eventAdmin.awaitSendingOfEvent(expected, Long.MAX_VALUE);
+                latch.countDown();
+            }
+        });
+
+        awaitingThread.start();
+
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+
+        ThreadInfo threadInfo = null;
+
+        while ((threadInfo = threadBean.getThreadInfo(awaitingThread.getId())) == null || threadInfo.getThreadState() != State.TIMED_WAITING) {
+            Thread.sleep(10);
+        }
+
+        awaitingThread.interrupt();
+
+        latch.await();
+    }
+    
+    @Test
+    public void postEventMatchingOnTopic() {
+        Event posted = new Event("topic", this.properties);
+
+        this.eventAdmin.postEvent(posted);
+        assertNotNull(this.eventAdmin.awaitPostingOfEvent("topic", 1000));
+    }
+
+    @Test
+    public void sendEventMatchingOnTopic() {
+        Event sent = new Event("topic", this.properties);
+
+        this.eventAdmin.sendEvent(sent);
+        assertNotNull(this.eventAdmin.awaitSendingOfEvent("topic", 1000));
+    }
+
+    @Test
+    public void awaitSendingTimeoutMatchingOnTopic() {
+        Event expected = new Event("topic", this.expectedProperties);
+        
+        this.eventAdmin.sendEvent(new Event("differentTopic", (Map<String,?>)null));
+        this.eventAdmin.postEvent(expected);
+        
+        long start = System.currentTimeMillis();
+        assertNull(this.eventAdmin.awaitSendingOfEvent("topic", 500));
+        long delta = System.currentTimeMillis() - start;
+        assertTrue("Delta of " + delta + " was less than expected", delta >= 500);
+    }
+
+    @Test
+    public void awaitPostingTimeoutMatchingOnTopic() {
+        Event expected = new Event("topic", this.expectedProperties);
+        
+        this.eventAdmin.postEvent(new Event("differentTopic", (Map<String,?>)null));
+        this.eventAdmin.sendEvent(expected);
+        
+        long start = System.currentTimeMillis();
+        assertNull(this.eventAdmin.awaitPostingOfEvent("topic", 500));
+        long delta = System.currentTimeMillis() - start;
+        assertTrue("Delta of " + delta + " was less than expected", delta >= 500);
+    }
+
+    @Test
+    public void postingOfEventWhileWaitingMatchingOnTopic() throws InterruptedException {
+        Event posted = new Event("topic", this.properties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                if (eventAdmin.awaitPostingOfEvent("topic", 10000) != null) {
+                    latch.countDown();
+                }
+            }
+        });
+
+        awaitingThread.start();
+
+        this.eventAdmin.postEvent(posted);
+
+        assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void sendingOfEventWhileWaitingMatchingOnTopic() throws InterruptedException {
+        Event posted = new Event("topic", this.properties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                if (eventAdmin.awaitSendingOfEvent("topic", 10000) != null) {
+                    latch.countDown();
+                }
+            }
+        });
+
+        awaitingThread.start();
+
+        this.eventAdmin.sendEvent(posted);
+
+        assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void removalOfPostedEventWhenSuccessfullyAwaitedMatchingOnTopic() {
+        Event posted = new Event("topic", this.properties);
+
+        this.eventAdmin.postEvent(posted);
+        assertNotNull(this.eventAdmin.awaitPostingOfEvent("topic", 1000));
+        assertNull(this.eventAdmin.awaitPostingOfEvent("topic", 1));
+    }
+
+    @Test
+    public void removalOfSentEventWhenSuccessfullyAwaitedMatchingOnTopic() {
+        Event sent = new Event("topic", this.properties);
+
+        this.eventAdmin.sendEvent(sent);
+        assertNotNull(this.eventAdmin.awaitSendingOfEvent("topic", 1000));
+        assertNull(this.eventAdmin.awaitSendingOfEvent("topic", 1));
+    }
+
+    @Test(timeout = 30000)
+    public void awaitingThreadsCanBeInterruptedWhenMatchingOnTopic() throws InterruptedException {        
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        Thread awaitingThread = new Thread(new Runnable() {
+
+            public void run() {
+                eventAdmin.awaitSendingOfEvent("topic", Long.MAX_VALUE);
+                latch.countDown();
+            }
+        });
+
+        awaitingThread.start();
+
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+
+        ThreadInfo threadInfo = null;
+
+        while ((threadInfo = threadBean.getThreadInfo(awaitingThread.getId())) == null || threadInfo.getThreadState() != State.TIMED_WAITING) {
+            Thread.sleep(10);
+        }
+
+        awaitingThread.interrupt();
+
+        latch.await();
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtilsTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtilsTests.java
new file mode 100644
index 0000000..6842a88
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/service/event/internal/EventUtilsTests.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.service.event.internal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+/**
+ */
+public class EventUtilsTests {
+    
+    @Test
+    public void equalObjectArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new Object[] {new Integer(5), Boolean.FALSE, "apple"}, new Object[] {new Integer(5), Boolean.FALSE, "apple"}));
+    }
+    
+    @Test
+    public void unequalObjectArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new Object[] {new Integer(5), Boolean.FALSE, "apple"}, new Object[] {new Integer(578), Boolean.FALSE, "orange"}));
+    }
+    
+    @Test
+    public void equalBooleanArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new boolean[] {false, true}, new boolean[] {false, true}));
+    }
+    
+    @Test
+    public void unequalBooleanArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new boolean[] {false, true}, new boolean[] {true, true}));
+    }
+
+    @Test
+    public void equalByteArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new byte[] {1, 2, 3}, new byte[] {1, 2, 3}));
+    }
+    
+    @Test
+    public void unequalByteArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new byte[] {1, 2, 3}, new byte[] {1, 3}));
+    }
+    
+    @Test
+    public void equalCharArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new char[] {'a', '1'}, new char[] {'a', '1'}));
+    }
+    
+    @Test
+    public void unequalCharArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new char[] {'a', '1'}, new char[] {'a'}));
+    }
+    
+    @Test
+    public void equalDoubleArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new double[] {1.0d, 3.45673d}, new double[] {1.0d, 3.45673d}));
+    }
+    
+    @Test
+    public void unequalDoubleArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new double[] {1.0d, 3.45673d}, new double[] {1.0d, 30.45673d}));
+    }
+    
+    @Test
+    public void equalFloatArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new float[] {1.0f, 3.45673f}, new float[] {1.0f, 3.45673f}));
+    }
+    
+    @Test
+    public void unequalFloatArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new float[] {1.0f, 3.45673f}, new float[] {1.0f, 0.45673f}));
+    }
+    
+    @Test
+    public void equalIntArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new int[] {-5, 0, 93}, new int[] {-5, 0, 93}));
+    }
+    
+    @Test
+    public void unequalIntArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new int[] {-5, 0, 93}, new int[] {-5, 0, 9356}));
+    }
+    
+    @Test
+    public void equalLongArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new long[] {-5, 0, 93}, new long[] {-5, 0, 93}));
+    }
+    
+    @Test
+    public void unequalLongArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new long[] {-5, 0, 93}, new long[] {-5, 0, 9356}));
+    }
+    
+    @Test
+    public void equalShortArrays() {
+        assertTrue(EventUtils.arraysAreEqual(new short[] {-5, 0, 93}, new short[] {-5, 0, 93}));
+    }
+    
+    @Test
+    public void unequalShortArrays() {
+        assertFalse(EventUtils.arraysAreEqual(new short[] {-5, 0, 93}, new short[] {-5, 0, 9356}));
+    }
+    
+    @Test
+    public void eventsWithDifferentTopicsAreNotEqual() {
+        assertFalse(EventUtils.eventsAreEqual(new Event("foo", (Map<String, ?>)null), new Event("bar", (Map<String, ?>)null)));
+    }
+    
+    @Test
+    public void eventsWithMatchingTopicsAndNoPropertiesAreEqual() {
+        assertTrue(EventUtils.eventsAreEqual(new Event("foo", (Map<String, ?>)null), new Event("foo", (Map<String, ?>)null)));
+    }
+    
+    @Test
+    public void eventsWithMatchingTopicsAndMatchingPropertiesAreEqual() {
+        assertTrue(EventUtils.eventsAreEqual(new Event("foo", createProperties()), new Event("foo", createProperties())));
+    }
+    
+    @Test
+    public void eventsWithMatchingTopicsAndDifferentNumberOfPropertiesAreNotEqual() {
+        Dictionary<String, ?> properties = createProperties();
+        properties.remove("byteArray");
+        
+        assertFalse(EventUtils.eventsAreEqual(new Event("foo", createProperties()), new Event("foo", properties)));
+    }
+    
+    @Test
+    public void eventsWithMatchingTopicsAndDifferentPropertiesAreNotEqual() {
+        Dictionary<String, Object> properties = createProperties();
+        properties.put("byteArray", new byte[] {6});
+        
+        assertFalse(EventUtils.eventsAreEqual(new Event("foo", createProperties()), new Event("foo", properties)));
+        
+        properties = createProperties();
+        properties.put("object", "bravo");
+        
+        assertFalse(EventUtils.eventsAreEqual(new Event("foo", createProperties()), new Event("foo", properties)));
+    }
+    
+    private Dictionary<String, Object> createProperties() {
+        Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        
+        properties.put("object", "alpha");
+        properties.put("booleanArray", new boolean[] {false, true});
+        properties.put("byteArray", new byte[] {1, 2});
+        properties.put("charArray", new char[] {'a', 'b'});
+        properties.put("doubleArray", new double[] {1.0d});
+        properties.put("floatArray", new float[] {2.45f});
+        properties.put("intArray", new int[] {1, 2, 3});
+        properties.put("longArray", new long[] {1L, 2L});
+        properties.put("shortArray", new short[] {5, 9, 18});
+        
+        return properties;
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/FalseFilterTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/FalseFilterTests.java
new file mode 100644
index 0000000..4cde55e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/FalseFilterTests.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+import org.eclipse.virgo.test.stubs.support.FalseFilter;
+
+public class FalseFilterTests {
+
+    private final FalseFilter filter = new FalseFilter();
+
+    @Test
+    public void match() {
+        assertFalse(filter.match((Dictionary<String, ?>) null));
+        assertFalse(filter.match((ServiceReference<?>) null));
+        assertFalse(filter.matchCase(null));
+        assertFalse(filter.matches((Map<String, ?>) null));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("", new FalseFilter().getFilterString());
+        assertEquals("testFilterString", new FalseFilter("testFilterString").toString());
+    }
+    
+    @Test
+    public void hashCodeEqualsToStringsHashCode() {
+        FalseFilter falseFilter = new FalseFilter("testFilterString");
+        assertEquals(falseFilter.hashCode(), falseFilter.toString().hashCode());
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilterTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilterTests.java
new file mode 100644
index 0000000..a18c9db
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/ObjectClassFilterTests.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class ObjectClassFilterTests {
+
+    private final ObjectClassFilter classFilter = new ObjectClassFilter(Object.class);
+
+    private final ObjectClassFilter classNameFilter = new ObjectClassFilter(Object.class.getName());
+
+    @Test
+    public void matchServiceReference() {
+        ServiceReference<Object> objectServiceReference = new ServiceReference<Object>() {
+
+            public int compareTo(Object reference) {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle getBundle() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Object getProperty(String key) {
+                return new String[] { Object.class.getName() };
+            }
+
+            public String[] getPropertyKeys() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle[] getUsingBundles() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        assertTrue(this.classFilter.match(objectServiceReference));
+        assertTrue(this.classNameFilter.match(objectServiceReference));
+
+        ServiceReference<Object> exceptionServiceReference = new ServiceReference<Object>() {
+
+            public int compareTo(Object reference) {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle getBundle() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Object getProperty(String key) {
+                return new String[] { Exception.class.getName() };
+            }
+
+            public String[] getPropertyKeys() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle[] getUsingBundles() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        assertFalse(this.classFilter.match(exceptionServiceReference));
+        assertFalse(this.classNameFilter.match(exceptionServiceReference));
+    }
+
+    @Test
+    public void matches() {
+        Map<String, String[]> classNameMap = new HashMap<String, String[]>();
+        classNameMap.put(Constants.OBJECTCLASS, new String[] { Object.class.getName(), Object.class.getName() });
+
+        assertTrue(this.classFilter.matches(classNameMap));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void matchesWithEmptyMap() {
+        this.classFilter.matches(new HashMap<String, Object>());
+    }
+
+    @Test
+    public void matchDictionaryTrue() {
+        Dictionary<String, Object> d1 = new Hashtable<>();
+        d1.put(Constants.OBJECTCLASS, new String[] { Object.class.getName() });
+        assertTrue(this.classFilter.match(d1));
+        assertTrue(this.classFilter.matchCase(d1));
+
+        assertTrue(this.classNameFilter.match(d1));
+        assertTrue(this.classNameFilter.matchCase(d1));
+    }
+
+    @Test
+    public void matchDictionaryFalse() {
+        Dictionary<String, Object> d1 = new Hashtable<>();
+        d1.put(Constants.OBJECTCLASS, new String[] { Exception.class.getName() });
+        assertFalse(this.classFilter.match(d1));
+        assertFalse(this.classFilter.matchCase(d1));
+
+        assertFalse(this.classNameFilter.match(d1));
+        assertFalse(this.classNameFilter.matchCase(d1));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("(objectClass=java.lang.Object)", this.classFilter.toString());
+        assertEquals("(objectClass=java.lang.Object)", this.classNameFilter.toString());
+    }
+
+    @Test
+    public void hashCodeEqualsToStringsHashCode() {
+        assertEquals(this.classFilter.hashCode(), this.classFilter.toString().hashCode());
+        assertEquals(this.classFilter.hashCode(), this.classNameFilter.toString().hashCode());
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/PropertiesFilterTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/PropertiesFilterTests.java
new file mode 100644
index 0000000..3f8f471
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/PropertiesFilterTests.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+public class PropertiesFilterTests {
+
+    @Test
+    public void matchServiceReference() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("testKey", "testValue");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        assertFalse(filter.match(new ServiceReference<Object>() {
+
+            public int compareTo(Object reference) {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle getBundle() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Object getProperty(String key) {
+                return null;
+            }
+
+            public String[] getPropertyKeys() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle[] getUsingBundles() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                throw new UnsupportedOperationException();
+            }
+        }));
+
+        assertFalse(filter.match(new ServiceReference<Object>() {
+
+            public int compareTo(Object reference) {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle getBundle() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Object getProperty(String key) {
+                return "badValue";
+            }
+
+            public String[] getPropertyKeys() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle[] getUsingBundles() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                throw new UnsupportedOperationException();
+            }
+        }));
+
+        assertTrue(filter.match(new ServiceReference<Object>() {
+
+            public int compareTo(Object reference) {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle getBundle() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Object getProperty(String key) {
+                return "testValue";
+            }
+
+            public String[] getPropertyKeys() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Bundle[] getUsingBundles() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                throw new UnsupportedOperationException();
+            }
+        }));
+    }
+
+    @Test
+    public void matchesSameProperties() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("testKey", "testValue");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+
+        assertTrue(filter.matches(properties));
+
+        properties.put("newTestKey", "newTestValue");
+        assertTrue(filter.matches(properties));
+
+        properties.remove("testKey"); //removes from filter
+        assertTrue(filter.matches(properties));
+    }
+
+    @Test
+    public void matchesNewProperties() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("testKey", "testValue");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+
+        Map<String, Object> newProperties = new HashMap<String, Object>();
+        newProperties.put("testKey", "testValue");
+
+        assertTrue(filter.matches(newProperties));
+
+        newProperties.put("newTestKey", "newTestValue");
+        assertTrue(filter.matches(newProperties));
+
+        newProperties.remove("testKey");
+        assertFalse(filter.matches(newProperties));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void matchesNull() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("testKey", "testValue");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        filter.matches(null);
+    }
+
+    @Test
+    public void matchDictionary() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("testKey", "testValue");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+
+        Dictionary<String, Object> d1 = new Hashtable<>();
+        assertFalse(filter.match(d1));
+        assertFalse(filter.matchCase(d1));
+
+        Dictionary<String, Object> d2 = new Hashtable<>();
+        d2.put("testKey", "badValue");
+        assertFalse(filter.match(d2));
+        assertFalse(filter.matchCase(d2));
+
+        Dictionary<String, Object> d3 = new Hashtable<>();
+        d3.put("testKey", "testValue");
+        assertTrue(filter.match(d3));
+        assertTrue(filter.matchCase(d3));
+    }
+
+    @Test
+    public void getFilterStringEmpty() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        assertEquals("", filter.getFilterString());
+    }
+
+    @Test
+    public void getFilterStringOne() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("key", "value");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        assertEquals("(key=value)", filter.toString());
+    }
+
+    @Test
+    public void getFilterStringMoreThanOne() {
+        Map<String, Object> properties = new TreeMap<String, Object>();
+        properties.put("key1", "value1");
+        properties.put("key2", "value2");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        assertEquals("(&(key1=value1)(key2=value2))", filter.toString());
+    }
+    
+    @Test
+    public void hashCodeEqualsToStringsHashCode() {
+        Map<String, Object> properties = new TreeMap<String, Object>();
+        properties.put("key1", "value1");
+        properties.put("key2", "value2");
+        PropertiesFilter filter = new PropertiesFilter(properties);
+        
+        assertEquals(filter.hashCode(), filter.toString().hashCode());
+    }
+
+    @Test
+    public void fromFilterStringEmpty() throws InvalidSyntaxException {
+        String filterString = "";
+        PropertiesFilter filter = new PropertiesFilter(filterString);
+        assertEquals(filterString, filter.getFilterString());
+    }
+
+    @Test
+    public void fromFilterStringOne() throws InvalidSyntaxException {
+        String filterString = "(key=value)";
+        PropertiesFilter filter = new PropertiesFilter(filterString);
+        assertEquals(filterString, filter.getFilterString());
+    }
+
+    @Test
+    public void fromFilterStringMoreThanOne() throws InvalidSyntaxException {
+        String filterString = "(&(key1=value1)(key2=value2))";
+        PropertiesFilter filter = new PropertiesFilter(filterString);
+        assertEquals(filterString, filter.getFilterString());
+    }
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/TrueFilterTests.java b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/TrueFilterTests.java
new file mode 100644
index 0000000..96c8304
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/java/org/eclipse/virgo/test/stubs/support/TrueFilterTests.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.stubs.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+public class TrueFilterTests {
+
+    private final TrueFilter filter = new TrueFilter();
+
+    @Test
+    public void match() {
+        assertTrue(filter.match((Dictionary<String, ?>) null));
+        assertTrue(filter.match((ServiceReference<?>) null));
+        assertTrue(filter.matchCase(null));
+        assertTrue(filter.matches((Map<String, ?>) null));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("", new TrueFilter().toString());
+        assertEquals("testFilterString", new TrueFilter("testFilterString").toString());
+    }
+
+    @Test
+    public void hashCodeEqualsToStringsHashCode() {
+        assertEquals(this.filter.hashCode(), this.filter.toString().hashCode());
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.stubs/src/test/resources/.gitignore b/test/org.eclipse.virgo.test.stubs/src/test/resources/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/src/test/resources/.gitignore
diff --git a/test/org.eclipse.virgo.test.stubs/template.mf b/test/org.eclipse.virgo.test.stubs/template.mf
new file mode 100644
index 0000000..0e5eeb3
--- /dev/null
+++ b/test/org.eclipse.virgo.test.stubs/template.mf
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-Name: OSGi Framework Test Stub Implementations
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: org.eclipse.virgo.test.stubs
+Bundle-Version: ${version}
+Import-Template: 
+ org.junit.*;version="[4.4.0, 5.0.0)",
+ org.osgi.framework.*;version="0",
+ org.osgi.service.cm;version="[1.3.0, 1.4.0)",
+ org.osgi.service.event;version="[1.3.0, 1.4.0)",
+ org.osgi.service.component;version="[1.1.0, 1.2.0)"
+Import-Package: 
+ org.aspectj.lang;version="[1.6.3.RELEASE, 2.0.0)",
+ org.aspectj.lang.annotation;version="[1.6.3.RELEASE, 2.0.0)",
+ org.aspectj.lang.reflect;version="[1.6.3.RELEASE, 2.0.0)",
+ org.aspectj.internal.lang.annotation;version="[1.6.3.RELEASE, 2.0.0)",
+ org.aspectj.runtime.internal;version="[1.6.3.RELEASE, 2.0.0)",
+ org.aspectj.runtime.reflect;version="[1.6.3.RELEASE, 2.0.0)"
+Excluded-Exports: 
+ org.eclipse.virgo.test.stubs.framework.aspects,
+ org.eclipse.virgo.test.stubs.internal,
+ org.eclipse.virgo.test.stubs.service.cm.aspects
diff --git a/test/org.eclipse.virgo.test.test/build.xml b/test/org.eclipse.virgo.test.test/build.xml
new file mode 100644
index 0000000..2ea71dd
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/build.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="org.eclipse.virgo.test.test">
+
+	<property file="${basedir}/../build.properties"/>
+	<property file="${basedir}/../build.versions"/>
+	<import file="${basedir}/../virgo-build/standard/default.xml"/>
+</project>
diff --git a/test/org.eclipse.virgo.test.test/ivy.xml b/test/org.eclipse.virgo.test.test/ivy.xml
new file mode 100644
index 0000000..87bae5b
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/ivy.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
+<ivy-module
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
+		version="1.3">
+
+	<info organisation="${project.organisation}" module="${ant.project.name}"/>
+
+	<configurations>
+		<include file="${virgo.build.dir}/common/default-ivy-configurations.xml"/>
+	</configurations>
+
+	<publications>
+		<artifact name="${ant.project.name}"/>
+		<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
+	</publications>
+
+	<dependencies>
+        <dependency org="org.junit" name="com.springsource.org.junit" rev="${org.junit}" />
+		<dependency org="org.eclipse.virgo.mirrored" name="org.eclipse.osgi" rev="${org.eclipse.osgi}" conf="compile->compile"/>
+		<dependency org="org.eclipse.virgo.test" name="org.eclipse.virgo.test.framework" rev="latest.integration"/>
+	</dependencies>
+
+</ivy-module>
diff --git a/test/org.eclipse.virgo.test.test/src/main/java/.gitignore b/test/org.eclipse.virgo.test.test/src/main/java/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/main/java/.gitignore
diff --git a/test/org.eclipse.virgo.test.test/src/main/resources/.gitignore b/test/org.eclipse.virgo.test.test/src/main/resources/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/main/resources/.gitignore
diff --git a/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BasicOsgiEnvironmentTests.java b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BasicOsgiEnvironmentTests.java
new file mode 100644
index 0000000..489e6a7
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BasicOsgiEnvironmentTests.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.virgo.test.framework.OsgiTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleReference;
+
+
+@RunWith(OsgiTestRunner.class)
+public class BasicOsgiEnvironmentTests {
+
+	@Test
+	public void testRunningInOsgi() {
+		assertTrue((getClass().getClassLoader() instanceof BundleReference));
+	}
+	
+	@Test
+	public void testCanGetBundleContext() {
+		BundleReference br = (BundleReference)getClass().getClassLoader();
+		Bundle bundle = br.getBundle();
+		
+		assertNotNull(bundle);
+		assertNotNull(bundle.getBundleContext());
+	}
+	
+	@Test
+	public void testCustomConfigurationFileApplied() {
+		BundleReference br = (BundleReference)getClass().getClassLoader();
+		Bundle bundle = br.getBundle();
+		
+		String property = bundle.getBundleContext().getProperty("test.property");
+		assertEquals("foo", property);
+	}
+}
diff --git a/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BundleDependenciesTests.java b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BundleDependenciesTests.java
new file mode 100644
index 0000000..c7e404d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/BundleDependenciesTests.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.test;
+
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.virgo.test.framework.BundleDependencies;
+import org.eclipse.virgo.test.framework.BundleEntry;
+import org.eclipse.virgo.test.framework.OsgiTestRunner;
+import org.eclipse.virgo.test.framework.TestFrameworkUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+
+@BundleDependencies(entries = {@BundleEntry("file:./src/test/resources/test-bundle")})
+@RunWith(OsgiTestRunner.class)
+public class BundleDependenciesTests {
+
+	@Test
+	public void testBundleInstalled() {
+		BundleContext bundleContext = TestFrameworkUtils.getBundleContextForTestClass(getClass());
+		Bundle[] bundles = bundleContext.getBundles();
+		boolean found = false;
+		for (Bundle bundle : bundles) {
+			if("test.bundle".equals(bundle.getSymbolicName()))  {
+			    found = true;
+			    break;
+			}
+		}
+		assertTrue(found);
+	}
+}
diff --git a/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/TestFrameworkUtilsTests.java b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/TestFrameworkUtilsTests.java
new file mode 100644
index 0000000..838ba82
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/java/org/eclipse/virgo/test/test/TestFrameworkUtilsTests.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.test;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.virgo.test.framework.OsgiTestRunner;
+import org.eclipse.virgo.test.framework.TestFrameworkUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.BundleContext;
+
+
+@RunWith(OsgiTestRunner.class)
+public class TestFrameworkUtilsTests {
+
+	@Test
+	public void testGetBundleContextForTestClass() {
+		BundleContext bundleContext = TestFrameworkUtils.getBundleContextForTestClass(getClass());
+		assertNotNull(bundleContext);
+	}
+	
+	@Test(expected=IllegalArgumentException.class)
+	public void testGetBundleContextForNonTestClass() {
+		TestFrameworkUtils.getBundleContextForTestClass(String.class);
+	}
+}
diff --git a/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/MANIFEST.MF b/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2be327d
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: org.eclipse.virgo.test.test
+Bundle-Version: 3.0.0
+Import-Package: org.junit,
+ org.osgi.framework,
+ org.eclipse.virgo.test.framework
+
diff --git a/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/test.config.properties b/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/test.config.properties
new file mode 100644
index 0000000..ccf6352
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/resources/META-INF/test.config.properties
@@ -0,0 +1,14 @@
+test.property=foo
+user.home=dummy
+#Ivy config properties
+org.eclipse.virgo.test.ivy.settings=file:src/test/resources/ivysettings.xml
+
+#Extra properties files
+org.eclipse.virgo.test.properties.include=file:../build.versions, file:../build.properties
+
+osgi.java.profile=file:src/test/resources/java-server.profile
+osgi.java.profile.bootdelegation=override
+osgi.parentClassloader=fwk
+osgi.context.bootdelegation=false
+osgi.compatibility.bootdelegation=false
+osgi.configuration.area=target
\ No newline at end of file
diff --git a/test/org.eclipse.virgo.test.test/src/test/resources/java-server.profile b/test/org.eclipse.virgo.test.test/src/test/resources/java-server.profile
new file mode 100644
index 0000000..74afd8c
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/resources/java-server.profile
@@ -0,0 +1,208 @@
+org.osgi.framework.system.packages = \
+ javax.accessibility,\
+ javax.activation,\
+ javax.activation;version="1.1.0",\
+ javax.activity,\
+ javax.annotation,\
+ javax.annotation;version="1.0.0",\
+ javax.annotation.processing,\
+ javax.crypto,\
+ javax.crypto.interfaces,\
+ javax.crypto.spec,\
+ javax.imageio,\
+ javax.imageio.event,\
+ javax.imageio.metadata,\
+ javax.imageio.plugins.bmp,\
+ javax.imageio.plugins.jpeg,\
+ javax.imageio.spi,\
+ javax.imageio.stream,\
+ javax.jws,\
+ javax.jws;version="2.0",\
+ javax.jws.soap,\
+ javax.jws.soap;version="2.0",\
+ javax.lang.model,\
+ javax.lang.model.element,\
+ javax.lang.model.type,\
+ javax.lang.model.util,\
+ javax.management,\
+ javax.management.loading,\
+ javax.management.modelmbean,\
+ javax.management.monitor,\
+ javax.management.openmbean,\
+ javax.management.relation,\
+ javax.management.remote,\
+ javax.management.remote.rmi,\
+ javax.management.timer,\
+ javax.naming,\
+ javax.naming.directory,\
+ javax.naming.event,\
+ javax.naming.ldap,\
+ javax.naming.spi,\
+ javax.net,\
+ javax.net.ssl,\
+ javax.print,\
+ javax.print.attribute,\
+ javax.print.attribute.standard,\
+ javax.print.event,\
+ javax.rmi,\
+ javax.rmi.CORBA,\
+ javax.rmi.ssl,\
+ javax.script,\
+ javax.script;version="1.1",\
+ javax.security.auth,\
+ javax.security.auth.callback,\
+ javax.security.auth.kerberos,\
+ javax.security.auth.login,\
+ javax.security.auth.spi,\
+ javax.security.auth.x500,\
+ javax.security.cert,\
+ javax.security.sasl,\
+ javax.sound.midi,\
+ javax.sound.midi.spi,\
+ javax.sound.sampled,\
+ javax.sound.sampled.spi,\
+ javax.sql,\
+ javax.sql.rowset,\
+ javax.sql.rowset.serial,\
+ javax.sql.rowset.spi,\
+ javax.swing,\
+ javax.swing.border,\
+ javax.swing.colorchooser,\
+ javax.swing.event,\
+ javax.swing.filechooser,\
+ javax.swing.plaf,\
+ javax.swing.plaf.basic,\
+ javax.swing.plaf.metal,\
+ javax.swing.plaf.multi,\
+ javax.swing.plaf.synth,\
+ javax.swing.table,\
+ javax.swing.text,\
+ javax.swing.text.html,\
+ javax.swing.text.html.parser,\
+ javax.swing.text.rtf,\
+ javax.swing.tree,\
+ javax.swing.undo,\
+ javax.tools,\
+ javax.transaction,\
+ javax.transaction;version="1.0.1",\
+ javax.transaction;version="1.1.0",\
+ javax.transaction.xa,\
+ javax.transaction.xa;version="1.0.1",\
+ javax.transaction.xa;version="1.1.0",\
+ javax.xml,\
+ javax.xml;version="1.0.1",\
+ javax.xml.bind,\
+ javax.xml.bind;version="2.0",\
+ javax.xml.bind.annotation,\
+ javax.xml.bind.annotation;version="2.0",\
+ javax.xml.bind.annotation.adapters,\
+ javax.xml.bind.annotation.adapters;version="2.0",\
+ javax.xml.bind.attachment,\
+ javax.xml.bind.attachment;version="2.0",\
+ javax.xml.bind.helpers,\
+ javax.xml.bind.helpers;version="2.0",\
+ javax.xml.bind.util,\
+ javax.xml.bind.util;version="2.0",\
+ javax.xml.crypto,\
+ javax.xml.crypto;version="1.0",\
+ javax.xml.crypto.dom,\
+ javax.xml.crypto.dom;version="1.0",\
+ javax.xml.crypto.dsig,\
+ javax.xml.crypto.dsig;version="1.0",\
+ javax.xml.crypto.dsig.dom,\
+ javax.xml.crypto.dsig.dom;version="1.0",\
+ javax.xml.crypto.dsig.keyinfo,\
+ javax.xml.crypto.dsig.keyinfo;version="1.0",\
+ javax.xml.crypto.dsig.spec,\
+ javax.xml.crypto.dsig.spec;version="1.0",\
+ javax.xml.datatype,\
+ javax.xml.namespace,\
+ javax.xml.parsers,\
+ javax.xml.soap,\
+ javax.xml.soap;version="1.3.0",\
+ javax.xml.stream,\
+ javax.xml.stream;version="1.0.1",\
+ javax.xml.stream.events,\
+ javax.xml.stream.events;version="1.0.1",\
+ javax.xml.stream.util,\
+ javax.xml.stream.util;version="1.0.1",\
+ javax.xml.transform,\
+ javax.xml.transform.dom,\
+ javax.xml.transform.sax,\
+ javax.xml.transform.stax,\
+ javax.xml.transform.stream,\
+ javax.xml.validation,\
+ javax.xml.ws,\
+ javax.xml.ws;version="2.1.1",\
+ javax.xml.ws.handler,\
+ javax.xml.ws.handler;version="2.1.1",\
+ javax.xml.ws.handler.soap,\
+ javax.xml.ws.handler.soap;version="2.1.1",\
+ javax.xml.ws.http,\
+ javax.xml.ws.http;version="2.1.1",\
+ javax.xml.ws.soap,\
+ javax.xml.ws.soap;version="2.1.1",\
+ javax.xml.ws.spi,\
+ javax.xml.ws.spi;version="2.1.1",\
+ javax.xml.xpath,\
+ org.ietf.jgss,\
+ org.omg.CORBA,\
+ org.omg.CORBA_2_3,\
+ org.omg.CORBA_2_3.portable,\
+ org.omg.CORBA.DynAnyPackage,\
+ org.omg.CORBA.ORBPackage,\
+ org.omg.CORBA.portable,\
+ org.omg.CORBA.TypeCodePackage,\
+ org.omg.CosNaming,\
+ org.omg.CosNaming.NamingContextExtPackage,\
+ org.omg.CosNaming.NamingContextPackage,\
+ org.omg.Dynamic,\
+ org.omg.DynamicAny,\
+ org.omg.DynamicAny.DynAnyFactoryPackage,\
+ org.omg.DynamicAny.DynAnyPackage,\
+ org.omg.IOP,\
+ org.omg.IOP.CodecFactoryPackage,\
+ org.omg.IOP.CodecPackage,\
+ org.omg.Messaging,\
+ org.omg.PortableInterceptor,\
+ org.omg.PortableInterceptor.ORBInitInfoPackage,\
+ org.omg.PortableServer,\
+ org.omg.PortableServer.CurrentPackage,\
+ org.omg.PortableServer.POAManagerPackage,\
+ org.omg.PortableServer.POAPackage,\
+ org.omg.PortableServer.portable,\
+ org.omg.PortableServer.ServantLocatorPackage,\
+ org.omg.SendingContext,\
+ org.omg.stub.java.rmi,\
+ org.w3c.dom,\
+ org.w3c.dom.bootstrap,\
+ org.w3c.dom.css,\
+ org.w3c.dom.events,\
+ org.w3c.dom.html,\
+ org.w3c.dom.ls,\
+ org.w3c.dom.ranges,\
+ org.w3c.dom.stylesheets,\
+ org.w3c.dom.traversal,\
+ org.w3c.dom.views ,\
+ org.xml.sax,\
+ org.xml.sax.ext,\
+ org.xml.sax.helpers
+org.osgi.framework.bootdelegation = \
+ com_cenqua_clover,\
+ com.cenqua.*,\
+ com.yourkit.*,\
+ org.eclipse.virgo.osgi.extensions.*,\
+ com.sun.*,\
+ javax.xml.*,\
+ org.apache.xerces.jaxp.*,\
+ org.w3c.*,\
+ org.xml.*,\
+ sun.*
+org.osgi.framework.executionenvironment = \
+ OSGi/Minimum-1.0,\
+ OSGi/Minimum-1.1,\
+ J2SE-1.3,\
+ J2SE-1.4,\
+ J2SE-1.5,\
+ JavaSE-1.6
+osgi.java.profile.name = SpringSource-dm-Server-Java6
diff --git a/test/org.eclipse.virgo.test.test/src/test/resources/test-bundle/META-INF/MANIFEST.MF b/test/org.eclipse.virgo.test.test/src/test/resources/test-bundle/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..45b2d40
--- /dev/null
+++ b/test/org.eclipse.virgo.test.test/src/test/resources/test-bundle/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Manifest-Version: 1
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: test.bundle
+Bundle-Version: 1.0
+
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractServerCommandThread.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractServerCommandThread.java
new file mode 100644
index 0000000..67a6084
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractServerCommandThread.java
@@ -0,0 +1,46 @@
+
+package org.eclipse.virgo.test.tools;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.util.Map;
+
+public abstract class AbstractServerCommandThread implements Runnable {
+
+    protected OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
+
+    protected Process process = null;
+
+    protected ProcessBuilder pb = null;
+
+    protected String binDir;
+
+    public AbstractServerCommandThread(String binDir) {
+        this.binDir = binDir;
+    }
+
+    protected void redirectProcessOutput() throws IOException {
+        InputStream is = process.getInputStream();
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(is, UTF_8))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                System.out.println(line);
+            }
+        }
+    }
+
+    protected void createAndStartProcess(String fileName) throws IOException {
+        pb = new ProcessBuilder(new String[] { fileName });
+        pb.redirectErrorStream(true);
+        Map<String, String> env = pb.environment();
+        env.put("JAVA_HOME", System.getProperty("java.home"));
+        process = pb.start();
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractSmokeTests.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractSmokeTests.java
new file mode 100644
index 0000000..ca4fdcb
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/AbstractSmokeTests.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.tools;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.virgo.test.tools.JmxUtils.isDefaultJmxPortAvailable;
+import static org.eclipse.virgo.test.tools.JmxUtils.waitForVirgoServerShutdownFully;
+import static org.eclipse.virgo.test.tools.JmxUtils.waitForVirgoServerStartFully;
+import static org.eclipse.virgo.test.tools.VirgoServerShutdownThread.shutdown;
+import static org.eclipse.virgo.test.tools.VirgoServerStartupThread.startup;
+import static org.eclipse.virgo.util.io.FileCopyUtils.copy;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+
+public abstract class AbstractSmokeTests {
+
+    private String srcDir = "src/smokeTest/resources";
+
+    private File bundlesDir = null;
+
+    protected abstract String getVirgoFlavor();
+
+    @Before
+    public void startServer() throws Exception {
+        if (!isDefaultJmxPortAvailable()) {
+            System.out.println("Port not available. Waiting for a few seconds.");
+            TimeUnit.SECONDS.sleep(10);
+            if (!isDefaultJmxPortAvailable()) {
+                System.out.println("Port still not available. Trying to shutdown running Virgo.");
+                shutdown(ServerUtils.getBinDir(getVirgoFlavor()));
+                if (!isDefaultJmxPortAvailable()) {
+                    System.out.println("Port still not available. Giving up.");
+                }
+            }
+        }
+        assertTrue("Default JMX port in use. Is another Virgo server still up and running?!", isDefaultJmxPortAvailable());
+        startup(ServerUtils.getBinDir(getVirgoFlavor()));
+        assertTrue("Server '" + getVirgoFlavor() + "' not started properly.", waitForVirgoServerStartFully());
+    }
+
+    @After
+    public void shutdownServer() throws Exception {
+        shutdown(ServerUtils.getBinDir(getVirgoFlavor()));
+        assertTrue("Server '" + getVirgoFlavor() + "' not shut down properly.", waitForVirgoServerShutdownFully());
+    }
+
+    private File setupBundleResourcesDir() {
+        if (bundlesDir == null) {
+            File testExpanded = new File("./" + srcDir);
+            bundlesDir = new File(testExpanded, "bundles");
+        }
+        return bundlesDir;
+    }
+
+    public void deployTestBundles(String flavor, String bundleName) throws Exception {
+        setupBundleResourcesDir();
+        copy(new File(bundlesDir, bundleName), new File(ServerUtils.getPickupDir(flavor), bundleName));
+        // allow the Server to finish the deployment
+        SECONDS.sleep(10);
+    }
+
+    public void undeployTestBundles(String flavor, String bundleName) throws Exception {
+        setupBundleResourcesDir();
+        File file = new File(ServerUtils.getPickupDir(flavor), bundleName);
+        if (file.exists()) {
+            file.delete();
+            // allow the Server to finish the undeployment
+            SECONDS.sleep(5);
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/JmxUtils.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/JmxUtils.java
new file mode 100644
index 0000000..25d18e2
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/JmxUtils.java
@@ -0,0 +1,314 @@
+
+package org.eclipse.virgo.test.tools;
+
+import static java.time.Instant.now;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.eclipse.virgo.util.io.NetUtils;
+
+// TODO - rework static usage of virgoHome
+public class JmxUtils {
+
+    public static File virgoHome;
+
+    private static final Duration HALF_SECOND = Duration.ofMillis(500);
+
+    private static final Duration THIRTY_SECONDS = Duration.ofSeconds(30);
+
+    private static final Duration TWO_MINUTES = Duration.ofMinutes(2);
+
+    private static final String ARTIFACT_BUNDLE = "bundle";
+
+    private static MBeanServerConnection connection = null;
+
+    private final static int JMX_DEFAULT_PORT = 9875;
+
+    private static final String JMXURL = "service:jmx:rmi:///jndi/rmi://localhost:" + JMX_DEFAULT_PORT + "/jmxrmi";
+
+    private static final String KEYSTORE = "/configuration/keystore";
+
+    private static final String KEYPASSWORD = "changeit";
+
+    private static final String DEPLOYER_MBEAN_NAME = "org.eclipse.virgo.kernel:category=Control,type=Deployer";
+
+    private static final String[] DEPLOYMENT_IDENTITY_FIELDS = new String[] { "type", "symbolicName", "version" };
+
+    private final static String STATUS_STARTED = "STARTED";
+
+    private final static String STATUS_STARTING = "STARTING";
+
+    public static boolean isDefaultJmxPortAvailable() {
+        return NetUtils.isPortAvailable(JMX_DEFAULT_PORT);
+    }
+
+    public static boolean isKernelStarted() {
+        return JmxUtils.STATUS_STARTED.equals(getKernelStatus());
+    }
+
+    public static boolean waitForVirgoServerStartFully() throws Exception {
+        Instant start = Instant.now();
+        while (start.plus(THIRTY_SECONDS).isAfter(Instant.now())) {
+            try {
+                String currentKernelStatus = getKernelStatus();
+                System.out.println("Current kernel status: '" + currentKernelStatus + "'");
+                switch (currentKernelStatus) {
+                    case STATUS_STARTED:
+                        // wait for startup to complete
+                        SECONDS.sleep(20);
+                        return true;
+                    case STATUS_STARTING:
+                    default:
+                        break;
+                }
+            } catch (IllegalStateException e) {
+                // ignore JMX related exception and try again?
+            }
+            SECONDS.sleep(1);
+        }
+        // startup failed
+        return false;
+    }
+
+    public static boolean waitForVirgoServerShutdownFully() throws Exception {
+        Instant start = now();
+        while (start.plus(THIRTY_SECONDS).isAfter(now())) {
+            if (isDefaultJmxPortAvailable()) {
+                // allow some more time to finish shutdown
+                SECONDS.sleep(10);
+                return true;
+            }
+            try {
+                String currentKernelStatus = getKernelStatus();
+                System.out.println("Current kernel status: '" + currentKernelStatus + "'");
+                if (!getKernelStatus().equals(STATUS_STARTED)) {
+                }
+            } catch (IllegalStateException e) {
+                // ignore JMX related exception and try again?
+            }
+            SECONDS.sleep(1);
+        }
+        // shutdown failed
+        return false;
+    }
+
+    public static void waitForMBean(String mBeanName) throws Exception {
+        waitForMBean(mBeanName, HALF_SECOND, TWO_MINUTES);
+    }
+
+    public static void waitForMBean(String mBeanName, Duration sleepDuration, Duration duration) throws Exception {
+        Instant start = Instant.now();
+        boolean mbeanStatus = false;
+        while (start.plus(duration).isAfter(Instant.now())) {
+            try {
+                ObjectName objectName = new ObjectName(mBeanName);
+                System.out.println("Looking for JMX object '" + objectName + "'");
+                mbeanStatus = getMBeanServerConnection().isRegistered(objectName);
+                if (mbeanStatus) {
+                    return;
+                }
+            } catch (IOException e) {
+                // swallow and retry - No JMX server available (yet)
+            }
+            Thread.sleep(sleepDuration.toMillis());
+        }
+        fail(String.format("After %d s and %d ns, artifact %s mbean Status was", duration.getSeconds(), duration.getNano(), mBeanName) + mbeanStatus);
+    }
+
+    public static void waitForArtifactInUserRegion(String type, String name, String version, long interval, long duration) throws Exception {
+        long startTime = System.currentTimeMillis();
+        boolean mbeanStatus = false;
+        while (System.currentTimeMillis() - startTime < duration) {
+            try {
+                ObjectName objectName = getObjectName(type, name, version);
+                System.out.println("Looking for JMX object '" + objectName + "'");
+                mbeanStatus = getMBeanServerConnection().isRegistered(objectName);
+                if (mbeanStatus) {
+                    return;
+                }
+            } catch (IOException e) {
+                // swallow and retry
+            }
+            Thread.sleep(interval);
+        }
+        fail(String.format("After %d ms, artifact %s mbean Status was", duration, name) + mbeanStatus);
+    }
+
+    public static void waitForArtifactInKernelRegion(String type, String name, String version, long interval, long duration) throws Exception {
+        long startTime = System.currentTimeMillis();
+        boolean mbeanStatus = false;
+        while (System.currentTimeMillis() - startTime < duration) {
+            try {
+                ObjectName objectName = getObjectNameInKernelRegion(type, name, version);
+                System.out.println("Looking for JMX object '" + objectName + "'");
+                mbeanStatus = getMBeanServerConnection().isRegistered(objectName);
+                if (mbeanStatus) {
+                    return;
+                }
+            } catch (IOException e) {
+                // swallow and retry
+            }
+            Thread.sleep(interval);
+        }
+        fail(String.format("After %d ms, artifact %s mbean Status was", duration, name) + mbeanStatus);
+    }
+
+    public static MBeanServerConnection getMBeanServerConnection() throws IOException {
+        String severDir = null;
+
+        String[] creds = { "admin", "admin" };
+        Map<String, String[]> env = new HashMap<String, String[]>();
+
+        try {
+            severDir = virgoHome.getCanonicalPath();
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to get canonical path of server directory '" + virgoHome + "'.");
+        }
+
+        env.put(JMXConnector.CREDENTIALS, creds);
+        System.setProperty("javax.net.ssl.trustStore", severDir + KEYSTORE);
+        System.setProperty("javax.net.ssl.trustStorePassword", KEYPASSWORD);
+        JMXServiceURL url;
+        try {
+            url = new JMXServiceURL(JMXURL);
+            connection = JMXConnectorFactory.connect(url, env).getMBeanServerConnection();
+        } catch (MalformedURLException e) {
+            throw new IllegalStateException("Failed to create JMX connection to '" + JMXURL + "'.", e);
+        }
+        return connection;
+    }
+
+    public static ObjectName getObjectName(String type, String name, String version) throws MalformedObjectNameException {
+        return new ObjectName(String.format(
+            "org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=%s,name=%s,version=%s,region=org.eclipse.virgo.region.user", type, name,
+            version));
+    }
+
+    public static ObjectName getObjectNameInKernelRegion(String type, String name, String version) throws MalformedObjectNameException {
+        return new ObjectName(String.format(
+            "org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=%s,name=%s,version=%s,region=org.eclipse.equinox.region.kernel", type, name,
+            version));
+    }
+
+    public static void deploy(File uploadedFile) throws Exception {
+        deploy(java.util.Collections.singletonList(uploadedFile));
+    }
+
+    public static void deploy(List<File> uploadedFiles) throws Exception {
+        waitForMBean(DEPLOYER_MBEAN_NAME, HALF_SECOND, TWO_MINUTES);
+        ObjectName objectName = new ObjectName(DEPLOYER_MBEAN_NAME);
+        for (File file : uploadedFiles) {
+            URI uri = file.toURI();
+            System.out.println("Deploying file '" + uri + "' on '" + objectName + "'");
+            Object invoke = getMBeanServerConnection().invoke(objectName, "deploy", new Object[] { uri.toString() },
+                new String[] { String.class.getName() });
+            getDeploymentIdentity(invoke);
+        }
+    }
+
+    // Taken from org.eclipse.virgo.management.console.UploadServlet.getDeploymentIdentity()
+    private static String getDeploymentIdentity(Object deploymentIdentity) {
+        StringBuilder builder = new StringBuilder();
+        if (deploymentIdentity instanceof CompositeDataSupport) {
+            CompositeDataSupport deploymentIdentityInstance = (CompositeDataSupport) deploymentIdentity;
+            Object[] all = deploymentIdentityInstance.getAll(DEPLOYMENT_IDENTITY_FIELDS);
+            builder.append(all[0]);
+            builder.append(" - ").append(all[1]);
+            builder.append(": ").append(all[2]);
+        }
+        return builder.toString();
+    }
+
+    public static void waitForArtifactState(String type, String name, String version, String state, long interval, long duration) throws Exception {
+        long startTime = System.currentTimeMillis();
+        String artifactstate = null;
+        while (System.currentTimeMillis() - startTime < duration) {
+            artifactstate = getMBeanServerConnection().getAttribute(getObjectName(type, name, version), "State").toString();
+            if (artifactstate.equals(state)) {
+                return;
+            }
+            Thread.sleep(interval);
+        }
+        fail(String.format("After %d ms, artifact %s state was", duration, name) + artifactstate);
+    }
+
+    public static void assertArtifactExists(String type, String name, String version) throws Exception {
+        assertTrue(String.format("Artifact %s:%s:%s does not exist", type, name, version),
+            getMBeanServerConnection().isRegistered(JmxUtils.getObjectName(type, name, version)));
+    }
+
+    public static void assertBundleArtifactExists(String name, String version) throws Exception {
+        assertTrue(String.format("Artifact %s:%s:%s does not exist", ARTIFACT_BUNDLE, name, version),
+            getMBeanServerConnection().isRegistered(JmxUtils.getObjectName(ARTIFACT_BUNDLE, name, version)));
+    }
+
+    public static void assertBundleArtifactExistsInKernelRegion(String name, String version) throws Exception {
+        assertTrue(String.format("Artifact %s:%s:%s does not exist", ARTIFACT_BUNDLE, name, version),
+            getMBeanServerConnection().isRegistered(JmxUtils.getObjectNameInKernelRegion(ARTIFACT_BUNDLE, name, version)));
+    }
+
+    public static void assertBundleArtifactState(String name, String version, String state) throws Exception {
+        assertArtifactState(ARTIFACT_BUNDLE, name, version, state);
+    }
+
+    public static void assertBundleArtifactStateInKernelRegion(String name, String version, String state) throws Exception {
+        assertArtifactStateInKernelRegion(ARTIFACT_BUNDLE, name, version, state);
+    }
+
+    public static void assertArtifactState(String type, String name, String version, String state) throws Exception {
+        assertEquals(String.format("admin console plan artifact %s:%s:%s is not in state %s", type, name, version, state), state,
+            getMBeanServerConnection().getAttribute(JmxUtils.getObjectName(type, name, version), "State"));
+    }
+
+    public static void assertArtifactStateInKernelRegion(String type, String name, String version, String state) throws Exception {
+        assertEquals(String.format("admin console plan artifact %s:%s:%s is not in state %s", type, name, version, state), state,
+            getMBeanServerConnection().getAttribute(JmxUtils.getObjectNameInKernelRegion(type, name, version), "State"));
+    }
+
+    public static void uninstall(String type, String name, String version) throws Exception {
+        getMBeanServerConnection().invoke(getObjectName(type, name, version), "uninstall", new Object[] {}, new String[] {});
+    }
+
+    public static String getKernelStatus() {
+        try {
+            return (String) getMBeanServerConnection().getAttribute(new ObjectName("org.eclipse.virgo.kernel:type=KernelStatus"), "Status");
+        } catch (AttributeNotFoundException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        } catch (InstanceNotFoundException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        } catch (MalformedObjectNameException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        } catch (MBeanException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        } catch (ReflectionException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to get server status.", e);
+        }
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/ServerUtils.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/ServerUtils.java
new file mode 100644
index 0000000..f141573
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/ServerUtils.java
@@ -0,0 +1,32 @@
+
+package org.eclipse.virgo.test.tools;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ServerUtils {
+
+    public static File getHome(String flavor) {
+        return new File("../build/install/virgo-" + flavor + "/").getAbsoluteFile();
+    }
+
+    public static String getBinDir(String flavor) {
+        return getVirgoServerSubdirectory(getHome(flavor), "bin");
+    }
+
+    public static String getPickupDir(String flavor) {
+        return getVirgoServerSubdirectory(getHome(flavor), "pickup");
+    }
+    
+    private static String getVirgoServerSubdirectory(File virgoHome, String subdirectoryName) {
+        if (virgoHome.isDirectory()) {
+            try {
+                return new File(virgoHome, subdirectoryName).getCanonicalPath();
+            } catch (IOException e) {
+                throw new IllegalStateException("No subdirectory '" + subdirectoryName + "' found withing '" + virgoHome + "'", e);
+            }
+        }
+        throw new IllegalStateException("No subdirectory '" + subdirectoryName + "' found within '" + virgoHome + "'");
+    }
+
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/UrlWaitLatch.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/UrlWaitLatch.java
new file mode 100644
index 0000000..64ce153
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/UrlWaitLatch.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.virgo.test.tools;
+
+import static java.time.Instant.now;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.apache.http.HttpStatus.SC_OK;
+import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
+import static org.junit.Assert.fail;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.HttpHostConnectException;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+public class UrlWaitLatch {
+
+    public static final long HALF_SECOND = 500;
+
+    public static final long ONE_MINUTE = 60 * 1000;
+
+    public static int waitFor(String url) {
+        return waitFor(url, HALF_SECOND, ONE_MINUTE);
+    }
+
+    public static int waitFor(String url, String username, String password) {
+        return waitFor(url, username, password, HALF_SECOND, ONE_MINUTE);
+    }
+
+    public static int waitFor(String url, long interval, long duration) {
+        return waitFor(url, null, null, interval, duration);
+    }
+
+    public static int waitFor(String url, String username, String password, long interval, long duration) {
+
+        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+        if (username != null) {
+            CredentialsProvider provider = new BasicCredentialsProvider();
+            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
+            provider.setCredentials(AuthScope.ANY, credentials);
+            httpClientBuilder.setDefaultCredentialsProvider(provider);
+        }
+
+        final HttpGet get = new HttpGet(url);
+        TimerTask task = new TimerTask() {
+
+            @Override
+            public void run() {
+                if (get != null) {
+                    System.err.println("Operation timed out. Aborting request.");
+                    get.abort();
+                }
+            }
+        };
+        new Timer(true).schedule(task, Duration.ofSeconds(60).toMillis());
+
+        try (CloseableHttpClient client = httpClientBuilder.build()) {
+            int statusCode = -1;
+            Instant start = now();
+            while (start.plus(Duration.ofSeconds(30)).isAfter(now())) {
+                try {
+                    statusCode = client.execute(get).getStatusLine().getStatusCode();
+                } catch (HttpHostConnectException e) {
+                    System.out.println("Connection refused. The servlet container seems not be ready yet.");
+                }
+                System.out.println("Current status Code: " + statusCode);
+                if (statusCode == SC_OK || statusCode == SC_UNAUTHORIZED) {
+                    task.cancel();
+                    return statusCode;
+                }
+                System.out.println("Sleeping for " + interval + " millis.");
+                MILLISECONDS.sleep(interval);
+            }
+
+            fail(String.format("After %d ms, status code was %d", duration, statusCode));
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("Failed to connect to '" + url + "'.");
+        } finally {
+            get.releaseConnection();
+        }
+        throw new RuntimeException("Failed to connect to '" + url + "'.");
+    }
+
+    public static void main(String[] args) throws Exception {
+        checkHttpPage("Checking splash screen...", "http://localhost:8080/");
+        checkHttpPage("Checking admin screen...", "http://localhost:8080/admin");
+        checkHttpPage("Checking admin login with (admin/admin)...", "http://localhost:8080/admin/content/overview", "admin", "admin");
+        checkHttpPage("Checking admin login with (foo/bar)...", "http://localhost:8080/admin/content/overview", "foo", "bar");
+    }
+
+    private static void checkHttpPage(String message, String url) {
+        System.out.print(message);
+        int returnCode = UrlWaitLatch.waitFor(url);
+        System.out.println(returnCode);
+    }
+
+    private static void checkHttpPage(String message, String url, String username, String password) {
+        System.out.print(message);
+        int returnCode = UrlWaitLatch.waitFor(url, username, password);
+        System.out.println(returnCode);
+    }
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerShutdownThread.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerShutdownThread.java
new file mode 100644
index 0000000..1563800
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerShutdownThread.java
@@ -0,0 +1,41 @@
+
+package org.eclipse.virgo.test.tools;
+
+import java.io.File;
+import java.io.IOException;
+
+public class VirgoServerShutdownThread extends AbstractServerCommandThread {
+
+    private File shutdown = null;
+
+    private String shutdownFileName = null;
+
+    private File shutdownURI = null;
+
+    public static void shutdown(String binDir) {
+        new Thread(new VirgoServerShutdownThread(binDir)).start();
+    }
+
+    private VirgoServerShutdownThread(String binDir) {
+        super(binDir);
+    }
+
+    @Override
+    public void run() {
+        try {
+            if (os.getName().contains("Windows")) {
+                shutdown = new File(binDir, "shutdown.bat");
+                shutdownURI = new File(shutdown.toURI());
+                shutdownFileName = shutdownURI.getCanonicalPath();
+            } else {
+                shutdown = new File(binDir, "shutdown.sh");
+                shutdownURI = new File(shutdown.toURI());
+                shutdownFileName = shutdownURI.getCanonicalPath();
+            }
+            createAndStartProcess(shutdownFileName);
+            redirectProcessOutput();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerStartupThread.java b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerStartupThread.java
new file mode 100644
index 0000000..7972a4e
--- /dev/null
+++ b/test/org.eclipse.virgo.test.tools/src/main/java/org/eclipse/virgo/test/tools/VirgoServerStartupThread.java
@@ -0,0 +1,41 @@
+
+package org.eclipse.virgo.test.tools;
+
+import java.io.File;
+import java.io.IOException;
+
+public class VirgoServerStartupThread extends AbstractServerCommandThread {
+
+    private File startup = null;
+
+    private String startupFileName = null;
+
+    private File startupURI = null;
+
+    public static void startup(String binDir) {
+        new Thread(new VirgoServerStartupThread(binDir)).start();
+    }
+
+    private VirgoServerStartupThread(String binDir) {
+        super(binDir);
+    }
+
+    @Override
+    public void run() {
+        try {
+            if (os.getName().contains("Windows")) {
+                startup = new File(binDir, "startup.bat");
+                startupURI = new File(startup.toURI());
+                startupFileName = startupURI.getCanonicalPath();
+            } else {
+                startup = new File(binDir, "startup.sh");
+                startupURI = new File(startup.toURI());
+                startupFileName = startupURI.getCanonicalPath();
+            }
+            createAndStartProcess(startupFileName);
+            redirectProcessOutput();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}