/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.nio.channels.FileChannel; import java.sql.SQLException; import java.util.Properties; import java.util.TimeZone; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.dspace.administer.RegistryImportException; import org.dspace.authorize.AuthorizeException; import org.dspace.browse.BrowseException; import org.dspace.content.NonUniqueMetadataException; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.junit.*; import static org.junit.Assert.*; import mockit.*; import org.apache.log4j.Logger; import org.dspace.administer.MetadataImporter; import org.dspace.administer.RegistryLoader; import org.dspace.browse.IndexBrowse; import org.dspace.browse.MockBrowseCreateDAOOracle; import org.dspace.content.MetadataField; import org.dspace.core.ConfigurationManager; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.search.DSIndexer; import org.dspace.storage.rdbms.MockDatabaseManager; import org.xml.sax.SAXException; /** * This is the base class for Unit Tests. It contains some generic mocks and * utilities that are needed by most of the unit tests developed for DSpace * * @author pvillega */ @UsingMocksAndStubs({MockDatabaseManager.class, MockBrowseCreateDAOOracle.class}) public class AbstractUnitTest { /** log4j category */ private static Logger log = Logger.getLogger(AbstractUnitTest.class); //Below there are static variables shared by all the instances of the class /** * Test properties */ protected static Properties testProps; //Below there are variables used in each test /** * Context mock object to use in the tests */ protected Context context; /** * EPerson mock object to use in the tests */ protected static EPerson eperson; /** * This method will be run before the first test as per @BeforeClass. It will * initialize resources required for the tests. * * Due to the way Maven works, unit tests can't be run from a POM package, * which forbids us to run the tests from the Assembly and Configuration * package. On the other hand we need a structure of folders to run the tests, * like "solr", "report", etc. * * This method will create all the folders and files required for the tests * in the test folder. To facilitate the work this will consist on a copy * from a folder in the resources folder. The ConfigurationManager will be * initialized to load the test "dspace.cfg" and then launch the unit tests. */ @BeforeClass public static void initOnce() { try { //set a standard time zone for the tests TimeZone.setDefault(TimeZone.getTimeZone("Europe/Dublin")); //load the properties of the tests testProps = new Properties(); URL properties = AbstractUnitTest.class.getClassLoader().getResource("test-config.properties"); testProps.load(properties.openStream()); //prepare the Dspace files URL origin = ClassLoader.getSystemResource("dspaceFolder"); File source = new File(origin.getPath()); File dspaceTmp = new File(testProps.getProperty("test.folder")); if (!dspaceTmp.exists()) { dspaceTmp.mkdirs(); } copyDir(source, dspaceTmp); //copy a file into assetstore for "register" tests on Bundle, Bitstream File tmpFile = new File(testProps.getProperty("test.bitstream")); File destParent = new File(testProps.getProperty("test.folder.assetstore")); destParent.mkdirs(); File dest = new File(testProps.getProperty("test.assetstore.bitstream")); if(!dest.exists()) { dest.createNewFile(); } copyFile(tmpFile, dest); //load the test configuration file URL configFile = AbstractUnitTest.class.getClassLoader().getResource(testProps.getProperty("test.config.file")); ConfigurationManager.loadConfig(configFile.getPath()); // // Initialise the service manager kernel DSpaceKernelImpl kernelImpl = null; try { kernelImpl = DSpaceKernelInit.getKernel(null); if (!kernelImpl.isRunning()) { kernelImpl.start(ConfigurationManager.getProperty("dspace.dir")); } } catch (Exception e) { // Failed to start so destroy it and log and throw an exception try { if(kernelImpl != null){ kernelImpl.destroy(); } } catch (Exception e1) { // Nothing to do } String message = "Failure during filter init: " + e.getMessage(); throw new IllegalStateException(message, e); } //load the default registries. This assumes the temporal filesystem is working //and the in-memory DB in place Context ctx = new Context(); ctx.turnOffAuthorisationSystem(); //we can't check via a boolean value (even static) as the class is destroyed by the //JUnit classloader. We rely on a value that will always be in the database if it has //been initialized to avoid doing the work twice if(MetadataField.find(ctx, 1) == null) { String base = testProps.getProperty("test.folder") + File.separator + "config"+ File.separator +"registries"+ File.separator; RegistryLoader.loadBitstreamFormats(ctx, base + "bitstream-formats.xml"); MetadataImporter.loadRegistry(base + "dublin-core-types.xml", true); MetadataImporter.loadRegistry(base + "sword-metadata.xml", true); ctx.commit(); //create eperson if required eperson = EPerson.find(ctx, 1); if(eperson == null) { eperson = EPerson.create(ctx); eperson.setFirstName("first"); eperson.setLastName("last"); eperson.setEmail("test@email.com"); eperson.setCanLogIn(true); } //Create search and browse indexes DSIndexer.cleanIndex(ctx); DSIndexer.createIndex(ctx); ctx.commit(); //indexer does a 'complete' on the context IndexBrowse indexer = new IndexBrowse(ctx); indexer.setRebuild(true); indexer.setExecute(true); indexer.initBrowse(); } ctx.restoreAuthSystemState(); if(ctx.isValid()) { ctx.complete(); } ctx = null; } catch (BrowseException ex) { log.error("Error creating the browse indexes", ex); fail("Error creating the browse indexes"); } catch (RegistryImportException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (NonUniqueMetadataException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (ParserConfigurationException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (SAXException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (TransformerException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (AuthorizeException ex) { log.error("Error loading default data", ex); fail("Error loading default data"); } catch (SQLException ex) { log.error("Error initializing the database", ex); fail("Error initializing the database"); } catch (IOException ex) { log.error("Error initializing tests", ex); fail("Error initializing tests"); } } /** * Copies one directory (And its contents) into another * * @param from Folder to copy * @param to Destination * @throws IOException There is an error while copying the content */ protected static void copyDir(File from, File to) throws IOException { if(!from.isDirectory() || !to.isDirectory()) { throw new IOException("Both parameters must be directories. from is "+from.isDirectory()+", to is "+to.isDirectory()); } File[] contents = from.listFiles(); for(File f: contents) { if(f.isFile()) { File copy = new File(to.getAbsolutePath() + File.separator + f.getName()); copy.createNewFile(); copyFile(f, copy); } else if(f.isDirectory()) { File copy = new File(to.getAbsolutePath() + File.separator + f.getName()); copy.mkdir(); copyDir(f, copy); } } } /** * Removes the copies of the origin files from the destination folder. Used * to remove the temporal copies of files done for testing * * @param from Folder to check * @param to Destination from which to remove contents * @throws IOException There is an error while copying the content */ protected static void deleteDir(File from, File to) throws IOException { if(!from.isDirectory() || !to.isDirectory()) { throw new IOException("Both parameters must be directories. from is "+from.isDirectory()+", to is "+to.isDirectory()); } File[] contents = from.listFiles(); for(File f: contents) { if(f.isFile()) { File copy = new File(to.getAbsolutePath() + File.separator + f.getName()); if(copy.exists()) { copy.delete(); } } else if(f.isDirectory()) { File copy = new File(to.getAbsolutePath() + File.separator + f.getName()); if(copy.exists() && copy.listFiles().length > 0) { deleteDir(f, copy); } copy.delete(); } } } /** * Copies one file into another * * @param from File to copy * @param to Destination of copy * @throws IOException There is an error while copying the content */ protected static void copyFile(File from, File to) throws IOException { if(!from.isFile() || !to.isFile()) { throw new IOException("Both parameters must be files. from is "+from.isFile()+", to is "+to.isFile()); } FileChannel in = (new FileInputStream(from)).getChannel(); FileChannel out = (new FileOutputStream(to)).getChannel(); in.transferTo(0, from.length(), out); in.close(); out.close(); } /** * This method will be run before every test as per @Before. It will * initialize resources required for the tests. * * Other methods can be annotated with @Before here or in subclasses * but no execution order is guaranteed */ @Before public void init() { try { //we start the context context = new Context(); context.setCurrentUser(eperson); context.commit(); } catch (SQLException ex) { log.error(ex.getMessage(),ex); fail("SQL Error on AbstractUnitTest init()"); } } /** * This method will be run after every test as per @After. It will * clean resources initialized by the @Before methods. * * Other methods can be annotated with @After here or in subclasses * but no execution order is guaranteed */ @After public void destroy() { if(context != null && context.isValid()) { context.abort(); } } /** * This method will be run after all tests finish as per @AfterClass. It will * clean resources initialized by the @BeforeClass methods. * */ @AfterClass public static void destroyOnce() { try { //the database will be cleaned automatically on shutdown (in-memory db) //we clear the copied resources URL origin = ClassLoader.getSystemResource("dspaceFolder"); File source = new File(origin.getPath()); File dspaceTmp = new File(testProps.getProperty("test.folder")); deleteDir(source, dspaceTmp); File destParent = new File(testProps.getProperty("test.folder.assetstore")); deleteDir(destParent,destParent); //we clear the properties testProps.clear(); testProps = null; } catch (IOException ex) { log.error("Error cleaning the temporal files of testing", ex); } } /** * This method checks the configuration for Surefire has been done properly * and classes that start with Abstract are ignored. It is also required * to be able to run this class directly from and IDE (we need one test) */ /* @Test public void testValidationShouldBeIgnored() { assertTrue(5 != 0.67) ; } */ /** * This method expects and exception to be thrown. It also has a time * constraint, failing if the test takes more than 15 ms. */ /* @Test(expected=java.lang.Exception.class, timeout=15) public void getException() throws Exception { throw new Exception("Fail!"); } */ }