How to make a manager 2

From BioclipseWiki

Jump to: navigation, search
Responsible author:jonalv
Bioclipse version:
Last updated:2011-8-4
Tags:


This page is the second version of the How to make a manager page. It describes what is sometimes known as "The third world order" but more precisely a way of creating managers where the manager implementation class does not implement the interface declaring what the manager can do.

See also Bioclipse SDK which helps to automate the making of a manager.

Contents

Building the manager

A Bioclipse Manager is an object used for performing things related to a specific topic in Bioclipse. As an example we'll take the ui manager. It contains methods for doing stuff like opening files in editors and doing other stuff with files. The same code for doing this is being used both from the graphical user interface (GUI) and from the JavaScript console and scripts.

However, there are a few differences on how you want to call manager methods from the GUI and from JavaScript. For example, let's look at the method that removes files: ui.remove. When calling this method from JavaScript we would like to write: ui.remove("/MyProject/temp.cml"). But when using the manager from the GUI, we get an object of the type IFile so then we want to call the method remove(IFile file) on the manager.

There are also a few other tedious tasks that we want the managers to leverage. For example, if we look at the delete method of an IFile it can take an IProgressMonitor which can be used for giving feedback to the user on progress when doing an operation that might take a while. However, in order to do this in a good way, we need to be running a job. Another problem is that when doing GUI stuff such as opening an editor, this must be done by the GUI thread, but if an exception is thrown in that thread, we still want feedback about what went wrong on the JavaScript console, if that happens to be the way we were calling the manager method.

What follows is a collection of instructions on how to do things like easily running a manager method as a job and converting between String and IFile and a bunch of other stuff.

Start by writing the manager interface

