package org.ovirt.engine.core.dao; import static org.junit.Assume.assumeTrue; import java.io.InputStream; import java.sql.Connection; import java.sql.Statement; import java.util.Properties; import javax.inject.Inject; import javax.sql.DataSource; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.operation.DatabaseOperation; import org.junit.Before; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; /** * {@code BaseDaoTestCase} provides a foundation for creating unit tests for the persistence layer. The annotation * {@link Transactional}, and the listener {@link TransactionalTestExecutionListener} ensure that all test * cases ({@link org.junit.Test} methods) are executed inside a transaction, and the transaction is automatically rolled * back on completion of the test. */ @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({ TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class }) @ContextConfiguration(locations = { "classpath:/test-beans.xml" }) @Transactional public abstract class BaseDaoTestCase { protected static final Guid PRIVILEGED_USER_ID = new Guid("9bf7c640-b620-456f-a550-0348f366544b"); protected static final String PRIVILEGED_USER_ENGINE_SESSION_ID = "c6f975b2-6f67-11e4-8455-3c970e14c386"; protected static final Guid UNPRIVILEGED_USER_ID = new Guid("9bf7c640-b620-456f-a550-0348f366544a"); protected static final String UNPRIVILEGED_USER_ENGINE_SESSION_ID = "9ee57fd0-6f67-11e4-9e67-3c970e14c386"; private static boolean initialized = false; @Inject protected DbFacade dbFacade; private static Object dataFactory; protected static boolean needInitializationSql = false; protected static String initSql; protected static DataSource dataSource; @BeforeClass public static void initTestCase() throws Exception { if(dataSource == null) { try { dataSource = createDataSource(); final IDataSet dataset = initDataSet(); // load data from fixtures to DB DatabaseOperation.CLEAN_INSERT.execute(getConnection(), dataset); SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); builder.bind("java:/ENGINEDataSource", dataSource); builder.activate(); initialized = true; } catch (Exception e) { /* * note: without logging current maven setting does NOT produce stacktrace/message for following AssertionError. * this error log is absolutely vital to actually see, what went wrong! */ LoggerFactory.getLogger(BaseDaoTestCase.class).error("Unable to init tests", e); /* * note: re-throwing exception here is absolutely vital. Without it, all tests of first executed * descendant test class will be normally executed. With added assumption using Assume then all tests * will be skipped and successful tests execution will be pronounced. This exception will cause first of * executed descendant test class fail and it's constructor will not be even reached. */ throw new AssertionError("Unable to init tests", e); } } } public BaseDaoTestCase() { /* * note: all tests, which reached this point when initialization failed, can be skipped, but only if first * executed test class stated failure. Otherwise all tests will be skipped and success pronounced. */ assumeTrue("Uninitialized TestCase, cannot proceed. Look above for causing exception.", initialized); } @Before public void setUp() throws Exception { } protected static IDataSet initDataSet() throws Exception { FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder(); builder.setColumnSensing(true); return builder.build(BaseDaoTestCase.class.getResourceAsStream("/fixtures.xml")); } protected static IDatabaseConnection getConnection() throws Exception { // get connection and setup it's meta data Connection con = dataSource.getConnection(); IDatabaseConnection connection = new DatabaseConnection(con); connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, dataFactory); connection.getConfig().setProperty(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true); if (needInitializationSql) { try (Statement stmt = con.createStatement()) { stmt.executeUpdate(initSql); } } return connection; } private static DataSource createDataSource() throws Exception { DataSource result = null; Properties properties = new Properties(); String job = System.getProperty("JOB_NAME", ""); String number = System.getProperty("BUILD_NUMBER", ""); String schemaNamePostfix = job + number; try (InputStream is = BaseDaoTestCase.class.getResourceAsStream("/test-database.properties")) { properties.load(is); ClassLoader.getSystemClassLoader().loadClass( properties.getProperty("database.driver")); String dbUrl = properties.getProperty("database.url") + schemaNamePostfix; result = new SingleConnectionDataSource( dbUrl, properties.getProperty("database.username"), properties.getProperty("database.password"), true); initSql = properties.getProperty("database.initsql"); loadDataFactory(properties.getProperty("database.testing.datafactory")); if (initSql != null && !initSql.isEmpty()) { needInitializationSql = true; } } return result; } private static void loadDataFactory(String dataFactoryClassname) throws Exception { Class<?> clazz = Class.forName(dataFactoryClassname); dataFactory = clazz.newInstance(); } public DbFacade getDbFacade() { return dbFacade; } public static DataSource getDataSource() { return dataSource; } }