/* * 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.felix.das; import java.io.IOException; import java.io.InputStream; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import junit.framework.Assert; import org.apache.felix.das.util.DriverLoader; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.ServiceReference; import org.osgi.service.device.Constants; import org.osgi.service.device.Device; import org.osgi.service.device.Driver; import org.osgi.service.device.DriverLocator; import org.osgi.service.device.DriverSelector; import org.osgi.service.device.Match; import org.osgi.service.log.LogService; /** * Test the actual implementation. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class DeviceManagerTest { @Mock private DeviceManager m_manager; @Mock Bundle m_systemBundle; @Mock private LogService m_log; private BundleContext m_context; private OSGiMock m_osgi; @Before public void setUp() throws Exception { m_osgi = new OSGiMock(); MockitoAnnotations.initMocks(this); m_context = m_osgi.getBundleContext(); m_manager = new DeviceManager( m_context ); Utils.invoke( m_manager, "init" ); Utils.inject( m_manager, LogService.class, m_log ); Mockito.when( m_context.getBundle( 0 ) ).thenReturn( m_systemBundle ); final CountDownLatch latch = new CountDownLatch( 1 ); Answer<Integer> answer = new Answer<Integer>() { public Integer answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); return Bundle.ACTIVE; } }; Mockito.when( m_systemBundle.getState() ).thenAnswer( answer ); Utils.invoke( m_manager, "start" ); latch.await( 5, TimeUnit.SECONDS ); Mockito.when( m_context.installBundle(Mockito.isA( String.class ), ( InputStream ) Mockito.isNull() ) ) .thenThrow(new NullPointerException( "inputstream is null exception" ) ); } @After public void tearDown() throws Exception { Utils.invoke( m_manager, "stop" ); Utils.invoke( m_manager, "destroy" ); } private Driver tstCreateDriver( String driverId, int match ) throws Exception { Properties p = new Properties(); p.put( Constants.DRIVER_ID, driverId ); p.put( "match", Integer.toString( match ) ); return tstCreateDriver( p ); } private Driver tstCreateDriver( Properties p ) throws Exception { Driver driver = Mockito.mock( Driver.class ); ServiceReference ref = m_osgi.registerService( new String[]{ Driver.class.getName() }, driver, p ); MatchAnswer answer = new MatchAnswer( ref ); Mockito.when( driver.match( Mockito.isA( ServiceReference.class ) ) ) .thenAnswer( answer ); Bundle bundle = m_osgi.getBundle( ref ); Mockito.when( bundle.getLocation() ) .thenReturn( DriverLoader.DRIVER_LOCATION_PREFIX + p.getProperty( Constants.DRIVER_ID )); return driver; } private Device tstCreateDevice( String[] cat ) { return tstCreateDevice( cat, true ); } private Device tstCreateDevice( String[] cat, boolean isDevice ) { Properties p = new Properties(); p.put( Constants.DEVICE_CATEGORY, cat ); if ( isDevice ) { return ( Device ) tstCreateService( p, Device.class ); } return tstCreateService( p, Object.class ); } @SuppressWarnings("unchecked") private <T> T tstCreateService( Properties p, Class<?> iface ) { T svc = ( T ) Mockito.mock( iface, iface.getSimpleName() ); m_osgi.registerService( new String[] { iface.getName() }, svc, p ); return svc; } /** * * prepared all expected behavior for the installation of a dynamic driver * bundle based on an acquired InputStream. * * * @param driverId * @param match * @param in * @return * @throws BundleException * @throws Exception */ private Driver tstExpectInstallDriverBundle( String driverId, int match, InputStream in ) throws BundleException, Exception { Bundle bundle = Mockito.mock( Bundle.class, "driverBundle" ); Mockito.when( m_context.installBundle( Mockito.eq( "_DD_" + driverId ), Mockito.eq( in ) ) ) .thenReturn( bundle ); final Driver driver = tstCreateDriver( driverId, match ); final ServiceReference driverRef = m_osgi.getReference( driver ); Answer<Object> answer = new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { m_manager.driverAdded( driverRef, driver ); return null; } }; //bundle start leads to the addition of the driver to //the device manager. Mockito.doAnswer(answer).when(bundle).start(); Mockito.when( bundle.getRegisteredServices() ) .thenReturn( new ServiceReference[]{ driverRef } ); return driver; } /** * returns a CountDownLatch. * This countdown latch will count down as soon as <code>Device.noDriverFound()</code> * has been called. * @param device the Device * @return the countdown latch */ private CountDownLatch tstExpectNoDriverFound( Device device ) { final CountDownLatch latch = new CountDownLatch( 1 ); //countdown when noDriverFound is called Answer<Object> answer = new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); return null; } }; Mockito.doAnswer( answer ).when(device).noDriverFound(); return latch; } private CountDownLatch tstExpectAttach( Driver driver, Object device ) throws Exception { final CountDownLatch latch = new CountDownLatch( 1 ); Answer<String> answer = new Answer<String>() { public String answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); return null; } }; //successful attach Mockito.when( driver.attach( m_osgi.getReference( device ) ) ) .thenAnswer( answer ); return latch; } private CountDownLatch tstExpectUnloadDriverBundle( Driver driver ) throws BundleException { final CountDownLatch latch = new CountDownLatch( 1 ); Answer<String> answer = new Answer<String>() { public String answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); return null; } }; Bundle bundle = m_osgi.getBundle( m_osgi.getReference( driver ) ); Mockito.doAnswer(answer).when( bundle ).uninstall(); return latch; } /** * This method generates behavior on the provided DriverLocator. * * The given driver Ids and their matches are expected as drivers found * by this driver locator. * Also, if a driver is found, we can also expect that loadDriver is called; * resulting in an InputStream. That particular input stream should, when installed * using a bundle context, lead to the registration of a driver with * the correcsponding driver id. * * @param locator * @param driverIds * @param matches * @return * @throws Exception */ private Map<String, Driver> tstExpectDriverLocatorFor( final DriverLocator locator, final String[] driverIds, int[] matches ) throws Exception { Mockito.when( locator.findDrivers( Mockito.isA( Dictionary.class ) ) ) .thenReturn( driverIds ); Map<String, Driver> drivers = new HashMap<String, Driver>(); final Map<String, InputStream> streams = new HashMap<String, InputStream>(); for ( String driverId : driverIds ) { InputStream in = Mockito.mock(InputStream.class, "[InputStream for: " + driverId + "]"); streams.put( driverId, in ); } Answer<InputStream> answer = new Answer<InputStream>() { public InputStream answer(InvocationOnMock invocation) throws Throwable { final String id = invocation.getArguments()[0].toString(); for ( String driverId : driverIds ) { if ( id.equals( driverId ) ) { return streams.get( id ); } } throw new IOException( "no such driverId defined in this locator: " + locator ); } }; Mockito.when( locator.loadDriver( Mockito.isA( String.class ) ) ) .thenAnswer( answer ); int i = 0; for ( String driverId : driverIds ) { Driver driver = tstExpectInstallDriverBundle( driverId, matches[i], streams.get( driverId ) ); drivers.put( driverId, driver ); i++; } return drivers; } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition */ @Test public void LocatorAdded() { DriverLocator locator = Mockito.mock( DriverLocator.class ); m_manager.locatorAdded( locator ); } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition/ removal */ @Test public void LocatorRemoved() { DriverLocator locator = Mockito.mock( DriverLocator.class ); m_manager.locatorAdded( locator ); m_manager.locatorRemoved( locator ); } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition * @throws Exception */ @Test public void DriverAdded() throws Exception { Driver driver = tstCreateDriver( "org.apache.felix.driver-1.0", 1 ); m_manager.driverAdded( m_osgi.getReference( driver ), driver ); } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition/ removal * @throws Exception */ @Test public void DriverRemoved() throws Exception { Driver driver = tstCreateDriver( "org.apache.felix.driver-1.0", 1 ); ServiceReference ref = m_osgi.getReference( driver ); m_manager.driverAdded( ref, driver ); m_manager.driverRemoved( ref ); } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition/ removal * @throws Exception */ @Test public void DeviceRemoved() throws Exception { Properties p = new Properties(); p.put(Constants.DEVICE_CATEGORY, new String[]{"dummy"}); ServiceReference ref = OSGiMock.createReference(p); Object device = new Object(); m_manager.deviceAdded( ref, device ); m_manager.deviceRemoved( ref ); } /** * does not really test anything special, but ensures that the internal * structure is able to parse the addition/ removal * @throws Exception */ @Test public void DeviceModified() throws Exception { Properties p = new Properties(); p.put(Constants.DEVICE_CATEGORY, new String[]{"dummy"}); ServiceReference ref = OSGiMock.createReference(p); Object device = new Object(); m_manager.deviceAdded( ref, new Object() ); m_manager.deviceModified(ref, device); } //intended flow, various configurations /** * We add a device, but there are no driver locators, so * the noDriverFound method must be called * @throws InterruptedException */ @Test public void DeviceAddedNoDriverLocator() throws InterruptedException { //create a mocked device Device device = tstCreateDevice( new String[] { "org.apache.felix" } ); CountDownLatch latch = tstExpectNoDriverFound( device ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !latch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected call noDriverFound" ); } } /** * We add a device, but there are no driver locators, however, there is a driver * that matches. Thus an attach must follow. * @throws Exception */ @Test public void DeviceAddedNoDriverLocatorSuccessfulAttach() throws Exception { Device device = tstCreateDevice( new String[] { "org.apache.felix" } ); Driver driver = tstCreateDriver( "org.apache.felix.driver-1.0", 1 ); CountDownLatch attachLatch = tstExpectAttach( driver, device ); m_manager.driverAdded( m_osgi.getReference( driver ), driver ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !attachLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected attach" ); } } /** * We add a device, but there are no driver locators, however, there is a driver * but it sadly doesn't match. Thus a <code>noDriverFound()</code> is called. * * @throws Exception */ @Test public void DeviceAddedNoDriverLocatorAttachFails() throws Exception { Device device = tstCreateDevice( new String[] { "org.apache.felix" } ); Driver driver = tstCreateDriver( "org.apache.felix.driver-1.0", Device.MATCH_NONE ); CountDownLatch attachLatch = tstExpectNoDriverFound( device ); m_manager.driverAdded( m_osgi.getReference( driver ), driver ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !attachLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected attach" ); } } /** * We add a device while there's one driverlocator that will successfully * locate and load two driver bundles. We expect a <code>Driver.attach()</code> for * the best matching driver. There's already a driver loaded that should not match. * * @throws Exception */ @Test public void DeviceAddedWithADriverLocator() throws Exception { final String driverId1 = "org.apache.felix.driver-1.0"; final String driverId2 = "org.apache.felix.driver-1.1"; final String notMatchingButLoadedDriverId = "dotorg.apache.felix.driver-1.0"; DriverLocator locator = Mockito.mock( DriverLocator.class ); Map<String, Driver> drivers = tstExpectDriverLocatorFor( locator, new String[] { driverId1, driverId2 }, new int[] { 30, 3 } ); Driver noMatcher = tstCreateDriver( notMatchingButLoadedDriverId, 100 ); Device device = tstCreateDevice( new String[]{ "org.apache.felix" } ); final CountDownLatch attachLatch = tstExpectAttach( drivers.get( driverId1 ), device ); final CountDownLatch unloadDriverLatch = tstExpectUnloadDriverBundle( drivers.get ( driverId2 ) ); m_manager.locatorAdded( locator ); m_manager.driverAdded( m_osgi.getReference( noMatcher ), noMatcher ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !attachLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an attach" ); } //since driver1 is attached, we expect an uninstall() //of all other (dynamically loaded) driver bundles if ( !unloadDriverLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an unload" ); } } @Test public void DeviceAddedWithADriverLocatorUnloadFails() throws Exception { final String driverId1 = "org.apache.felix.driver-1.0"; final String driverId2 = "org.apache.felix.driver-1.1"; final String notMatchingButLoadedDriverId = "dotorg.apache.felix.driver-1.0"; DriverLocator locator = Mockito.mock( DriverLocator.class ); Map<String, Driver> drivers = tstExpectDriverLocatorFor( locator, new String[] { driverId1, driverId2 }, new int[] { 30, 3 } ); Driver noMatcher = tstCreateDriver( notMatchingButLoadedDriverId, 100 ); Device device = tstCreateDevice( new String[]{ "org.apache.felix" } ); final CountDownLatch attachLatch = tstExpectAttach( drivers.get( driverId1 ), device ); final CountDownLatch unloadDriverLatch = new CountDownLatch( 1 ); ServiceReference driver2Ref = m_osgi.getReference( drivers.get( driverId2 ) ); Bundle driver2Bundle = m_osgi.getBundle( driver2Ref ); Answer<Object> answer = new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { try { throw new BundleException("test driverBundle uninstall failed"); } finally { unloadDriverLatch.countDown(); } } }; Mockito.doAnswer(answer).when(driver2Bundle).uninstall(); m_manager.locatorAdded( locator ); m_manager.driverAdded( m_osgi.getReference( noMatcher ), noMatcher ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !attachLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an attach" ); } if ( !unloadDriverLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an unload" ); } //since driver1 is attached, we expect an uninstall() //of all other (dynamically loaded) driver bundles //Driver driver = drivers.get( driverId2 ); //tstVerifyBundleUninstall( driver ); } /** * Two drivers equally match the device. There is a driver selector * that comes to the rescue that selects driver2. * * @throws Exception */ @Test public void EqualMatchWithDriverSelector() throws Exception { final String driverId1 = "org.apache.felix.driver-1.0"; final String driverId2 = "org.apache.felix.driver-1.1"; DriverLocator locator = Mockito.mock( DriverLocator.class ); Map<String, Driver> drivers = tstExpectDriverLocatorFor( locator, new String[] { driverId1, driverId2 }, new int[] { 20, 20 } ); Device device = tstCreateDevice( new String[]{ "org.apache.felix" } ); DriverSelector selector = Mockito.mock( DriverSelector.class ); SelectorMatcher matcher = new SelectorMatcher( driverId2 ); Mockito.when( selector.select( Mockito.eq( m_osgi.getReference( device ) ), Mockito.isA(Match[].class) ) ).thenAnswer( matcher ); final CountDownLatch attachLatch = tstExpectAttach( drivers.get( driverId2 ), device ); Utils.inject( m_manager, DriverSelector.class, selector ); m_manager.locatorAdded( locator ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !attachLatch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an attach" ); } //driver2 is attached, so driver1 bundle should uninstall. //Driver driver = drivers.get( driverId1 ); //tstVerifyBundleUninstall( driver ); } //exceptional flow @Test public void DriverLocator_findDriverFails() throws Exception { final CountDownLatch latch = new CountDownLatch( 1 ); Answer<String[]> answer = new Answer<String[]>() { public String[] answer(InvocationOnMock invocation) throws Throwable { latch.countDown(); throw new RuntimeException( "test exception" ); } }; DriverLocator locator = Mockito.mock( DriverLocator.class, "locator" ); Mockito.when( locator.findDrivers( Mockito.isA( Dictionary.class ) ) ) .thenAnswer( answer ); Device device = tstCreateDevice( new String[] { "org.apache.felix" } ); final CountDownLatch latch2 = new CountDownLatch( 1 ); Answer<Object> answer2 = new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { latch2.countDown(); return null; } }; Mockito.doAnswer(answer2).when(device).noDriverFound(); m_manager.locatorAdded( locator ); m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !latch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected a call to DriverLocator.findDrivers" ); } if ( !latch2.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected a call to Driver.noDriverFound" ); } } /** * This test verified correct behavior when after a driver * attach led to a referral, this referral leads to an exception. * * * @throws Exception */ @Ignore public void DriverReferral_ReferralFails() throws Exception { final String referredDriver = "org.apache.felix.driver-2.0"; String[] driverIds = new String[] { "org.apache.felix.driver-1.0", "org.apache.felix.driver-1.1" }; int[] driverMatches = new int[]{ 1, Device.MATCH_NONE }; DriverLocator locator = Mockito.mock( DriverLocator.class, "locator for v1.x" ); Map<String, Driver> drivers = tstExpectDriverLocatorFor( locator, driverIds, driverMatches ); DriverLocator locatorv2 = Mockito.mock( DriverLocator.class, "locator for v2.x (fails always)" ); Mockito.when( locatorv2.findDrivers( Mockito.isA( Dictionary.class ) ) ) .thenReturn( null ); Mockito.when( locatorv2.loadDriver( Mockito.startsWith( "org.apache.felix.driver-1" ) ) ) .thenReturn( null ); InputStream referredInputStream = Mockito.mock(InputStream.class); Mockito.when( locatorv2.loadDriver( referredDriver ) ).thenReturn( referredInputStream ); //this is what initial driver referral eventually leads //to: the loading of a driver bundle //we fake it, so that it fails Mockito.when( m_context.installBundle( Mockito.anyString(), Mockito.isA( InputStream.class ) ) ) .thenThrow(new BundleException( "test exception" ) ); Driver matched = drivers.get( "org.apache.felix.driver-1.0" ); final CountDownLatch latch = new CountDownLatch( 1 ); Answer<String> driver10_attach = new Answer<String>() { public String answer(InvocationOnMock invocation) throws Throwable { System.out.println("driver10_attach()"); latch.countDown(); return referredDriver; } }; Device device = tstCreateDevice( new String[]{ "org.apache.felix" } ); Mockito.when( matched.match( m_osgi.getReference( device ) ) ).thenReturn( 10 ); Mockito.when( matched.attach( Mockito.isA( ServiceReference.class ) ) ) .thenAnswer( driver10_attach ); // for ( String driverId : driverIds ) // { // Driver driver = drivers.get( driverId ); // tstExpectBundleUninstall( driver ); // } //the actual test m_manager.locatorAdded( locator ); m_manager.locatorAdded( locatorv2 ); //depman induced callback m_manager.deviceAdded( m_osgi.getReference( device ), device ); if ( !latch.await( 5, TimeUnit.SECONDS ) ) { Assert.fail( "expected an attach to: " + driverIds[0] ); } Mockito.verify(device).noDriverFound(); } /** * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ private class MatchAnswer implements Answer<Integer> { private final ServiceReference m_driverRef; public MatchAnswer( ServiceReference driverRef ) { m_driverRef = driverRef; } public Integer answer(InvocationOnMock invocation) throws Throwable { ServiceReference deviceRef = ( ServiceReference ) invocation.getArguments()[0]; String[] categories = String[].class.cast( deviceRef.getProperty( Constants.DEVICE_CATEGORY ) ); String driverId = String.class.cast( m_driverRef.getProperty( Constants.DRIVER_ID ) ); for ( String string : categories ) { if ( driverId.startsWith( string ) ) { Object match = m_driverRef.getProperty( "match" ); return Integer.valueOf( match.toString() ); } } return Device.MATCH_NONE; } } private class SelectorMatcher implements Answer<Integer> { private String m_driverId; public SelectorMatcher( String driverId ) { m_driverId = driverId; } public Integer answer(InvocationOnMock invocation) throws Throwable { int i = 0; Match[] matches = (Match[])invocation.getArguments()[1]; for ( Match match : matches ) { if ( match.getDriver().getProperty( Constants.DRIVER_ID ).equals( m_driverId ) ) { return i; } i++; } return DriverSelector.SELECT_NONE; } } }