diff --git a/pom.xml b/pom.xml index 6639f5f..50d015e 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,14 @@ com.oracle.jdbc ucp + + com.oracle.jdbc + ojdbc8 + + + com.oracle.jdbc + orai18n + @@ -53,6 +61,18 @@ logback-classic 1.2.3 + + com.oracle.jdbc + ojdbc8 + 12.2.0.1 + compile + + + com.oracle.jdbc + orai18n + 12.2.0.1 + compile + @@ -139,8 +159,26 @@ true + + maven.oracle.com + + true + + + false + + https://maven.oracle.com + default + + + + maven.oracle.com + https://maven.oracle.com + + + utPLSQL-local diff --git a/src/main/java/org/utplsql/cli/ReporterManager.java b/src/main/java/org/utplsql/cli/ReporterManager.java index 3c33575..fe846a7 100644 --- a/src/main/java/org/utplsql/cli/ReporterManager.java +++ b/src/main/java/org/utplsql/cli/ReporterManager.java @@ -19,6 +19,8 @@ class ReporterManager { private List reporterOptionsList; + private List reporterGatherErrors; + private ExecutorService executorService; ReporterManager(List reporterParams ) { initReporterOptionsList(reporterParams); @@ -49,6 +51,25 @@ private void initReporterOptionsList( List reporterParams ) { } } + private void abortGathering(Throwable e) { + addGatherError(e); + executorService.shutdownNow(); + } + + private void addGatherError( Throwable e ) { + if ( reporterGatherErrors == null ) { + reporterGatherErrors = new ArrayList<>(); + } + reporterGatherErrors.add(e); + } + + boolean haveGatherErrorsOccured() { + return reporterGatherErrors != null && !reporterGatherErrors.isEmpty(); + } + + List getGatherErrors() { + return reporterGatherErrors; + } /** Initializes the reporters so we can use the id to gather results * @@ -56,7 +77,7 @@ private void initReporterOptionsList( List reporterParams ) { * @return List of Reporters * @throws SQLException */ - public List initReporters(Connection conn, ReporterFactory reporterFactory, CompatibilityProxy compatibilityProxy) throws SQLException + List initReporters(Connection conn, ReporterFactory reporterFactory, CompatibilityProxy compatibilityProxy) throws SQLException { final List reporterList = new ArrayList<>(); @@ -79,10 +100,14 @@ public List initReporters(Connection conn, ReporterFactory reporterFac * * @param executorService * @param dataSource - * @param returnCode */ - public void startReporterGatherers(ExecutorService executorService, final DataSource dataSource, final int[] returnCode) + void startReporterGatherers(ExecutorService executorService, final DataSource dataSource) { + if ( this.executorService != null && !this.executorService.isShutdown()) + throw new IllegalStateException("There is already a running executor service!"); + + this.executorService = executorService; + // TODO: Implement Init-check // Gather each reporter results on a separate thread. for (ReporterOptions ro : reporterOptionsList) { @@ -103,9 +128,7 @@ public void startReporterGatherers(ExecutorService executorService, final DataSo ro.getReporterObj().getOutputBuffer().printAvailable(conn, printStreams); } catch (SQLException | FileNotFoundException e) { - System.out.println(e.getMessage()); - returnCode[0] = Cli.DEFAULT_ERROR_CODE; - executorService.shutdownNow(); + abortGathering(e); } finally { if (fileOutStream != null) fileOutStream.close(); @@ -114,9 +137,9 @@ public void startReporterGatherers(ExecutorService executorService, final DataSo } } - public List getReporterOptionsList() { + List getReporterOptionsList() { return reporterOptionsList; } - public int getNumberOfReporters() { return reporterOptionsList.size(); } + int getNumberOfReporters() { return reporterOptionsList.size(); } } diff --git a/src/main/java/org/utplsql/cli/RunCommand.java b/src/main/java/org/utplsql/cli/RunCommand.java index 13d461a..cbfca36 100644 --- a/src/main/java/org/utplsql/cli/RunCommand.java +++ b/src/main/java/org/utplsql/cli/RunCommand.java @@ -2,17 +2,20 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.zaxxer.hikari.HikariDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.utplsql.api.*; import org.utplsql.api.compatibility.CompatibilityProxy; import org.utplsql.api.db.DefaultDatabaseInformation; import org.utplsql.api.exception.DatabaseNotCompatibleException; +import org.utplsql.api.exception.OracleCreateStatmenetStuckException; import org.utplsql.api.exception.SomeTestsFailedException; import org.utplsql.api.exception.UtPLSQLNotInstalledException; import org.utplsql.api.reporter.Reporter; import org.utplsql.api.reporter.ReporterFactory; import org.utplsql.cli.exception.DatabaseConnectionFailed; +import org.utplsql.cli.exception.ReporterTimeoutException; import org.utplsql.cli.log.StringBlockFormatter; import javax.sql.DataSource; @@ -22,12 +25,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; /** - * Created by vinicius.moreira on 19/04/2017. + * Issues a Run-Command with all the options + * + * Uses an executor to start a RunTestRunnerTask and one ReporterGatheringTask per Reporter requested. * * @author vinicious moreira * @author pesse @@ -138,27 +141,17 @@ else if ( logDebug ) { LoggerConfiguration.configure(level); } - public int run() { + public int doRun() throws OracleCreateStatmenetStuckException { init(); outputMainInformation(); + HikariDataSource dataSource = null; + int returnCode = 0; try { final List reporterList; - final File baseDir = new File("").getAbsoluteFile(); - final FileMapperOptions[] sourceMappingOptions = {null}; - final FileMapperOptions[] testMappingOptions = {null}; - - final int[] returnCode = {0}; - - sourceMappingOptions[0] = getFileMapperOptionsByParamListItem(this.sourcePathParams, baseDir); - testMappingOptions[0] = getFileMapperOptionsByParamListItem(this.testPathParams, baseDir); - - final List finalIncludeObjectsList = getObjectList(includeObjects); - final List finalExcludeObjectsList = getObjectList(excludeObjects); - - final DataSource dataSource = DataSourceProvider.getDataSource(getConnectionInfo(), getReporterManager().getNumberOfReporters() + 1); + dataSource = (HikariDataSource) DataSourceProvider.getDataSource(getConnectionInfo(), getReporterManager().getNumberOfReporters() + 2); initDatabase(dataSource); reporterList = initReporters(dataSource); @@ -172,48 +165,77 @@ public int run() { ExecutorService executorService = Executors.newFixedThreadPool(1 + reporterList.size()); // Run tests. - executorService.submit(() -> { - try (Connection conn = dataSource.getConnection()) { - TestRunner testRunner = new TestRunner() - .addPathList(testPaths) - .addReporterList(reporterList) - .sourceMappingOptions(sourceMappingOptions[0]) - .testMappingOptions(testMappingOptions[0]) - .colorConsole(this.colorConsole) - .failOnErrors(true) - .skipCompatibilityCheck(skipCompatibilityCheck) - .includeObjects(finalIncludeObjectsList) - .excludeObjects(finalExcludeObjectsList); - - logger.info("Running tests now."); - logger.info("--------------------------------------"); - testRunner.run(conn); - } catch (SomeTestsFailedException e) { - returnCode[0] = this.failureExitCode; - } catch (SQLException e) { - System.out.println(e.getMessage()); - returnCode[0] = Cli.DEFAULT_ERROR_CODE; - executorService.shutdownNow(); - } - }); + Future future = executorService.submit(new RunTestRunnerTask(dataSource, newTestRunner(reporterList))); // Gather each reporter results on a separate thread. - getReporterManager().startReporterGatherers(executorService, dataSource, returnCode); - - executorService.shutdown(); - executorService.awaitTermination(timeoutInMinutes, TimeUnit.MINUTES); + getReporterManager().startReporterGatherers(executorService, dataSource); + + try { + future.get(timeoutInMinutes, TimeUnit.MINUTES); + } catch (TimeoutException e) { + executorService.shutdownNow(); + throw new ReporterTimeoutException(timeoutInMinutes); + } catch (ExecutionException e) { + if (e.getCause() instanceof SomeTestsFailedException) { + returnCode = failureExitCode; + } else { + executorService.shutdownNow(); + throw e.getCause(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + throw e; + } + finally { + executorService.shutdown(); + if (!executorService.awaitTermination(timeoutInMinutes, TimeUnit.MINUTES)) { + throw new ReporterTimeoutException(timeoutInMinutes); + } + } logger.info("--------------------------------------"); logger.info("All tests done."); - - return returnCode[0]; - } - catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed e ) { + } catch ( OracleCreateStatmenetStuckException e ) { + throw e; + } catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed | ReporterTimeoutException e ) { System.out.println(e.getMessage()); - } catch (Exception e) { + returnCode = Cli.DEFAULT_ERROR_CODE; + } catch (Throwable e) { e.printStackTrace(); + returnCode = Cli.DEFAULT_ERROR_CODE; + } finally { + if ( dataSource != null ) + dataSource.close(); } - return 1; + return returnCode; + } + + public int run() { + for ( int i = 1; i<5; i++ ) { + try { + return doRun(); + } catch (OracleCreateStatmenetStuckException e) { + logger.warn("WARNING: Caught Oracle stuck during creation of Runner-Statement. Retrying ({})", i); + } + } + + return Cli.DEFAULT_ERROR_CODE; + } + + private TestRunner newTestRunner( List reporterList) { + + final File baseDir = new File("").getAbsoluteFile(); + + return new TestRunner() + .addPathList(testPaths) + .addReporterList(reporterList) + .sourceMappingOptions(getFileMapperOptionsByParamListItem(this.sourcePathParams, baseDir)) + .testMappingOptions(getFileMapperOptionsByParamListItem(this.testPathParams, baseDir)) + .colorConsole(this.colorConsole) + .failOnErrors(true) + .skipCompatibilityCheck(skipCompatibilityCheck) + .includeObjects(getObjectList(includeObjects)) + .excludeObjects(getObjectList(excludeObjects)); } private ArrayList getObjectList(String includeObjects) { diff --git a/src/main/java/org/utplsql/cli/RunTestRunnerTask.java b/src/main/java/org/utplsql/cli/RunTestRunnerTask.java new file mode 100644 index 0000000..99c388f --- /dev/null +++ b/src/main/java/org/utplsql/cli/RunTestRunnerTask.java @@ -0,0 +1,65 @@ +package org.utplsql.cli; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.TestRunner; +import org.utplsql.api.exception.OracleCreateStatmenetStuckException; +import org.utplsql.api.exception.SomeTestsFailedException; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; + +/** Runs the utPLSQL Test-Runner + * + * Takes care of its connection. + * In case of an OracleCreateStatementStuckException it will abort the connection, otherwise close it. + * + * @author pesse + */ +public class RunTestRunnerTask implements Callable { + + private static final Logger logger = LoggerFactory.getLogger(RunTestRunnerTask.class); + private DataSource dataSource; + private TestRunner testRunner; + + RunTestRunnerTask(DataSource dataSource, TestRunner testRunner) { + this.dataSource = dataSource; + this.testRunner = testRunner; + } + + @Override + public Boolean call() throws Exception { + Connection conn = null; + try { + conn = dataSource.getConnection(); + logger.info("Running tests now."); + logger.info("--------------------------------------"); + testRunner.run(conn); + } catch (SomeTestsFailedException e) { + throw e; + } catch (OracleCreateStatmenetStuckException e ) { + try { + conn.abort(Executors.newSingleThreadExecutor()); + conn = null; + } catch (SQLException e1) { + logger.error(e1.getMessage(), e1); + } + throw e; + } catch (SQLException e) { + System.out.println(e.getMessage()); + throw e; + } finally { + if ( conn != null ) { + try { + conn.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + } + return true; + } +} diff --git a/src/main/java/org/utplsql/cli/TestRunnerTask.java b/src/main/java/org/utplsql/cli/TestRunnerTask.java new file mode 100644 index 0000000..4b18f1f --- /dev/null +++ b/src/main/java/org/utplsql/cli/TestRunnerTask.java @@ -0,0 +1,24 @@ +package org.utplsql.cli; + +import org.utplsql.api.TestRunner; + +import java.sql.Connection; +import java.util.concurrent.ExecutorService; + +class TestRunnerTask implements Runnable { + + private Connection con; + private TestRunner testRunner; + private ExecutorService executorService; + + TestRunnerTask(Connection con, TestRunner testRunner, ExecutorService executorService ) { + this.con = con; + this.testRunner = testRunner; + this.executorService = executorService; + } + + @Override + public void run() { + + } +} diff --git a/src/main/java/org/utplsql/cli/exception/ReporterTimeoutException.java b/src/main/java/org/utplsql/cli/exception/ReporterTimeoutException.java new file mode 100644 index 0000000..39ce2b1 --- /dev/null +++ b/src/main/java/org/utplsql/cli/exception/ReporterTimeoutException.java @@ -0,0 +1,15 @@ +package org.utplsql.cli.exception; + +public class ReporterTimeoutException extends Exception { + + private final int timeOutInMinutes; + + public ReporterTimeoutException( int timeoutInMinutes ) { + super("Timeout while waiting for reporters to finish for " + timeoutInMinutes + " minutes"); + this.timeOutInMinutes = timeoutInMinutes; + } + + public int getTimeOutInMinutes() { + return timeOutInMinutes; + } +} diff --git a/src/test/java/org/utplsql/cli/RunCommandIssue20Test.java b/src/test/java/org/utplsql/cli/RunCommandIssue20Test.java new file mode 100644 index 0000000..6b847f8 --- /dev/null +++ b/src/test/java/org/utplsql/cli/RunCommandIssue20Test.java @@ -0,0 +1,48 @@ +package org.utplsql.cli; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.reporter.CoreReporters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit test for run command. + * + * @author philipp salivsberg + */ +class RunCommandIssue20Test { + + private static final Logger logger = LoggerFactory.getLogger(RunCommandIssue20Test.class); + + @Test + void runLoop() { + RunCommand runCmd = TestHelper.createRunCommand( + TestHelper.getConnectionString(), + "-p=TEST_BETWNSTR.normal_case", + "-f=ut_documentation_reporter"); + List reporterOptionsList = runCmd.getReporterOptionsList(); + ReporterOptions reporterOptions1 = reporterOptionsList.get(0); + assertEquals(CoreReporters.UT_DOCUMENTATION_REPORTER.name(), reporterOptions1.getReporterName()); + assertTrue(reporterOptions1.outputToScreen()); + // Loop in same JVM, uses a lot of new connections without closing existing ones, this might lead to + // "Could not establish connection to database. Reason: IO Error: Got minus one from a read call" + // before hitting the hanger at oracle.jdbc.driver.OracleStruct.getOracleAttributes(OracleStruct.java:347) + // You may increase processes and implicitly sessions by "alter system set processes=1024 scope=spfile;" + for (int i=0; i <= 120; i++) { + logger.info("======================="); + logger.info("Loop number " + i); + logger.info("======================="); + int result = runCmd.run(); + if (result != 0) { + logger.error("Got an error during run. Return Code was " + result + "." ); + break; + } + } + } + +} \ No newline at end of file