In order to be usable with Spring for AOP later on, our Bioclipse manager is going to be written to work according to an interface. Bioclipse uses annotations and method signature conventions for doing automagic things with managers. Let's start out by looking at some example code:

 @PublishedClass(value = "Controls access to Bioclipse UI.")
 @TestClasses(
   "net.bioclipse.ui.business.tests.UIManagerTest," +
   "net.bioclipse.ui.business.tests.UIManagerPluginTest"
 )
 public interface IUIManager extends IBioclipseManager {
 
   @Recorded
   @PublishedMethod(params="String filePath",
                    methodSummary="Opens a file in an editor.")
   @TestMethods("testOpen_String")
   @GuiAction
   public void open(String filePath);
 
   @Recorded
   @GuiAction
   public void open(IFile file);

This is a part of the interface defining the ui manager. I have marked all the annotations in different color (yes I know it's a bit too colorful, but it's for a good cause so please bear with me...). Just by the number of different colors we can see that there are many annotations involved here. Let's go through them one by one.

The @PublishedClass and @PublishedMethod annotations

The first annotation found in the example is @PublishedClass. It is used by the help system in the JavaScript console in Bioclipse. The value of this annotations is the first text that is showed when you write man ui in the JavaScript console.

The @PublishedMethod annotation contains the text shown when asking for help on a specific manager method. For example when writing man ui.open. This annotation takes two paramaters — params and methodSummary. These two paramaters are used for documenting the manager methods and they should be used in a very special way for our manager documentation to be consistent. The params should be "a comma separated list of the type (without package name) and name of each parameter, separated by a space, e.g. 'IMolecule molecule' or 'ICDKMolecule mol, boolean isFancy'". (That is: exactly as the Java-method parameter list definition that is on the next line. If you copy that one you should be safe.)

All the explanations about those paramaters shall be found in the methodSummary paramater of the annotation. Here follows an example that hopefully makes things even clearer:

 
 @Recorded
 @PublishedMethod(params = "IMolecule mol, boolean overwrite",                                  
                  methodSummary = "saves mol to a file, if previously read from file. " +       
         		           "Overwrite determines if existing file shall be overwritten.")
 @TestMethods("testSaveMolecule_IMolecule_boolean")
 public void saveMolecule(IMolecule mol, boolean overwrite)
             throws BioclipseException, CDKException, CoreException;
 

If the methods takes zero parameters just skip the annotation-parameter named "params". It is not mandatory (just becasue of this situation, actually).

The @TestClasses and @TestMethods annotations

These two annotations are used by the test framework for keeping track of which test exercises which methods, and for making sure each method is covered by a test. The @TestClasses annotation is used to specify in which test classes the actual method that tests this manager's methods can be found, and the annotation @TestMethods specifies the names of the test methods that test the annotated method. The @TestClasses annotates a manager interface, and @TestMethods annotates a manager method. These tests should be plugin-tests but more on that topic later.

The @GuiAction annotation

The @GuiAction annotation is used by a Spring AOP advice (more about them later), basically it indicates that this method should be run by the SWT GUI thread. The reason for this annotation was that exception handling for code running in the SWT GUI thread was excessively cumbersome. To leverage, this solution lifts out the exception handling from the manager method using AOP. As a bonus, the part where you implement the Runnable interface and give it to the asyncExec method got lifted out as well and all that remains to do for the manager author is to add the @GuiAction annotation on a method designed to be run in the main user interface thread.

Any method annotated with the @GuiAction annotation will be executed in the GUI thread, and exceptions thrown during such an execution will be printed to the JavaScript console if and when the method was called from the JavaScript console. If run from Java ( e.g. called from the UI) the exception should render an error dialog instead.

The @Recorded annotation

Also the Recorded annotation is used by a Spring AOP advice (more about them later). If a method has this annotation the fact that it was called and with what parameters it was called will be recorded in such a way that a JavaScript script repeating whatever the user did can be generated. At least in theory. However this feature is very experimental for the moment and it is not fully working yet.

The translation from a String representing a file path to an IFile (and vice versa)

This translation is done automagicly with the help of Spring AOP based on some method signature conventions. If the interface declares a method with the same name and the same number of parameters as the one found in the manager implementation but taking a String instead of the IFile in the implemented method, then that String will be transformed into an IFile and the method taking an IFile will be called instead.

Similarly if two similar methods are declared in the interface, differing only in that one of them returns an IFile and the other a String then only this one returning an IFile needs to be implemented. When the other one is called the one method will be called and the resulting IFile will be transformed into a String at runtime with the help of AOP before returned to the manager method call.

Running manager methods as jobs

Manager methods can be run as Eclipse jobs. This however is not done by annotations but by method signature conventions. Note that the methods must be void. If you want to reeturn a result, see next section. A manager method that fulfills the following criteria will be run as a job:

If there is no method in the manager class with the exact same signature as the called one on the interface but there is one identical (except for String / IFile variations) plus as the last parameter an IProgressMonitor then the method taking the IProgressMonitor will be called in a job with the jobs progress monitor.

Running manager methods as jobs which return a result

This is the way to go if you want to have a method which returns a result and runs as a job. This is done by method signature convention as well. As an example, take a method which is supposed to take a list of molecules and a "compare molecule" and calculate the Tanimoto similarity of the molecules to the "compare molecule", returning them as a list of floats. In Javascript, this should be "blocking", in Java, we would like to run it as a job, but still get the result for display at the end. Our manager interface will have two methods:

public List<Float> calculateTanimoto( List<IMolecule> calculateFor, IMolecule reference ) throws BioclipseException;
public void calculateTanimoto( List<IMolecule> calculateFor, IMolecule reference, BioclipseUIJob<List<Float>> uiJob ) throws BioclipseException;

The methods share the two "real world" parameters calculateFor and reference. The first method can be called straightforward and will typically be used on console (you would need to add annotations as explained). The second method is the one we will use in Java. The BioclipseUIJob will give us the result. Code using this would look like this:

ourmanager.calculateTanimoto( mols, reference, new BioclipseUIJob<List<Float>>() {
               @Override
               public void runInUI() {
                   List<Float> result = getReturnValue();
                   //here do something with the result
               }
});

If the operation takes long time to finish you might not want the result to open up at once when it is ready. For example when performing a lengthy search operation where the result will be shown in an editor the user might be doing other stuff in Bioclipse while waiting for the search to finish and don't want an editor to pop up from nowhere. Instead the user wants to click in the progress view to show the result. To get this behavior the @SilentNotification annotation can be used. It takes two optional parameters: message and silentAfter. message specifies the link text that should be clicked in the progress view to show the result and silentAfter gives a time in ms to when the silent notification should be turned on. The default value for the message is: "Job completed, results available!" and for the timeout: 500 ms. So if the job is finished in less than half a second the BioclipseUIJob will be run at once and if it takes longer the BioclipseUIJob will be run when the user clicks the link in the progress view.

Running manager methods as jobs which return partial results

If you want your job to return your result bitwise while it runs, you can also do this. This is useful if you want to report on progress to your users. For the above Tanimoto example, you would add this method to the manager interface:

public BioclipseJob<Float> calculateTanimoto( List<IMolecule> calculateFor, 
                                              IMolecule reference, 
                                              BioclipseJobUpdateHook<Float> hook ) 
                           throws BioclipseException;

Note the signature contains a BioclipseJobUpdateHook instead of the BioclipseUIJob and this is parameterised with Float (whereas the Job has a List<Float>), since it will return single results.

Calling this method would look like this:

ourmanager.calculateTanimoto( mols, reference, 
           new BioclipseJobUpdateHook<Float>("Tanimoto"){
               public void partialReturn( Float chunk ) {
                    //here you would update your user interface
                    //and you could collect results in a list or whatever you like
               }
           });

The BioclipseJob returned by the calculateTanimoto method can for example be used when waiting for a job to finish by calling the join method on it. This can be useful when starting a bunch of jobs and collecting results from all of them and only continue when all results have been collected. The parameter given to the BioclipseJobUpdateHook ("Tanimoto") is the name of the created job. This is used for identifying a specific job in the view that list jobs.

Then implement the manager methods

Next step is to write our manager methods. Our dispatchers are going to catch calls to the methods declared in the interface and do some transformations of the parameters so that they conform to one single manager in all cases. For example translate any Strings at a position where the manager method takes an IFile into an IFile.

An implementation of the two open() methods mentioned in the explanation of the interface would look like this:

public void open( final IFile file ) {
   //do something with file here
}

Note this directly corresponds to the open(IFile) method in the interface, but will also be used if open(String path) is called.

Running manager methods as jobs which return a result

Both methods mentioned above under this heading are implemented in one method. This would look like this:

public void calculateTanimoto( List<IMolecule> calculateFor,
                               IMolecule reference,
                               IReturner returner,
                               IProgressMonitor monitor)
            throws BioclipseException {
 List<Float> result = new ArrayList<Float>();
 //Do calculation
 returner.completeReturn( result ); 
}

Notice that the method is void and returns its result via the returner. The method signature has the two "real world" parameters, an IReturner and an IProgressMonitor. These will be created automatically by the framework.

This is the way to go if you only have the interface method which returns the complete result. If you have methods for complete and partial returns, see next section.

If you have two manager methods which would normally call each other (e. g. you want to have method work on a single IMolecule as well), we recommend that you create a private method doing the actual work just returning the result (no IReturner involved), which you can call from both methods and wrap the result in the returner there.

Running manager methods as jobs which return partial results

If you have all three tanimoto methods mentioned above, you would still have only one method in the implementation. This would look lik this:

public void calculateTanimoto( List<IMolecule> calculateFor,
                               IMolecule reference,
                               IReturner<Float> returner,
                               IProgressMonitor monitor)
            throws BioclipseException {
 for(IMolecule mol : calculateFor){
   //Do calculation for mol
   Float result = ...;
   returner.partialReturn( result );
 }
}

The difference is that you do not use the completeReturn method of the Returner, but the partialReturn (more than once to make sense). If the interface method for the complete return is called (i. e. the one with the BioclipseUIJob), the results will automatically collected and returned.

Hooking in all the Spring AOP stuff

It has become time to write the XML configuration file that Spring will read and use as a recipe when instantiating our manager objects. One for JavaScript and one for Java. Both objects are based on the same class and the same parent interface but they have different Spring advices wired in to them. These configurations are made in XML files situated in META-INF/spring. You can either put all in one file or split it up among many XML files if you would prefer that. But let's look at an example:


 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:osgi="http://www.springframework.org/schema/osgi"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/osgi 
                            http://www.springframework.org/schema/osgi/spring-osgi.xsd">
 
   <osgi:reference id="recordingAdvice"                                                              
                   interface="net.bioclipse.recording.IRecordingAdvice" />                           
                                                                                                     
   <osgi:reference id="javaManagerDispatcherAdvisor"                                                 
                   interface="net.bioclipse.managers.business.IJavaManagerDispatcherAdvisor" />      
                                                                                                     
   <osgi:reference id="javaScriptManagerDispatcherAdvisor"                                           
                   interface="net.bioclipse.managers.business.IJavaScriptManagerDispatcherAdvisor"/> 
                                                                                                     
   <osgi:reference id="wrapInProxyAdvice"                                                            
                   interface="net.bioclipse.recording.IWrapInProxyAdvice" />                         
 
   <bean id="recordingAdvisor"                                                
         class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 
     <property name="advice"  ref="recordingAdvice" />                        
     <property name="pattern" value=".*" />                                   
   </bean>                                                                    
 
   <bean id="uiManagerTarget"                         
         class="net.bioclipse.ui.business.UIManager"> 
   </bean>                                            
 
   <bean id="javaUiManager"                                          
         class="org.springframework.aop.framework.ProxyFactoryBean"> 
     <property name="target"                                         
 	       ref="uiManagerTarget" />                              
     <property name="proxyInterfaces"                                
 	       value="net.bioclipse.ui.business.IUIManager" />       
     <property name="interceptorNames">                              
       <list>                                                        
          <value>recordingAdvisor</value>                            
          <value>wrapInProxyAdvice</value>                           
          <value>javaManagerDispatcherAdvisor</value>                
       </list>                                                       
     </property>                                                     
   </bean>                                                           
 
   <bean id="javaScriptUiManager"                                    
         class="org.springframework.aop.framework.ProxyFactoryBean"> 
     <property name="target"                                         
               ref="uiManagerTarget" />                              
     <property name="proxyInterfaces"                                
               value="net.bioclipse.ui.business.IJSUIManager" />     
     <property name="interceptorNames" >                             
       <list>                                                        
         <value>recordingAdvisor</value>                             
         <value>wrapInProxyAdvice</value>                            
         <value>javaScriptManagerDispatcherAdvisor</value>           
       </list>                                                       
     </property>                                                     
   </bean>                                                           
 
  <osgi:service                                            
     id="javaUiManagerOSGI"                                
     ref="javaUiManager"                                   
     interface="net.bioclipse.ui.business.IUIManager" />   
                                                           
   <osgi:service                                           
     id="javaScriptUiManagerOSGI"                          
     ref="javaScriptUiManager"                             
     interface="net.bioclipse.ui.business.IJSUIManager" /> 
 
 </beans>

This is actually the entire file for the plugin containing the ui manager. I have color coded the different parts of the file.

Our actual manager bean, the uiManagerTarget bean

Let's start with something easy -- the jmolManagerTarget bean. A Spring bean is basically just an instantiated class. This bean is the instantiation of our manager class. It is called target because it is going to be the target object of the manager proxies that we are going to create later.

Importing all advices

But before we start defining proxies let's get a hold on all the advices that we re going to use. These advices are instantiated by Spring in other Bioclipse plugins and published to the OSGI container by the Spring OSGI extender bundle. So each osgi:reference tag in this example imports the service registered for the given interface and places it in the local Spring container so that it can be used when putting together other beans.

The recording advisor

This is a recording specific thing which gives a chance to change which methods should be "decorated" with the recorder advice. For the moment it is called on all methods and then it checks for the @Recorded annotation and acts if that annotation is present. For the future it would be nice to have an Advisor that looks for that annotation instead of simply doing all methods.

Building the manager object to be used from the gui

This is the part where we specify how our manager is going to be weaved together. Basically we specify the proxy that is going to be controlling the execution of a certain manager method. We define which interface we are going to be proxying and what bean is going to be proxied. Then comes a list of interceptors that we want to add to our service object. For a typical GUI flavoured manager these are:

Advisor Description
recordingAdvisor Does the recording of manager methods
wrapInProxyAdvice Wraps all return IBIoObjects in a Spring proxy so calls to they can be recorded
javaManagerDispatcherAdvisor Makes sure the right method is called, and creates job and a lot more

Building the manager object to be used from JavaScript

From JavaScript we need another flavor of our manager. For example, progress monitors are handled in a different way since when running a JavaScript script we don't know how many ticks the progress monitor will get before being done. The advices here are:

Advisor Description
recordingAdvisor Records method calls
wrapInProxyAdvice Wraps all return IBIoObjects in a Spring proxy so calls to they can be recorded
javaScriptManagerDispatcherAdvisor Makes sure the right method is called

Publishing our manager objects

Finally we publish our new manager beans to the OSGI container so that other plugins can use them. Just as the beans we imported where associated with an interface these beans are associated with an interface when exported.

Making our manager available from JavaScript and for our gui code

The default way of getting a manager for GUI use has become to have a method on the Activator of the plugin containing said manager and here is what is needed for that. Once again let's have a look at some example code:

 public class Activator extends AbstractUIPlugin {
 
   // The plug-in ID
   public static final String PLUGIN_ID = "net.bioclipse.ui.business";
 
   // The shared instance
   private static Activator plugin;
 
   private ServiceTracker finderTracker;
   private ServiceTracker jsFinderTracker;
 
   /**
   * The constructor
   */
   public Activator() {
   }
 
   public void start(BundleContext context) throws Exception {           
     super.start(context);                                               
     plugin = this;                                                      
 	                                                                   
     finderTracker = new ServiceTracker( context,                        
                                         IUIManager.class.getName(),     
                                         null );                         
     finderTracker.open();                                               
                                                                         
     jsFinderTracker = new ServiceTracker( context,                      
                                           IJSUIManager.class.getName(), 
                                           null );                       
     jsFinderTracker.open();                                             
   }                                                                     
 
   public void stop(BundleContext context) throws Exception {
     plugin = null;
     super.stop(context);
   }
 
   /**
    * Returns the shared instance
    *
    * @return the shared instance
    */
   public static Activator getDefault() {
     return plugin;
   }
 
   public IUIManager getUIManager() {                                       
     IUIManager uiManager;                                                  
                                                                            
     try {                                                                  
       uiManager                                                            
           = (IUIManager) finderTracker.waitForService(1000*30);            
     }                                                                      
     catch (InterruptedException e) {                                       
       throw                                                                
         new IllegalStateException("Could not get js console manager", e);  
     }                                                                      
     if (uiManager == null) {                                               
       throw new IllegalStateException("Could not get js console manager"); 
     }                                                                      
     return uiManager;                                                      
   }                                                                        
 
   public IUIManager getJSUIManager() {                                     
     IJSUIManager jsuiManager;                                              
                                                                            
     try {                                                                  
       jsuiManager                                                          
           = (IJSUIManager) jsFinderTracker.waitForService(1000*30);        
     }                                                                      
     catch (InterruptedException e) {                                       
       throw                                                                
         new IllegalStateException("Could not get js console manager", e);  
     }                                                                      
     if (jsuiManager == null) {                                             
       throw new IllegalStateException("Could not get js console manager"); 
     }                                                                      
     return jsuiManager;                                                    
   }                                                                        
 }

The start method

In order to get our manager objects we need to fetch them from the OSGI container. This is done with service trackers which are initialized in the start method.

The getUIManager() and the getJSUIManager() method

In the get methods we ask the service trackers to get the manager objects from the OSGI container. Note that we also give them a time out for how long they should wait before giving up since we can't be sure that the managers actually are in the OSGI container. The exception thrown in this methods are often seen in stack traces when something with the Spring configuration or for example the order plugins are started is wrong. What it means is just that the manager object asked for was not to be found in the OSGI container. Exactly why that was so is not always so easy to figure out though...

 public class UIManagerFactory implements IExecutableExtension, 
                                          IExecutableExtensionFactory {
 
   private Object jsConsoleManager;
   
   public void setInitializationData( IConfigurationElement config,       
                                      String propertyName,                
                                      Object data) throws CoreException { 
                                                                          
     jsConsoleManager = Activator.getDefault().getJSUIManager();          
   }                                                                      
   
   public Object create() throws CoreException { 
     return jsConsoleManager;                    
   }                                                      
 }

The ManagerFactory is used for getting the manager into JavaScript

Managers that are given to the extension point named net.bioclipse.core.scriptingContribution are injected into JavaScript. In order to give a manager to the extension point we use a factory method. In the initialization method it fetches the manager object and the create method it simply returns that object. So we give an instance of the manager factory to the extension point and when it needs the manager it will get it through the create method.

Manager best practices

  • If your manager method ends with creating a file, do not return void but rather a String with the workspace-relative path to the file. This greatly simplifies for people writing scripts.
  • If you return a List<? extends IBioObject>, e.g. List<IMolecule>, return a BioList implementation and not an ArrayList. BioList makes the list available for recording and also allows it to be opened in ui.open(myBioList).
  • Write methods to simplify for users. If you think users may want to operate on one molecule and on a list of molecules, write two methods! It is not good practice to force users to use a list if they only want to input a single molecule.
  • Use List<> for input parameters, e.g. use List<IMolecule> and not IMolecule[]

A few last general tips

A good tip is to make your managers stateless, i.e. without non-final instance variables. A stateless class is much easier to reason about when several threads are working on it, as might happen to any manager class.

If you are working with Resources, you should consider using Eclipse's jobs functionality.

Also have a look at the Building a recordable plugin text.

Testing the manager

To help you write your managers in a way that Bioclipse understand what you mean, there is a test framework to test against expected pattern (those listed above).

Conformance testing

API Testing

This is easiest done by extending the Bioclipse2 testing framework defined in AbstractManagerTest. Create an APITest.java which resembles the following, and change GistManager to the manager you want to test:

 public class APITest extends AbstractManagerTest {
 
   GistManager gist;
 
   @Override
   public IBioclipseManager getManager() {
     return gist;
   }
 
   @Override
   public Class<? extends IBioclipseManager> getManagerInterface() {
     return IGistManager.class;
   }
 
 }

Ensuring you test all functionality

This is easiest done by extending the Bioclipse coverage testing framework defined in AbstractCoverageTest (and replace the GistManager details with those about your manager):

 public class CoverageTest extends AbstractCoverageTest {
   
   private static GistManager manager = new GistManager();
 
   @Override
   public IBioclipseManager getManager() {
     return manager;
   }
 
   @Override
   public Class<? extends IBioclipseManager> getManagerInterface() {
     return IGistManager.class;
   }
 
 }

Functionality Testing

All functionality exposed by managers must be tested in detail, both when run from a Java and from a JavaScript environment. This section explains how we recommend this is done.

It refers to two test class, JavaGistManagerPluginTest and JavaScriptGistManagerPluginTest, which do not contain any actual unit test, but extend a third class AbstractGistManagerPluginTest which does. The reason behind this, is that all functionality must be tested when run from both a Java and a JavaScript environment.

And the JavaGistManagerPluginTest and JavaScriptGistManagerPluginTest only differ, and this is important, in how to instantiate a proxy to the manager; one gets a Java flavor, the other a JavaScript flavor:

 public class JavaGistManagerPluginTest
      extends AbstractGistManagerPluginTest {
 
   @BeforeClass 
   public static void setup() {
     gist = net.bioclipse.gist.Activator.getDefault().getJavaManager();
   }
 
 }

And

 public class JavaScriptGistManagerPluginTest 
      extends AbstractGistManagerPluginTest {
 
   @BeforeClass 
   public static void setup() {
     gist = net.bioclipse.gist.Activator.getDefault().getJavaScriptManager();
   }
 
 }

Putting it together in Suites

To simplify running all tests for a single manager, two test suites are typically set up: one Suite for testing the functionality as JUnit Plugin tests (which allows running them from within a running Bioclipse instance), and old-fashion unit test using non-plugin testing which is faster to run (as it does not require Bioclipse to start fully), and which can be used to test the way the Manager code is written (APITest) and what is tested (CoverageTest).

Non-Plugin tests

This suite will look like the following:

 @RunWith(Suite.class)
 @SuiteClasses({
   APITest.class,
   CoverageTest.class
 })
 public class AllGistManagerTests {
 
 }

Plugin tests

A JUnit4 suite to run all PluginTests can be created using this template, e.g. names as AllGistManagerPluginTests:

 @RunWith(Suite.class)
 @SuiteClasses({
   JavaGistManagerPluginTest.class,
   JavaScriptGistManagerPluginTest.class
 })
 public class AllGistManagerPluginTests {
 
 }


Running the Plugin Tests

Running the Plugin Tests is a bit tricky, as Eclipse start by doing it wrong.

Step 1. Set up a configuration

Right click on the AllGistManagerPluginTests (or the equivalent for your manager) test, and select Run as -> JUnit Plug-in Test. This will fail, but at least gives a run configuration.

Step 2. Fix the run configuration

Select from the menu Run -> Run Configurations... Select the JUnit Plug-in Test AllGistManagerPluginTests, and go to the Main tab:

Image:RunPluginTest.png

Make sure to Program to Run -> Run a product to net.bioclipse.ui.product as shown above.

Then, go to the Plug-ins tab, and set the Launch with to 'plugins selected below only' as shown below:

Image:RunPluginTest1.png

Then follow these steps:

 1. Deselect All plug-ins
 2. unselect 'Include optional dependencies ...' and 'Add new workspace plug-ins ...' (see above screenshot)
 3. select the plugin where your AllGistManagerPluginTests is located
 4. hit Add Required Plug-ins
 5. hit the Validate plugin

Then you should hit run. If you get an error about "The System Bundle's start level can not be modified", then you must reset the target platform.

Personal tools