text: import tempfile
  
  import mock
  import pytest
  from pytest_girder.plugin_registry import PluginRegistry
  
  from girder import plugin
  from girder.exceptions import GirderException
  from girder.plugin import GirderPlugin
  
  
  @pytest.fixture
  def logprint():
      with mock.patch.object(plugin, 'logprint') as logprintMock:
          yield logprintMock
  
  
  @pytest.fixture
  def registry(request):
      testPluginMarkers = request.node.iter_markers('plugin')
      pluginRegistry = PluginRegistry(include_installed_plugins=False)
      if testPluginMarkers is not None:
          for testPluginMarker in testPluginMarkers:
              if len(testPluginMarker.args) > 1:
                  pluginRegistry.registerTestPlugin(
                      *testPluginMarker.args, **testPluginMarker.kwargs
                  )
      with pluginRegistry():
          yield
  
  
  class InvalidPlugin(GirderPlugin):
      pass
  
  
  class NoDeps(GirderPlugin):
      def __init__(self, *args, **kwargs):
          super(NoDeps, self).__init__(*args, **kwargs)
          self._testLoadMock = mock.Mock()
  
      def load(self, info):
          self._testLoadMock(info)
  
  
  class PluginWithNPM(NoDeps):
      CLIENT_SOURCE_PATH = 'web_client'
  
  
  class ReturnsFive(NoDeps):
      def load(self, info):
          super(ReturnsFive, self).load(info)
          return 5
  
  
  class DependsOnPlugin1(NoDeps):
      def load(self, info):
          plugin.getPlugin('plugin1').load(info)
          super(DependsOnPlugin1, self).load(info)
  
  
  class DependsOnPlugin2(NoDeps):
      def load(self, info):
          plugin.getPlugin('plugin2').load(info)
          super(DependsOnPlugin2, self).load(info)
  
  
  class DependsOnPlugin1and2(NoDeps):
      def load(self, info):
          plugin.getPlugin('plugin1').load(info)
          plugin.getPlugin('plugin2').load(info)
          super(DependsOnPlugin1and2, self).load(info)
  
  
  class ThrowsOnLoad(NoDeps):
      def load(self, info):
          super(DependsOnPlugin1and2, self).load(info)
          raise Exception()
  
  
  class HasDisplayName(NoDeps):
      DISPLAY_NAME = 'A plugin with a display name'
  
  
  @pytest.mark.plugin('invalid', InvalidPlugin)
  def testPluginWithNoLoadMethod(registry):
      with pytest.raises(NotImplementedError):
          plugin.getPlugin('invalid').load({})
  
  
  @pytest.mark.plugin('display', HasDisplayName)
  def testPluginWithDisplayName(registry):
      pluginDef = plugin.getPlugin('display')
      assert pluginDef.name == 'display'
      assert pluginDef.displayName == 'A plugin with a display name'
  
  
  @pytest.mark.plugin('nodeps', NoDeps)
  def testPluginWithNoDisplayName(registry):
      pluginDef = plugin.getPlugin('nodeps')
      assert pluginDef.name == 'nodeps'
      assert pluginDef.displayName == 'nodeps'
  
  
  @pytest.mark.plugin('nodeps', NoDeps, description='description', version='1.0.0', url='url')
  def testPluginMetadata(registry):
      pluginDef = plugin.getPlugin('nodeps')
      assert pluginDef.name == 'nodeps'
      assert pluginDef.version == '1.0.0'
      assert pluginDef.url == 'url'
      assert pluginDef.description == 'description'
      assert pluginDef.npmPackages() == {}
  
  
  @pytest.mark.plugin('client_plugin', PluginWithNPM)
  def testPluginWithNPMPackage(registry):
      with tempfile.NamedTemporaryFile() as packageJson:
          packageJson.write(b'{"name": "@girder/test_plugin"}')
          packageJson.flush()
          pluginDef = plugin.getPlugin('client_plugin')
          with mock.patch.object(plugin, 'resource_filename', return_value=packageJson.name):
              assert '@girder/test_plugin' in pluginDef.npmPackages()
  
  
  @pytest.mark.plugin('plugin1', NoDeps)
  @pytest.mark.plugin('plugin2', NoDeps)
  @pytest.mark.plugin('plugin3', NoDeps)
  @pytest.mark.plugin('plugin4', NoDeps)
  def testAllPlugins(registry):
      allPlugins = plugin.allPlugins()
      assert set(allPlugins) == {'plugin1', 'plugin2', 'plugin3', 'plugin4'}
  
  
  @pytest.mark.plugin('plugin1', NoDeps)
  def testPluginLoad(registry):
      plugin1Definition = plugin.getPlugin('plugin1')
      assert plugin1Definition is not None
      assert plugin1Definition.loaded is False
  
      plugin1Definition.load(info={})
  
      assert plugin1Definition.loaded is True
      plugin1Definition._testLoadMock.assert_called_once()
  
      # Attempting to load a second time should do nothing
      plugin1Definition._testLoadMock.reset_mock()
  
      plugin1Definition.load(info={})
  
      assert plugin1Definition.loaded is True
      plugin1Definition._testLoadMock.assert_not_called()
  
  
  @pytest.mark.plugin('plugin1', ReturnsFive)
  def testPluginLoadReturn(registry):
      plugin1Definition = plugin.getPlugin('plugin1')
  
      assert plugin1Definition.load(info={}) == 5
  
      # The value should be returned every time load is called
      assert plugin1Definition.load(info={}) == 5
  
  
  # plugin._loadPlugins cannot be tested without providing "names", since all built-in Girder plugins
  # are typically installed for testing and will be loaded
  
  
  @pytest.mark.plugin('plugin1', NoDeps)
  def testLoadPluginsSingle(registry, logprint):
      plugin._loadPlugins(info={}, names=['plugin1'])
  
      assert set(plugin.loadedPlugins()) == {'plugin1'}
  
      plugin1Definition = plugin.getPlugin('plugin1')
      assert plugin1Definition is not None
      assert plugin1Definition.loaded is True
      plugin1Definition._testLoadMock.assert_called_once()
  
      logprint.success.assert_any_call('Loaded plugin "plugin1"')
  
  
  @pytest.mark.plugin('throws', ThrowsOnLoad)
  def testLoadPluginsWithError(registry):
      with pytest.raises(Exception) as exception1:
          plugin._loadPlugins(info={}, names=['throws'])
  
      assert plugin.loadedPlugins() == []
  
      # Try again, as this shouldn't corrupt the loading system
      with pytest.raises(Exception) as exception2:
          plugin._loadPlugins(info={}, names=['throws'])
  
      # Ensure the exception is new each time
      assert exception1.value is not exception2.value
  
  
  @pytest.mark.plugin('plugin1', NoDeps)
  @pytest.mark.plugin('plugin2', DependsOnPlugin1)
  def testLoadPluginsWithDeps(registry, logprint):
      plugin._loadPlugins(info={}, names=['plugin2'])
  
      assert set(plugin.loadedPlugins()) == {'plugin1', 'plugin2'}
  
      for pluginName in ['plugin1', 'plugin2']:
          pluginDefinition = plugin.getPlugin(pluginName)
          assert pluginDefinition is not None
          assert pluginDefinition.loaded is True
          pluginDefinition._testLoadMock.assert_called_once()
  
      # Since plugin1 is the dependant, it must be loaded first
      logprint.success.assert_has_calls([
          mock.call('Loaded plugin "plugin1"'),
          mock.call('Loaded plugin "plugin2"')
      ], any_order=False)
  
  
  @pytest.mark.plugin('plugin1', NoDeps)
  @pytest.mark.plugin('plugin2', NoDeps)
  @pytest.mark.plugin('plugin3', ThrowsOnLoad)
  def testLoadPluginsExclusion(registry):
      # Ignoring installed but not-requested plugins only happens in the testing environment, but
      # is critical functionality
      plugin._loadPlugins(info={}, names=['plugin1'])
  
      assert set(plugin.loadedPlugins()) == {'plugin1'}
  
      for pluginName in ['plugin2', 'plugin3']:
          pluginDefinition = plugin.getPlugin(pluginName)
          assert pluginDefinition is not None
          assert pluginDefinition.loaded is False
          pluginDefinition._testLoadMock.assert_not_called()
  
  
  def testLoadPluginsMissing(registry):
      # This case should not typically happen outside of the testing environment
      with pytest.raises(GirderException, match='Plugin missing is not installed'):
          plugin._loadPlugins(info={}, names=['missing'])
  
      assert plugin.loadedPlugins() == []
  
  
  def testRegisterWebroot(registry):
      plugin.registerPluginWebroot('webroot', 'plugin')
      assert plugin.getPluginWebroots() == {'plugin': 'webroot'}
  