You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@toree.apache.org by lb...@apache.org on 2016/02/29 18:44:17 UTC

[2/3] incubator-toree git commit: Added initial plugin implementation

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/PluginManagerSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/PluginManagerSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/PluginManagerSpec.scala
new file mode 100644
index 0000000..09e0f10
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/PluginManagerSpec.scala
@@ -0,0 +1,532 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins
+
+import java.io.File
+
+import org.apache.toree.plugins.dependencies.DependencyManager
+import org.mockito.Matchers._
+import org.mockito.Mockito._
+import org.scalatest.mock.MockitoSugar
+import org.scalatest.{FunSpec, Matchers, OneInstancePerTest}
+import test.utils._
+
+import scala.util.{Failure, Success}
+
+class PluginManagerSpec extends FunSpec with Matchers
+  with OneInstancePerTest with MockitoSugar
+{
+  private val TestPluginName = "some.plugin.class.name"
+
+  private val mockPluginClassLoader = mock[PluginClassLoader]
+  private val mockPluginSearcher = mock[PluginSearcher]
+  private val mockDependencyManager = mock[DependencyManager]
+  private val pluginManager = new PluginManager(
+    pluginClassLoader = mockPluginClassLoader,
+    pluginSearcher    = mockPluginSearcher,
+    dependencyManager = mockDependencyManager
+  )
+
+  describe("PluginManager") {
+    describe("#isActive") {
+      it("should return true if a plugin is loaded") {
+        val classInfoList = Seq(
+          TestClassInfo(
+            name = classOf[TestPlugin].getName,
+            location = new File("some/path/to/file.jar")
+          )
+        )
+
+        // When returning class information
+        doReturn(classInfoList.toIterator)
+          .when(mockPluginSearcher).search(any[File])
+
+        doReturn(classOf[TestPlugin])
+          .when(mockPluginClassLoader).loadClass(anyString())
+
+        // Perform the loading of plugins
+        pluginManager.loadPlugins(mock[File])
+
+        // Verify expected plugin has been loaded and is active
+        pluginManager.isActive(classOf[TestPlugin].getName) should be (true)
+      }
+
+      it("should return false if a plugin is not loaded") {
+        pluginManager.isActive(TestPluginName)
+      }
+    }
+
+    describe("#plugins") {
+      it("should return an iterator over all active plugins") {
+        val classInfoList = Seq(
+          TestClassInfo(
+            name = classOf[TestPlugin].getName,
+            location = new File("some/path/to/file.jar")
+          )
+        )
+
+        // When returning class information
+        doReturn(classInfoList.toIterator)
+          .when(mockPluginSearcher).search(any[File])
+
+        doReturn(classOf[TestPlugin])
+          .when(mockPluginClassLoader).loadClass(anyString())
+
+        // Perform the loading of plugins
+        pluginManager.loadPlugins(mock[File])
+
+        // Verify that we have plugins loaded
+        pluginManager.plugins should have size 1
+      }
+
+      it("should be empty if no plugins are loaded") {
+        pluginManager.plugins should be (empty)
+      }
+    }
+
+    describe("#loadPlugin") {
+      it("should return the same plugin if one exists with matching name") {
+        val c = classOf[TestPlugin]
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+
+        // If name matches, doesn't try to create class
+        pluginManager.loadPlugin(c.getName, null).get should be (plugin)
+      }
+
+      it("should create a new instance of the class and return it as a plugin") {
+        val p = pluginManager.loadPlugin("name", classOf[TestPlugin])
+
+        p.get shouldBe a [TestPlugin]
+      }
+
+      it("should set the internal plugin manager of the new plugin") {
+        val p = pluginManager.loadPlugin("name", classOf[TestPlugin])
+
+        p.get.pluginManager should be (pluginManager)
+      }
+
+      it("should add the new plugin to the list of active plugins") {
+        val c = classOf[TestPlugin]
+        val name = c.getName
+        val plugin = pluginManager.loadPlugin(name, c).get
+
+        pluginManager.isActive(name) should be (true)
+        pluginManager.findPlugin(name).get should be (plugin)
+      }
+
+      it("should return a failure if unable to create the class instance") {
+        class SomeClass(x: Int) extends Plugin
+        val p = pluginManager.loadPlugin("", classOf[SomeClass])
+
+        p.isFailure should be (true)
+        p.failed.get shouldBe an [InstantiationException]
+      }
+
+      it("should return an unknown plugin type failure if created class but not a plugin") {
+        // NOTE: Must use global class (not nested) to find one with empty constructor
+        val p = pluginManager.loadPlugin("", classOf[NotAPlugin])
+
+        p.isFailure should be (true)
+        p.failed.get shouldBe an [UnknownPluginTypeException]
+      }
+    }
+
+    describe("#loadPlugins") {
+      it("should load nothing if the plugin searcher returns empty handed") {
+        val expected = Nil
+
+        doReturn(Iterator.empty).when(mockPluginSearcher).search(any[File])
+        val actual = pluginManager.loadPlugins(mock[File])
+
+        actual should be (expected)
+      }
+
+      it("should add paths containing plugins to the plugin class loader") {
+        val classInfoList = Seq(
+          TestClassInfo(
+            name = "some.class",
+            location = new File("some/path/to/file.jar")
+          )
+        )
+
+        // When returning class information
+        doReturn(classInfoList.toIterator)
+          .when(mockPluginSearcher).search(any[File])
+
+        doReturn(classOf[TestPlugin])
+          .when(mockPluginClassLoader).loadClass(anyString())
+
+        // Perform the loading of plugins
+        pluginManager.loadPlugins(mock[File])
+
+        // Should add the locations from class information
+        classInfoList.map(_.location.toURI.toURL)
+          .foreach(verify(mockPluginClassLoader).addURL)
+      }
+
+      it("should load the plugin classes as external plugins") {
+        val classInfoList = Seq(
+          TestClassInfo(
+            name = classOf[TestPlugin].getName,
+            location = new File("some/path/to/file.jar")
+          )
+        )
+
+        // When returning class information
+        doReturn(classInfoList.toIterator)
+          .when(mockPluginSearcher).search(any[File])
+
+        doReturn(classOf[TestPlugin])
+          .when(mockPluginClassLoader).loadClass(anyString())
+
+        // Perform the loading of plugins
+        val plugins = pluginManager.loadPlugins(mock[File])
+
+        // Should contain a new instance of our test plugin class
+        plugins should have length 1
+        plugins.head shouldBe a [TestPlugin]
+      }
+    }
+
+    describe("#initializePlugins") {
+      it("should send the initialize event to the specified plugins") {
+        val testPlugin = new TestPlugin
+
+        @volatile var called = false
+        testPlugin.addInitCallback(() => called = true)
+
+        pluginManager.initializePlugins(Seq(testPlugin))
+        called should be (true)
+      }
+
+      it("should include any scoped dependencies in the initialized event") {
+        val testPlugin = new TestPluginWithDependencies
+        val dependency = TestPluginDependency(999)
+
+        @volatile var called = false
+        @volatile var d: TestPluginDependency = null
+        testPlugin.addInitCallback((d2) => {
+          called = true
+          d = d2
+        })
+
+        val dependencyManager = new DependencyManager
+        dependencyManager.add(dependency)
+
+        doReturn(dependencyManager).when(mockDependencyManager)
+          .merge(dependencyManager)
+
+        pluginManager.initializePlugins(Seq(testPlugin), dependencyManager)
+        called should be (true)
+        d should be (dependency)
+      }
+
+      it("should return a collection of successes for each plugin method") {
+        val testPlugin = new TestPlugin
+
+        val results = pluginManager.initializePlugins(Seq(testPlugin))
+        results should have size 1
+        results.head.pluginName should be (testPlugin.name)
+        results.head.isSuccess should be (true)
+      }
+
+      it("should return failures for any failed plugin method") {
+        val testPlugin = new TestPlugin
+        testPlugin.addInitCallback(() => throw new Throwable)
+
+        val results = pluginManager.initializePlugins(Seq(testPlugin))
+        results should have size 1
+        results.head.pluginName should be (testPlugin.name)
+        results.head.isFailure should be (true)
+      }
+    }
+
+    describe("#findPlugin") {
+      it("should return Some(plugin) if a plugin with matching name is found") {
+        val c = classOf[TestPlugin]
+        val p = pluginManager.loadPlugin(c.getName, c).get
+        pluginManager.findPlugin(p.name) should be (Some(p))
+      }
+
+      it("should return None if no plugin with matching name is found") {
+        pluginManager.findPlugin("some.class") should be (None)
+      }
+    }
+
+    describe("#destroyPlugins") {
+      it("should send the destroy event to the specified plugins") {
+        val testPlugin = new TestPlugin
+
+        @volatile var called = false
+        testPlugin.addDestroyCallback(() => called = true)
+
+        pluginManager.destroyPlugins(Seq(testPlugin))
+        called should be (true)
+      }
+
+      it("should include any scoped dependencies in the destroy event") {
+        val testPlugin = new TestPluginWithDependencies
+        val dependency = TestPluginDependency(999)
+
+        @volatile var called = false
+        @volatile var d: TestPluginDependency = null
+        testPlugin.addDestroyCallback((d2) => {
+          called = true
+          d = d2
+        })
+
+        val dependencyManager = new DependencyManager
+        dependencyManager.add(dependency)
+
+        doReturn(dependencyManager).when(mockDependencyManager)
+          .merge(dependencyManager)
+
+        pluginManager.destroyPlugins(Seq(testPlugin), dependencyManager)
+        called should be (true)
+        d should be (dependency)
+      }
+
+      it("should return a collection of successes for each plugin method") {
+        val testPlugin = new TestPlugin
+
+        val results = pluginManager.destroyPlugins(Seq(testPlugin))
+        results should have size 1
+        results.head.pluginName should be (testPlugin.name)
+        results.head.isSuccess should be (true)
+      }
+
+      it("should return failures for any failed plugin method") {
+        val testPlugin = new TestPlugin
+        testPlugin.addDestroyCallback(() => throw new Throwable)
+
+        val results = pluginManager.destroyPlugins(Seq(testPlugin))
+        results should have size 1
+        results.head.pluginName should be (testPlugin.name)
+        results.head.isFailure should be (true)
+      }
+
+      it("should remove any plugin that is successfully destroyed") {
+        val c = classOf[TestPlugin]
+
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        pluginManager.plugins should have size 1
+
+        pluginManager.destroyPlugins(Seq(plugin))
+        pluginManager.plugins should be (empty)
+      }
+
+      it("should remove any plugin that fails if destroyOnFailure is true") {
+        val c = classOf[TestPlugin]
+
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        pluginManager.plugins should have size 1
+
+        val testPlugin = plugin.asInstanceOf[TestPlugin]
+        testPlugin.addDestroyCallback(() => throw new Throwable)
+
+        pluginManager.destroyPlugins(Seq(plugin))
+        pluginManager.plugins should be (empty)
+      }
+
+      it("should not remove the plugin from the list if a destroy callback fails and destroyOnFailure is false") {
+        val c = classOf[TestPlugin]
+
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        pluginManager.plugins should have size 1
+
+        val testPlugin = plugin.asInstanceOf[TestPlugin]
+        testPlugin.addDestroyCallback(() => throw new Throwable)
+
+        pluginManager.destroyPlugins(Seq(plugin), destroyOnFailure = false)
+        pluginManager.plugins should contain (testPlugin)
+      }
+    }
+
+    describe("#firstEventFirstResult") {
+      it("should return Some(PluginMethodResult) with first result in list if non-empty") {
+        lazy val expected = Some(SuccessPluginMethodResult(
+          testPlugin.eventMethodMap(TestPlugin.DefaultEvent).head,
+          Seq("first")
+        ))
+
+        lazy val testPlugin = pluginManager.loadPlugin(
+          classOf[TestPlugin].getName, classOf[TestPlugin]
+        ).get.asInstanceOf[TestPlugin]
+        testPlugin.addEventCallback(() => "first")
+
+        lazy val testPluginWithDependencies = pluginManager.loadPlugin(
+          classOf[TestPluginWithDependencies].getName,
+          classOf[TestPluginWithDependencies]
+        ).get.asInstanceOf[TestPluginWithDependencies]
+        testPluginWithDependencies.addEventCallback(_ => "last")
+
+        val actual = pluginManager.fireEventFirstResult(TestPlugin.DefaultEvent)
+
+        actual should be (expected)
+      }
+
+      it("should return None if list is empty") {
+        val expected = None
+
+        val actual = pluginManager.fireEventFirstResult(TestPlugin.DefaultEvent)
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#firstEventLastResult") {
+      it("should return Some(Try(result)) with last result in list if non-empty") {
+        lazy val expected = Some(SuccessPluginMethodResult(
+          testPluginWithDependencies.eventMethodMap(TestPlugin.DefaultEvent).head,
+          Seq("last")
+        ))
+
+        lazy val testPlugin = pluginManager.loadPlugin(
+          classOf[TestPlugin].getName, classOf[TestPlugin]
+        ).get.asInstanceOf[TestPlugin]
+        testPlugin.addEventCallback(() => "first")
+
+        lazy val testPluginWithDependencies = pluginManager.loadPlugin(
+          classOf[TestPluginWithDependencies].getName,
+          classOf[TestPluginWithDependencies]
+        ).get.asInstanceOf[TestPluginWithDependencies]
+        testPluginWithDependencies.addEventCallback(_ => "last")
+
+        val dm = new DependencyManager
+        dm.add(TestPluginDependency(999))
+
+        doReturn(dm).when(mockDependencyManager).merge(any[DependencyManager])
+
+        val actual = pluginManager.fireEventLastResult(
+          TestPlugin.DefaultEvent, dm.toSeq: _*
+        )
+
+        actual should be (expected)
+      }
+
+      it("should return None if list is empty") {
+        val expected = None
+
+        val actual = pluginManager.fireEventLastResult(TestPlugin.DefaultEvent)
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#fireEvent") {
+      it("should invoke any plugin methods listening for the event") {
+        val c = classOf[TestPlugin]
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        val testPlugin = plugin.asInstanceOf[TestPlugin]
+
+        @volatile var called = 0
+        @volatile var calledMulti = 0
+        testPlugin.addEventCallback(() => called += 1)
+        testPlugin.addEventsCallback(() => calledMulti += 1)
+
+        pluginManager.fireEvent(TestPlugin.DefaultEvent)
+        pluginManager.fireEvent(TestPlugin.DefaultEvents1)
+        pluginManager.fireEvent(TestPlugin.DefaultEvents2)
+
+        called should be (1)
+        calledMulti should be (2)
+      }
+
+      it("should include any scoped dependencies in the fired event") {
+        val c = classOf[TestPluginWithDependencies]
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        val testPlugin = plugin.asInstanceOf[TestPluginWithDependencies]
+        val dependency = TestPluginDependency(999)
+        val dependencyManager = new DependencyManager
+        dependencyManager.add(dependency)
+
+        @volatile var called = 0
+        @volatile var calledMulti = 0
+        testPlugin.addEventCallback((d) => {
+          d should be (dependency)
+          called += 1
+        })
+        testPlugin.addEventsCallback((d) => {
+          d should be (dependency)
+          calledMulti += 1
+        })
+
+        doReturn(dependencyManager).when(mockDependencyManager)
+          .merge(dependencyManager)
+        pluginManager.fireEvent(TestPlugin.DefaultEvent, dependencyManager)
+
+        doReturn(dependencyManager).when(mockDependencyManager)
+          .merge(dependencyManager)
+        pluginManager.fireEvent(TestPlugin.DefaultEvents1, dependencyManager)
+
+        doReturn(dependencyManager).when(mockDependencyManager)
+          .merge(dependencyManager)
+        pluginManager.fireEvent(TestPlugin.DefaultEvents2, dependencyManager)
+
+        called should be (1)
+        calledMulti should be (2)
+      }
+
+      it("should return a collection of results for invoked plugin methods") {
+        val c = classOf[TestPlugin]
+        val plugin = pluginManager.loadPlugin(c.getName, c).get
+        val testPlugin = plugin.asInstanceOf[TestPlugin]
+
+        testPlugin.addEventCallback(() => {})
+        testPlugin.addEventsCallback(() => throw new Throwable)
+
+        val r1 = pluginManager.fireEvent(TestPlugin.DefaultEvent)
+        val r2 = pluginManager.fireEvent(TestPlugin.DefaultEvents1)
+
+        r1 should have size 1
+        r1.head.isSuccess should be (true)
+
+        r2 should have size 1
+        r2.head.isFailure should be (true)
+      }
+
+      it("should return results based on method and plugin priority") {
+        val testPlugin = {
+          val c = classOf[TestPlugin]
+          val plugin = pluginManager.loadPlugin(c.getName, c).get
+          plugin.asInstanceOf[TestPlugin]
+        }
+
+        val priorityPlugin = {
+          val c = classOf[PriorityPlugin]
+          val plugin = pluginManager.loadPlugin(c.getName, c).get
+          plugin.asInstanceOf[PriorityPlugin]
+        }
+
+        // Order should be
+        // 1. eventMethod (priority)
+        // 2. eventMethod (test)
+        // 3. eventMethod2 (priority)
+        val r = pluginManager.fireEvent(TestPlugin.DefaultEvent)
+
+        r.head.pluginName should be (priorityPlugin.name)
+        r.head.methodName should be ("eventMethod")
+
+        r(1).pluginName should be (testPlugin.name)
+        r(1).methodName should be ("eventMethod")
+
+        r(2).pluginName should be (priorityPlugin.name)
+        r(2).methodName should be ("eventMethod2")
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodResultSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodResultSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodResultSpec.scala
new file mode 100644
index 0000000..0589aa7
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodResultSpec.scala
@@ -0,0 +1,151 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins
+
+import java.lang.reflect.Method
+
+import org.apache.toree.plugins.annotations.{Priority, Event}
+import org.scalatest.mock.MockitoSugar
+import org.scalatest.{OneInstancePerTest, Matchers, FunSpec}
+import org.mockito.Mockito._
+
+import scala.util.{Failure, Success, Try}
+
+class PluginMethodResultSpec extends FunSpec with Matchers
+  with OneInstancePerTest with MockitoSugar
+{
+  private val testResult = new AnyRef
+  private val testThrowable = new Throwable
+
+  @Priority(level = 998)
+  private class TestPlugin extends Plugin {
+    @Priority(level = 999)
+    @Event(name = "success")
+    def success() = testResult
+
+    @Event(name = "failure")
+    def failure() = throw testThrowable
+  }
+
+  private val testPlugin = new TestPlugin
+
+  private val successResult: PluginMethodResult = SuccessPluginMethodResult(
+    testPlugin.eventMethodMap("success").head,
+    testResult
+  )
+
+  private val failureResult: PluginMethodResult = FailurePluginMethodResult(
+    testPlugin.eventMethodMap("failure").head,
+    testThrowable
+  )
+
+  describe("PluginMethodResult") {
+    describe("#pluginName") {
+      it("should return the name of the plugin from the invoked plugin method") {
+        val expected = testPlugin.name
+
+        val actual = successResult.pluginName
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#methodName") {
+      it("should return the name of the method from the invoked plugin method") {
+        val expected = "success"
+
+        val actual = successResult.methodName
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#pluginPriority") {
+      it("should return the priority of the plugin from the invoked plugin method") {
+        val expected = 998
+
+        val actual = successResult.pluginPriority
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#methodPriority") {
+      it("should return the priority of the method from the invoked plugin method") {
+        val expected = 999
+
+        val actual = successResult.methodPriority
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#isSuccess") {
+      it("should return true if representing a success result") {
+        val expected = true
+
+        val actual = successResult.isSuccess
+
+        actual should be (expected)
+      }
+
+      it("should return false if representing a failure result") {
+        val expected = false
+
+        val actual = failureResult.isSuccess
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#isFailure") {
+      it("should return false if representing a success result") {
+        val expected = false
+
+        val actual = successResult.isFailure
+
+        actual should be (expected)
+      }
+
+      it("should return true if representing a failure result") {
+        val expected = true
+
+        val actual = failureResult.isFailure
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#toTry") {
+      it("should return Success(result) if representing a success result") {
+        val expected = Success(testResult)
+
+        val actual = successResult.toTry
+
+        actual should be (expected)
+      }
+
+      it("should return Failure(throwable) if representing a failure result") {
+        val expected = Failure(testThrowable)
+
+        val actual = failureResult.toTry
+
+        actual should be (expected)
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodSpec.scala
new file mode 100644
index 0000000..ae7c334
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/PluginMethodSpec.scala
@@ -0,0 +1,319 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+package org.apache.toree.plugins
+
+import org.apache.toree.plugins.annotations._
+import org.apache.toree.plugins.dependencies.{DepClassNotFoundException, DepUnexpectedClassException, DepNameNotFoundException}
+import org.scalatest.{OneInstancePerTest, Matchers, FunSpec}
+
+class PluginMethodSpec extends FunSpec with Matchers with OneInstancePerTest {
+  private val testThrowable = new Throwable
+  private case class TestDependency(x: Int)
+  private class TestPlugin extends Plugin {
+    @Init def initMethod() = {}
+    @Event(name = "event1") def eventMethod() = {}
+    @Events(names = Array("event2", "event3")) def eventsMethod() = {}
+    @Destroy def destroyMethod() = {}
+
+    @Event(name = "event1") @Events(names = Array("event2", "event3"))
+    def allEventsMethod() = {}
+
+    @Priority(level = 999) def priorityMethod() = {}
+
+    def dependencyMethod(testDependency: TestDependency) = testDependency
+
+    def namedDependencyMethod(
+      @DepName(name = "name") testDependency: TestDependency
+    ) = testDependency
+
+    def normalMethod() = {}
+
+    def badMethod() = throw testThrowable
+  }
+
+  private val testPlugin = new TestPlugin
+
+  describe("PluginMethod") {
+    describe("#eventNames") {
+      it("should return the event names associated with the method") {
+        val expected = Seq("event1", "event2", "event3")
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("allEventsMethod")
+        )
+
+        val actual = pluginMethod.eventNames
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#isInit") {
+      it("should return true if method is annotated with Init") {
+        val expected = true
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("initMethod")
+        )
+
+        val actual = pluginMethod.isInit
+
+        actual should be (expected)
+      }
+
+      it("should return false if method is not annotated with Init") {
+        val expected = false
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val actual = pluginMethod.isInit
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#isEvent") {
+      it("should return true if method is annotated with Event") {
+        val expected = true
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("eventMethod")
+        )
+
+        val actual = pluginMethod.isEvent
+
+        actual should be (expected)
+      }
+
+      it("should return false if method is not annotated with Event") {
+        val expected = false
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val actual = pluginMethod.isEvent
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#isEvents") {
+      it("should return true if method is annotated with Events") {
+        val expected = true
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("eventsMethod")
+        )
+
+        val actual = pluginMethod.isEvents
+
+        actual should be (expected)
+      }
+
+      it("should return false if method is not annotated with Events") {
+        val expected = false
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val actual = pluginMethod.isEvents
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#isDestroy") {
+      it("should return true if method is annotated with Destroy") {
+        val expected = true
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("destroyMethod")
+        )
+
+        val actual = pluginMethod.isDestroy
+
+        actual should be (expected)
+      }
+
+      it("should return false if method is not annotated with Destroy") {
+        val expected = false
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val actual = pluginMethod.isDestroy
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#priority") {
+      it("should return the priority level of the method if provided") {
+        val expected = 999
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("priorityMethod")
+        )
+
+        val actual = pluginMethod.priority
+
+        actual should be (expected)
+      }
+
+      it("should return the default priority level of the method if not provided") {
+        val expected = PluginMethod.DefaultPriority
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val actual = pluginMethod.priority
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#invoke") {
+      it("should return a failure of DepNameNotFound if named dependency missing") {
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod(
+            "namedDependencyMethod",
+            classOf[TestDependency]
+          )
+        )
+
+        import org.apache.toree.plugins.Implicits._
+        val result = pluginMethod.invoke(TestDependency(999))
+
+        result.toTry.failed.get shouldBe a [DepNameNotFoundException]
+      }
+
+      it("should return a failure of DepUnexpectedClass if named dependency found with wrong class") {
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod(
+            "namedDependencyMethod",
+            classOf[TestDependency]
+          )
+        )
+
+        import org.apache.toree.plugins.Implicits._
+        val result = pluginMethod.invoke("name" -> new AnyRef)
+
+        result.toTry.failed.get shouldBe a [DepUnexpectedClassException]
+      }
+
+      it("should return a failure of DepClassNotFound if no dependency with class found") {
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod(
+            "dependencyMethod",
+            classOf[TestDependency]
+          )
+        )
+
+        val result = pluginMethod.invoke()
+
+        result.toTry.failed.get shouldBe a [DepClassNotFoundException]
+      }
+
+      it("should return a failure of the underlying exception if an error encountered on invocation") {
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("badMethod")
+        )
+
+        val result = pluginMethod.invoke()
+
+        result.toTry.failed.get should be (testThrowable)
+      }
+
+      it("should return a success if invoked correctly") {
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod("normalMethod")
+        )
+
+        val result = pluginMethod.invoke()
+
+        result.isSuccess should be (true)
+      }
+
+      it("should be able to inject named dependencies") {
+        val expected = TestDependency(999)
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod(
+            "namedDependencyMethod",
+            classOf[TestDependency]
+          )
+        )
+
+        import org.apache.toree.plugins.Implicits._
+        val result = pluginMethod.invoke(
+          "name2" -> TestDependency(998),
+          "name" -> expected,
+          "name3" -> TestDependency(1000)
+        )
+        val actual = result.toTry.get
+
+        actual should be (expected)
+      }
+
+      it("should be able to inject dependencies") {
+        val expected = TestDependency(999)
+
+        val pluginMethod = PluginMethod(
+          testPlugin,
+          classOf[TestPlugin].getDeclaredMethod(
+            "dependencyMethod",
+            classOf[TestDependency]
+          )
+        )
+
+        import org.apache.toree.plugins.Implicits._
+        val result = pluginMethod.invoke(
+          "test",
+          expected,
+          Int.box(3)
+        )
+        val actual = result.toTry.get
+
+        actual should be (expected)
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/PluginSearcherSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/PluginSearcherSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/PluginSearcherSpec.scala
new file mode 100644
index 0000000..58231fb
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/PluginSearcherSpec.scala
@@ -0,0 +1,184 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins
+
+import java.io.File
+
+import org.clapper.classutil.{Modifier, ClassFinder}
+import org.scalatest.mock.MockitoSugar
+import org.scalatest.{OneInstancePerTest, Matchers, FunSpec}
+
+import org.mockito.Mockito._
+import test.utils.TestClassInfo
+
+class PluginSearcherSpec extends FunSpec with Matchers
+  with OneInstancePerTest with MockitoSugar
+{
+  private val mockClassFinder = mock[ClassFinder]
+  private val pluginSearcher = new PluginSearcher {
+    override protected def newClassFinder(): ClassFinder = mockClassFinder
+    override protected def newClassFinder(paths: Seq[File]): ClassFinder =
+      mockClassFinder
+  }
+
+  private val pluginClassInfo = TestClassInfo(
+    name = classOf[Plugin].getName,
+    modifiers = Set(Modifier.Interface)
+  )
+  private val directPluginClassInfo = TestClassInfo(
+    name = "direct.plugin",
+    superClassName = pluginClassInfo.name
+  )
+  private val directAsInterfacePluginClassInfo = TestClassInfo(
+    name = "direct.interface.plugin",
+    interfaces = List(pluginClassInfo.name)
+  )
+  private val indirectPluginClassInfo = TestClassInfo(
+    name = "indirect.plugin",
+    superClassName = directPluginClassInfo.name
+  )
+  private val indirectAsInterfacePluginClassInfo = TestClassInfo(
+    name = "indirect.interface.plugin",
+    interfaces = List(directAsInterfacePluginClassInfo.name)
+  )
+  private val traitPluginClassInfo = TestClassInfo(
+    name = "trait.plugin",
+    modifiers = Set(Modifier.Interface)
+  )
+  private val abstractClassPluginClassInfo = TestClassInfo(
+    name = "abstract.plugin",
+    modifiers = Set(Modifier.Abstract)
+  )
+  private val classInfos = Seq(
+    pluginClassInfo,
+    directPluginClassInfo, directAsInterfacePluginClassInfo,
+    indirectPluginClassInfo, indirectAsInterfacePluginClassInfo,
+    traitPluginClassInfo, abstractClassPluginClassInfo
+  )
+
+  describe("PluginSearcher") {
+    describe("#internal") {
+      it("should find any plugins directly extending the Plugin class") {
+        val expected = directPluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.internal.map(_.name)
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins directly extending the Plugin trait") {
+        val expected = directAsInterfacePluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.internal.map(_.name)
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins indirectly extending the Plugin class") {
+        val expected = indirectPluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.internal.map(_.name)
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins indirectly extending the Plugin trait") {
+        val expected = indirectAsInterfacePluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.internal.map(_.name)
+
+        actual should contain (expected)
+      }
+
+      it("should not include any traits or abstract classes") {
+        val expected = Seq(
+          abstractClassPluginClassInfo.name,
+          traitPluginClassInfo.name
+        )
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.internal.map(_.name)
+
+        actual should not contain atLeastOneOf (expected.head, expected.tail)
+      }
+    }
+
+    describe("#search") {
+      it("should find any plugins directly extending the Plugin class") {
+        val expected = directPluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.search().map(_.name).toSeq
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins directly extending the Plugin trait") {
+        val expected = directAsInterfacePluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.search().map(_.name).toSeq
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins indirectly extending the Plugin class") {
+        val expected = indirectPluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.search().map(_.name).toSeq
+
+        actual should contain (expected)
+      }
+
+      it("should find any plugins indirectly extending the Plugin trait") {
+        val expected = indirectAsInterfacePluginClassInfo.name
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.search().map(_.name).toSeq
+
+        actual should contain (expected)
+      }
+
+      it("should not include any traits or abstract classes") {
+        val expected = Seq(
+          abstractClassPluginClassInfo.name,
+          traitPluginClassInfo.name
+        )
+
+        doReturn(classInfos.toIterator).when(mockClassFinder).getClasses()
+
+        val actual = pluginSearcher.search().map(_.name).toSeq
+
+        actual should not contain atLeastOneOf (expected.head, expected.tail)
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/PluginSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/PluginSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/PluginSpec.scala
new file mode 100644
index 0000000..fd5e1f0
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/PluginSpec.scala
@@ -0,0 +1,327 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins
+
+import java.lang.reflect.Method
+
+import org.apache.toree.plugins.annotations._
+import org.apache.toree.plugins.dependencies.DependencyManager
+import org.scalatest.mock.MockitoSugar
+import org.scalatest.{OneInstancePerTest, Matchers, FunSpec}
+import org.mockito.Mockito._
+import org.mockito.Matchers.{eq => mockEq, _}
+import scala.reflect.runtime.universe._
+
+class PluginSpec extends FunSpec with Matchers with OneInstancePerTest with MockitoSugar {
+  private val mockPluginManager = mock[PluginManager]
+  private val testPlugin = {
+    val plugin = new TestPlugin
+    plugin.pluginManager_=(mockPluginManager)
+    plugin
+  }
+  private val extendedTestPlugin = {
+    val extendedPlugin = new ExtendedTestPlugin
+    extendedPlugin.pluginManager_=(mockPluginManager)
+    extendedPlugin
+  }
+  private val registerPlugin = new RegisterPlugin
+
+  @Priority(level = 999) private class PriorityPlugin extends Plugin
+
+  describe("Plugin") {
+    describe("#name") {
+      it("should be the name of the class implementing the plugin") {
+        val expected = classOf[TestPlugin].getName
+
+        val actual = testPlugin.name
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#priority") {
+      it("should return the priority set by the plugin's annotation") {
+        val expected = 999
+
+        val actual = (new PriorityPlugin).priority
+
+        actual should be (expected)
+      }
+
+      it("should default to zero if not set via the plugin's annotation") {
+        val expected = Plugin.DefaultPriority
+
+        val actual = (new TestPlugin).priority
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#initMethods") {
+      it("should return any method annotated with @Init including from ancestors") {
+        val expected = Seq(
+          // Inherited
+          classOf[TestPlugin].getDeclaredMethod("init2"),
+          classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+          // Overridden
+          classOf[ExtendedTestPlugin].getDeclaredMethod("init1"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+          // New
+          classOf[ExtendedTestPlugin].getDeclaredMethod("init4"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+        ).map(PluginMethod.apply(extendedTestPlugin, _: Method))
+
+        val actual = extendedTestPlugin.initMethods
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#destroyMethods") {
+      it("should return any method annotated with @Destroy including from ancestors") {
+        val expected = Seq(
+          // Inherited
+          classOf[TestPlugin].getDeclaredMethod("destroy2"),
+          classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+          // Overridden
+          classOf[ExtendedTestPlugin].getDeclaredMethod("destroy1"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+          // New
+          classOf[ExtendedTestPlugin].getDeclaredMethod("destroy4"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+        ).map(PluginMethod.apply(extendedTestPlugin, _: Method))
+
+        val actual = extendedTestPlugin.destroyMethods
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#eventMethods") {
+      it("should return any method annotated with @Event including from ancestors") {
+        val expected = Seq(
+          // Inherited
+          classOf[TestPlugin].getDeclaredMethod("event2"),
+          classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+          // Overridden
+          classOf[ExtendedTestPlugin].getDeclaredMethod("event1"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+          // New
+          classOf[ExtendedTestPlugin].getDeclaredMethod("event4"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+        ).map(PluginMethod.apply(extendedTestPlugin, _: Method))
+
+        val actual = extendedTestPlugin.eventMethods
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#eventsMethods") {
+      it("should return any method annotated with @Events including from ancestors") {
+        val expected = Seq(
+          // Inherited
+          classOf[TestPlugin].getDeclaredMethod("multiEvent2"),
+          classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+          // Overridden
+          classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent1"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+          // New
+          classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent4"),
+          classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+        ).map(PluginMethod.apply(extendedTestPlugin, _: Method))
+
+        val actual = extendedTestPlugin.eventsMethods
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#eventMethodMap") {
+      it("should return a map of event names to their annotated methods") {
+        val expected = Map(
+          "event1" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("event2"),
+
+            // Overridden
+            classOf[ExtendedTestPlugin].getDeclaredMethod("event1"),
+            classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent1"),
+
+            // New
+            classOf[ExtendedTestPlugin].getDeclaredMethod("event4"),
+            classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent4")
+          ),
+          "event2" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("multiEvent2"),
+
+            // Overridden
+            classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent1"),
+
+            // New
+            classOf[ExtendedTestPlugin].getDeclaredMethod("multiEvent4")
+          ),
+          "event3" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("multiEvent2")
+          ),
+          "mixed1" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+            // Overridden
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+            // New
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+          ),
+          "mixed2" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+            // Overridden
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+            // New
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+          ),
+          "mixed3" -> Seq(
+            // Inherited
+            classOf[TestPlugin].getDeclaredMethod("mixed2"),
+
+            // Overridden
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed1"),
+
+            // New
+            classOf[ExtendedTestPlugin].getDeclaredMethod("mixed4")
+          )
+        ).mapValues(m => m.map(PluginMethod.apply(extendedTestPlugin, _: Method)))
+
+        val actual = extendedTestPlugin.eventMethodMap
+
+        actual.keys should contain theSameElementsAs (expected.keys)
+        actual.foreach { case (k, v) =>
+          v should contain theSameElementsAs (expected(k))
+        }
+      }
+    }
+
+    describe("#register") {
+      it("should not allow registering a dependency if the plugin manager is not set") {
+        intercept[AssertionError] { registerPlugin.register(new AnyRef) }
+        intercept[AssertionError] { registerPlugin.register("id", new AnyRef) }
+      }
+
+      it("should create a new name for the dependency if not specified") {
+        registerPlugin.pluginManager_=(mockPluginManager)
+
+        val value = new AnyRef
+        val mockDependencyManager = mock[DependencyManager]
+        doNothing().when(mockDependencyManager).add(anyString(), mockEq(value))(any[TypeTag[AnyRef]])
+        doReturn(mockDependencyManager).when(mockPluginManager).dependencyManager
+
+        registerPlugin.register(value)
+      }
+
+      it("should add the dependency using the provided name") {
+        registerPlugin.pluginManager_=(mockPluginManager)
+
+        val name = "some name"
+        val value = new AnyRef
+        val mockDependencyManager = mock[DependencyManager]
+        doNothing().when(mockDependencyManager).add(mockEq(name), mockEq(value))(any[TypeTag[AnyRef]])
+        doReturn(mockDependencyManager).when(mockPluginManager).dependencyManager
+
+        registerPlugin.register(name, value)
+      }
+    }
+  }
+
+  private class TestPlugin extends Plugin {
+    @Init def init1() = {}
+    @Init protected def init2() = {}
+    @Init private def init3() = {}
+    @Event(name = "event1") def event1() = {}
+    @Event(name = "event1") protected def event2() = {}
+    @Event(name = "event1") private def event3() = {}
+    @Events(names = Array("event2", "event3")) def multiEvent1() = {}
+    @Events(names = Array("event2", "event3")) protected def multiEvent2() = {}
+    @Events(names = Array("event2", "event3")) private def multiEvent3() = {}
+    @Destroy def destroy1() = {}
+    @Destroy protected def destroy2() = {}
+    @Destroy private def destroy3() = {}
+
+    @Init
+    @Event(name = "mixed1")
+    @Events(names = Array("mixed2", "mixed3"))
+    @Destroy
+    def mixed1() = {}
+
+    @Init
+    @Event(name = "mixed1")
+    @Events(names = Array("mixed2", "mixed3"))
+    @Destroy
+    protected def mixed2() = {}
+
+    @Init
+    @Event(name = "mixed1")
+    @Events(names = Array("mixed2", "mixed3"))
+    @Destroy
+    private def mixed3() = {}
+  }
+
+  private class ExtendedTestPlugin extends TestPlugin {
+    @Init override def init1() = {}
+    @Event(name = "event1") override def event1() = {}
+    @Events(names = Array("event1", "event2")) override def multiEvent1() = {}
+    @Destroy override def destroy1() = {}
+    @Init
+    @Event(name = "mixed1")
+    @Events(names = Array("mixed2", "mixed3"))
+    @Destroy
+    override def mixed1() = {}
+
+    @Init def init4() = {}
+    @Event(name = "event1") def event4() = {}
+    @Events(names = Array("event1", "event2")) def multiEvent4() = {}
+    @Destroy def destroy4() = {}
+    @Init
+    @Event(name = "mixed1")
+    @Events(names = Array("mixed2", "mixed3"))
+    @Destroy
+    def mixed4() = {}
+  }
+
+  private class RegisterPlugin extends Plugin {
+    override def register[T <: AnyRef : TypeTag](
+      value: T
+    ): Unit = super.register(value)
+    override def register[T <: AnyRef](
+      name: String,
+      value: T
+    )(implicit typeTag: TypeTag[T]): Unit = super.register(name, value)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencyManagerSpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencyManagerSpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencyManagerSpec.scala
new file mode 100644
index 0000000..4ee02c3
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencyManagerSpec.scala
@@ -0,0 +1,560 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins.dependencies
+
+import org.scalatest.{OneInstancePerTest, Matchers, FunSpec}
+
+class DependencyManagerSpec extends FunSpec with Matchers with OneInstancePerTest {
+  private val dependencyManager = new DependencyManager
+
+  describe("DependencyManager") {
+    describe("#Empty") {
+      it("should return the same dependency manager each time") {
+        val expected = DependencyManager.Empty
+        val actual = DependencyManager.Empty
+
+        actual should be (expected)
+      }
+
+      it("should not add dependencies when the add method is invoked") {
+        val d = DependencyManager.Empty
+
+        d.add(new Object)
+        d.add("id", new Object)
+        d.add(Dependency.fromValue(new Object))
+
+        d.toSeq should be (empty)
+      }
+    }
+
+    describe("#from") {
+      it("should return a new dependency manager using the dependencies") {
+        val expected = Seq(
+          Dependency.fromValue("value1"),
+          Dependency.fromValue("value2")
+        )
+
+        val actual = DependencyManager.from(expected: _*).toSeq
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should throw an exception if two dependencies have the same name") {
+        intercept[IllegalArgumentException] {
+          DependencyManager.from(
+            Dependency.fromValueWithName("name", "value1"),
+            Dependency.fromValueWithName("name", "value2")
+          )
+        }
+      }
+    }
+
+    describe("#merge") {
+      it("should return a new dependency manager with both manager's dependencies") {
+        val expected = Seq(
+          Dependency.fromValue("value1"),
+          Dependency.fromValue("value2"),
+          Dependency.fromValue("value3"),
+          Dependency.fromValue("value4")
+        )
+
+        val dm1 = DependencyManager.from(
+          expected.take(expected.length / 2): _*
+        )
+
+        val dm2 = DependencyManager.from(
+          expected.takeRight(expected.length / 2): _*
+        )
+
+        val actual = dm1.merge(dm2).toSeq
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should overwrite any dependency with the same name from this manager with the other") {
+        val expected = Seq(
+          Dependency.fromValueWithName("name", "value1"),
+          Dependency.fromValue("value2"),
+          Dependency.fromValue("value3"),
+          Dependency.fromValue("value4")
+        )
+
+        val dm1 = DependencyManager.from(
+          Dependency.fromValueWithName("name", "value5")
+        )
+
+        val dm2 = DependencyManager.from(expected: _*)
+
+        val actual = dm1.merge(dm2).toSeq
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#toMap") {
+      it("should return a map of dependency names to dependency values") {
+        val expected = Map(
+          "some name" -> new Object,
+          "some other name" -> new Object
+        )
+
+        expected.foreach { case (k, v) => dependencyManager.add(k, v) }
+
+        val actual = dependencyManager.toMap
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#toSeq") {
+      it("should return a sequence of dependency objects") {
+        val expected = Seq(
+          Dependency.fromValue(new Object),
+          Dependency.fromValue(new Object)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.toSeq
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#add") {
+      it("should generate a dependency name if not provided") {
+        dependencyManager.add(new Object)
+
+        dependencyManager.toSeq.head.name should not be (empty)
+      }
+
+      it("should use the provided name as the dependency's name") {
+        val expected = "some name"
+
+        dependencyManager.add(expected, new Object)
+
+        val actual = dependencyManager.toSeq.head.name
+
+        actual should be (expected)
+      }
+
+      it("should use the provided value for the dependency's value") {
+        val expected = new Object
+
+        dependencyManager.add(expected)
+
+        val actual = dependencyManager.toSeq.head.value
+
+        actual should be (expected)
+      }
+
+      it("should use the reflective type of the value for the dependency's type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = typeOf[Object]
+
+        dependencyManager.add(new Object)
+
+        val actual = dependencyManager.toSeq.head.`type`
+
+        actual should be (expected)
+      }
+
+      it("should add the provided dependency object directly") {
+        val expected = Dependency.fromValue(new Object)
+
+        dependencyManager.add(expected)
+
+        val actual = dependencyManager.toSeq.head
+
+        actual should be (expected)
+      }
+
+      it("should throw an exception if a dependency with the same name already exists") {
+        intercept[IllegalArgumentException] {
+          dependencyManager.add("id", new Object)
+          dependencyManager.add("id", new Object)
+        }
+      }
+    }
+
+    describe("#find") {
+      it("should return Some(Dependency) if found by name") {
+        val expected = Some(Dependency.fromValue(new Object))
+
+        dependencyManager.add(expected.get)
+
+        val actual = dependencyManager.find(expected.get.name)
+
+        actual should be (expected)
+      }
+
+      it("should return None if no dependency with a matching name exists") {
+        val expected = None
+
+        val actual = dependencyManager.find("some name")
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#findByType") {
+      it("should return a collection including of dependencies with the same type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[Object], new Object),
+          Dependency("id2", typeOf[Object], new Object)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByType(typeOf[Object])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection including of dependencies with a sub type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new String),
+          Dependency("id2", typeOf[String], new String)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByType(typeOf[Object])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return an empty collection if no dependency has the type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Nil
+
+        dependencyManager.add(Dependency("id", typeOf[Object], new Object))
+        dependencyManager.add(Dependency("id2", typeOf[Object], new Object))
+
+        val actual = dependencyManager.findByType(typeOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#findByTypeClass") {
+      it("should return a collection including of dependencies with the same class for the type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[Object], new Object),
+          Dependency("id2", typeOf[Object], new Object)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByTypeClass(classOf[Object])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection including of dependencies with a sub class for the type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new String),
+          Dependency("id2", typeOf[String], new String)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByTypeClass(classOf[Object])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return an empty collection if no dependency has a matching class for its type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Nil
+
+        dependencyManager.add(Dependency("id", typeOf[Object], new Object))
+        dependencyManager.add(Dependency("id2", typeOf[Object], new Object))
+
+        val actual = dependencyManager.findByTypeClass(classOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      ignore("should throw an exception if the dependency's type class is not found in the provided class' classloader") {
+        import scala.reflect.runtime.universe._
+
+        intercept[ClassNotFoundException] {
+          // TODO: Find some class that is in a different classloader and
+          //       create a dependency from it
+          dependencyManager.add(Dependency("id", typeOf[Object], new Object))
+
+          dependencyManager.findByTypeClass(classOf[Object])
+        }
+      }
+    }
+
+    describe("#findByValueClass") {
+      it("should return a collection including of dependencies with the same class for the value") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[AnyVal], new AnyRef),
+          Dependency("id2", typeOf[AnyVal], new AnyRef)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByValueClass(classOf[AnyRef])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection including of dependencies with a sub class for the value") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[AnyVal], new String),
+          Dependency("id2", typeOf[AnyVal], new String)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.findByValueClass(classOf[AnyRef])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return an empty collection if no dependency has a matching class for its value") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Nil
+
+        dependencyManager.add(Dependency("id", typeOf[String], new Object))
+        dependencyManager.add(Dependency("id2", typeOf[String], new Object))
+
+        val actual = dependencyManager.findByValueClass(classOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#remove") {
+      it("should remove the dependency with the matching name") {
+        val dSeq = Seq(
+          Dependency.fromValue(new Object),
+          Dependency.fromValue(new Object)
+        )
+
+        val dToRemove = Dependency.fromValue(new Object)
+        dSeq.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        dependencyManager.remove(dToRemove.name)
+
+        val actual = dependencyManager.toSeq
+
+        actual should not contain (dToRemove)
+      }
+
+      it("should return Some(Dependency) representing the removed dependency") {
+        val expected = Some(Dependency.fromValue(new Object))
+
+        dependencyManager.add(expected.get)
+
+        val actual = dependencyManager.remove(expected.get.name)
+
+        actual should be (expected)
+      }
+
+      it("should return None if no dependency was removed") {
+        val expected = None
+
+        val actual = dependencyManager.remove("some name")
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#removeByType") {
+      it("should remove dependencies with the specified type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[String], new AnyRef)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByType(typeOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should remove dependencies with a type that is a subtype of the specified type") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[String], new AnyRef)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByType(typeOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection of any removed dependencies") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[CharSequence], new AnyRef)
+        )
+
+        val all = Seq(
+          Dependency("id3", typeOf[Integer], new AnyRef),
+          Dependency("id4", typeOf[Boolean], new AnyRef)
+        ) ++ expected
+
+        all.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByType(typeOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#removeByTypeClass") {
+      it("should remove dependencies with the specified type class") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[String], new AnyRef)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByTypeClass(classOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should remove dependencies with a type that is a subtype of the specified type class") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[String], new AnyRef)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByTypeClass(classOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection of any removed dependencies") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[String], new AnyRef),
+          Dependency("id2", typeOf[CharSequence], new AnyRef)
+        )
+
+        val all = Seq(
+          Dependency("id3", typeOf[Integer], new AnyRef),
+          Dependency("id4", typeOf[Boolean], new AnyRef)
+        ) ++ expected
+
+        all.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByTypeClass(classOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+
+    describe("#removeByValueClass") {
+      it("should remove dependencies with the specified value class") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[AnyRef], new String),
+          Dependency("id2", typeOf[AnyRef], new String)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByValueClass(classOf[String])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should remove dependencies with a type that is a subtype of the specified value class") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[AnyRef], new String),
+          Dependency("id2", typeOf[AnyRef], new String)
+        )
+
+        expected.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByValueClass(classOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+
+      it("should return a collection of any removed dependencies") {
+        import scala.reflect.runtime.universe._
+
+        val expected = Seq(
+          Dependency("id", typeOf[AnyRef], new String),
+          Dependency("id2", typeOf[AnyRef], new CharSequence {
+            override def charAt(i: Int): Char = ???
+
+            override def length(): Int = ???
+
+            override def subSequence(i: Int, i1: Int): CharSequence = ???
+          })
+        )
+
+        val all = Seq(
+          Dependency("id3", typeOf[AnyRef], Int.box(3)),
+          Dependency("id4", typeOf[AnyRef], Boolean.box(true))
+        ) ++ expected
+
+        all.foreach(dependencyManager.add(_: Dependency[_ <: AnyRef]))
+
+        val actual = dependencyManager.removeByValueClass(classOf[CharSequence])
+
+        actual should contain theSameElementsAs (expected)
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencySpec.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencySpec.scala b/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencySpec.scala
new file mode 100644
index 0000000..b434e2a
--- /dev/null
+++ b/plugins/src/test/scala/org/apache/toree/plugins/dependencies/DependencySpec.scala
@@ -0,0 +1,133 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package org.apache.toree.plugins.dependencies
+
+import org.scalatest.{FunSpec, OneInstancePerTest, Matchers}
+
+import scala.tools.nsc.util.ScalaClassLoader.URLClassLoader
+
+class DependencySpec extends FunSpec with Matchers with OneInstancePerTest {
+  import scala.reflect.runtime.universe._
+
+  describe("Dependency") {
+    describe("constructor") {
+      it("should throw illegal argument exception if name is null") {
+        intercept[IllegalArgumentException] {
+          Dependency(null, typeOf[DependencySpec], new Object)
+        }
+      }
+
+      it("should throw illegal argument exception if name is empty") {
+        intercept[IllegalArgumentException] {
+          Dependency("", typeOf[DependencySpec], new Object)
+        }
+      }
+
+      it("should throw illegal argument exception if type is null") {
+        intercept[IllegalArgumentException] {
+          Dependency("id", null, new Object)
+        }
+      }
+
+      it("should throw illegal argument exception if value is null") {
+        intercept[IllegalArgumentException] {
+          Dependency("id", typeOf[DependencySpec], null)
+        }
+      }
+    }
+
+    describe("#typeClass") {
+      it("should return the class found in the class loader that matches the type") {
+        val expected = this.getClass
+
+        val d = Dependency("id", typeOf[DependencySpec], new Object)
+        val actual = d.typeClass(this.getClass.getClassLoader)
+
+        actual should be (expected)
+      }
+
+      it("should throw an exception if no matching class is found in the classloader") {
+        intercept[ClassNotFoundException] {
+          val d = Dependency("id", typeOf[DependencySpec], new Object)
+          d.typeClass(new URLClassLoader(Nil, null))
+        }
+      }
+    }
+
+    describe("#valueClass") {
+      it("should return the class directly from the dependency's value") {
+        val expected = classOf[Object]
+
+        val d = Dependency("id", typeOf[DependencySpec], new Object)
+        val actual = d.valueClass
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#fromValue") {
+      it("should generate a unique name for the dependency") {
+        val d = Dependency.fromValue(new Object)
+
+        // TODO: Stub out UUID method to test id was generated
+        d.name should not be (empty)
+      }
+
+      it("should use the provided value as the dependency's value") {
+        val expected = new Object
+
+        val actual = Dependency.fromValue(expected).value
+
+        actual should be (expected)
+      }
+
+      it("should acquire the reflective type from the provided value") {
+        val expected = typeOf[Object]
+
+        val actual = Dependency.fromValue(new Object).`type`
+
+        actual should be (expected)
+      }
+    }
+
+    describe("#fromValueWithName") {
+      it("should use the provided name as the name for the dependency") {
+        val expected = "some dependency name"
+
+        val actual = Dependency.fromValueWithName(expected, new Object).name
+
+        actual should be (expected)
+      }
+
+      it("should use the provided value as the dependency's value") {
+        val expected = new Object
+
+        val actual = Dependency.fromValueWithName("id", expected).value
+
+        actual should be (expected)
+      }
+
+      it("should acquire the reflective type from the provided value") {
+        val expected = typeOf[Object]
+
+        val actual = Dependency.fromValueWithName("id", new Object).`type`
+
+        actual should be (expected)
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/NotAPlugin.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/NotAPlugin.scala b/plugins/src/test/scala/test/utils/NotAPlugin.scala
new file mode 100644
index 0000000..1e7f3fa
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/NotAPlugin.scala
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package test.utils
+
+/**
+ * Class that is not a plugin, but has an empty constructor.
+ *
+ * @note Exists in global space instead of nested in test classes due to the
+ *       fact that Scala creates a non-nullary constructor when a class is
+ *       nested.
+ */
+class NotAPlugin

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/PriorityPlugin.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/PriorityPlugin.scala b/plugins/src/test/scala/test/utils/PriorityPlugin.scala
new file mode 100644
index 0000000..4ac9e30
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/PriorityPlugin.scala
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+package test.utils
+
+import org.apache.toree.plugins.annotations._
+
+import scala.collection.mutable
+
+@Priority(level = -1) class PriorityPlugin extends TestPlugin {
+  @Init @Priority(level = 1)
+  override def initMethod(): mutable.Seq[Any] = super.initMethod()
+
+  @Init def initMethod2() = {}
+
+  @Event(name = "event1") @Priority(level = 1)
+  override def eventMethod(): mutable.Seq[Any] = super.eventMethod()
+
+  @Event(name = "event1") def eventMethod2() = {}
+
+  @Events(names = Array("event2", "event3")) @Priority(level = 1)
+  override def eventsMethod(): mutable.Seq[Any] = super.eventsMethod()
+
+  @Events(names = Array("event2", "event3"))
+  def eventsMethod2() = {}
+
+  @Destroy @Priority(level = 1)
+  override def destroyMethod(): mutable.Seq[Any] = super.destroyMethod()
+
+  @Init def destroyMethod2() = {}
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/RegisteringTestPlugin.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/RegisteringTestPlugin.scala b/plugins/src/test/scala/test/utils/RegisteringTestPlugin.scala
new file mode 100644
index 0000000..5519358
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/RegisteringTestPlugin.scala
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package test.utils
+
+import org.apache.toree.plugins.Plugin
+import org.apache.toree.plugins.annotations.{Destroy, Event, Events, Init}
+
+import RegisteringTestPlugin._
+
+/**
+ * Test plugin that registers dependencies.
+ *
+ * @note Exists in global space instead of nested in test classes due to the
+ *       fact that Scala creates a non-nullary constructor when a class is
+ *       nested.
+ */
+class RegisteringTestPlugin extends Plugin {
+  type Callback = () => Any
+  private var initCallbacks = collection.mutable.Seq[Callback]()
+  private var eventCallbacks = collection.mutable.Seq[Callback]()
+  private var eventsCallbacks = collection.mutable.Seq[Callback]()
+  private var destroyCallbacks = collection.mutable.Seq[Callback]()
+
+  def addInitCallback(callback: Callback) = initCallbacks :+= callback
+  def addEventCallback(callback: Callback) = eventCallbacks :+= callback
+  def addEventsCallback(callback: Callback) = eventsCallbacks :+= callback
+  def addDestroyCallback(callback: Callback) = destroyCallbacks :+= callback
+
+  @Init def initMethod() = {
+    register(InitDepName, TestPluginDependency(996))
+    initCallbacks.map(_())
+  }
+
+  @Event(name = "event1") def eventMethod() = {
+    register(EventDepName, TestPluginDependency(997))
+    eventCallbacks.map(_())
+  }
+
+  @Events(names = Array("event2", "event3")) def eventsMethod() = {
+    register(EventsDepName, TestPluginDependency(998))
+    eventsCallbacks.map(_ ())
+  }
+
+  @Destroy def destroyMethod() = {
+    register(DestroyDepName, TestPluginDependency(999))
+    destroyCallbacks.map(_())
+  }
+}
+
+object RegisteringTestPlugin {
+  val DefaultEvent = "event1"
+  val DefaultEvents1 = "event2"
+  val DefaultEvents2 = "event3"
+  val InitDepName = "init-dep"
+  val EventDepName = "event-dep"
+  val EventsDepName = "events-dep"
+  val DestroyDepName = "destroy-dep"
+}

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/TestClassInfo.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/TestClassInfo.scala b/plugins/src/test/scala/test/utils/TestClassInfo.scala
new file mode 100644
index 0000000..ab59802
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/TestClassInfo.scala
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package test.utils
+
+import java.io.File
+
+import org.clapper.classutil.Modifier.Modifier
+import org.clapper.classutil.{ClassInfo, FieldInfo, MethodInfo}
+
+case class TestClassInfo(
+  superClassName: String = "",
+  interfaces: List[String] = Nil,
+  location: File = null,
+  methods: Set[MethodInfo] = Set(),
+  fields: Set[FieldInfo] = Set(),
+  signature: String = "",
+  modifiers: Set[Modifier] = Set(),
+  name: String = ""
+) extends ClassInfo
+

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/TestFieldInfo.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/TestFieldInfo.scala b/plugins/src/test/scala/test/utils/TestFieldInfo.scala
new file mode 100644
index 0000000..492fc94
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/TestFieldInfo.scala
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package test.utils
+
+import org.clapper.classutil.FieldInfo
+import org.clapper.classutil.Modifier.Modifier
+
+case class TestFieldInfo(
+  signature: String = "",
+  descriptor: String = "",
+  exceptions: List[String] = Nil,
+  modifiers: Set[Modifier] = Set(),
+  name: String = "",
+  value: Option[Object] = None
+) extends FieldInfo
+

http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/4c0dccfb/plugins/src/test/scala/test/utils/TestMethodInfo.scala
----------------------------------------------------------------------
diff --git a/plugins/src/test/scala/test/utils/TestMethodInfo.scala b/plugins/src/test/scala/test/utils/TestMethodInfo.scala
new file mode 100644
index 0000000..7e61cc7
--- /dev/null
+++ b/plugins/src/test/scala/test/utils/TestMethodInfo.scala
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+package test.utils
+
+import org.clapper.classutil.MethodInfo
+import org.clapper.classutil.Modifier.Modifier
+
+private case class TestMethodInfo(
+  signature: String = "",
+  descriptor: String = "",
+  exceptions: List[String] = Nil,
+  modifiers: Set[Modifier] = Set(),
+  name: String = ""
+) extends MethodInfo
+