From 8be0c92a4f76cec8b6b96d3c4d60561fb2ab48c7 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 10:43:49 +0200 Subject: [PATCH 01/19] add methods produceReportWithCoverage and getHtmlCoverage --- .../sqldev/dal/RealtimeReporterDao.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java index 87be783c..deef2ddd 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java @@ -93,6 +93,47 @@ public void produceReport(final String reporterId, final List pathList) jdbcTemplate.update(plsql, binds); } + public void produceReportWithCoverage(final String realtimeReporterId, final String coverageReporterId, + final List pathList, final List schemaList, final List includeObjectList, + final List excludeObjectList) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_rt_rep ut_realtime_reporter := ut_realtime_reporter();\n"); + sb.append(" l_cov_rep ut_coverage_html_reporter := ut_coverage_html_reporter();\n"); + sb.append("BEGIN\n"); + sb.append(" l_rt_rep.set_reporter_id(?);\n"); + sb.append(" l_rt_rep.output_buffer.init();\n"); + sb.append(" l_cov_rep.set_reporter_id(?);\n"); + sb.append(" l_cov_rep.output_buffer.init();\n"); + sb.append(" sys.dbms_output.enable(NULL);\n"); + sb.append(" ut_runner.run(\n"); + sb.append(" a_paths => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(pathList, 31)); + sb.append(" ),\n"); + if (!schemaList.isEmpty()) { + sb.append(" a_coverage_schemes => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(schemaList, 31)); + sb.append(" ),\n"); + } + if (!includeObjectList.isEmpty()) { + sb.append(" a_include_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(includeObjectList, 31)); + sb.append(" ),\n"); + } + if (!excludeObjectList.isEmpty()) { + sb.append(" a_exclude_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(excludeObjectList, 31)); + sb.append(" ),\n"); + } + sb.append(" a_reporters => ut_reporters(l_rt_rep, l_cov_rep)\n"); + sb.append(" );\n"); + sb.append(" sys.dbms_output.disable;\n"); + sb.append("END;"); + final String plsql = sb.toString(); + final Object[] binds = { realtimeReporterId, coverageReporterId }; + jdbcTemplate.update(plsql, binds); + } + public void consumeReport(final String reporterId, final RealtimeReporterEventConsumer consumer) { StringBuilder sb = new StringBuilder(); sb.append("DECLARE\n"); @@ -108,7 +149,7 @@ public Void doInCallableStatement(final CallableStatement cs) throws SQLExceptio cs.setString(1, reporterId); cs.registerOutParameter(2, OracleTypes.CURSOR); cs.execute(); - final ResultSet rs = ((ResultSet) cs.getObject(2)); + final ResultSet rs = (ResultSet) cs.getObject(2); while (rs.next()) { final String itemType = rs.getString("item_type"); final Clob textClob = rs.getClob("text"); @@ -124,6 +165,36 @@ public Void doInCallableStatement(final CallableStatement cs) throws SQLExceptio }); } + public String getHtmlCoverage(final String reporterId) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_reporter ut_coverage_html_reporter := ut_coverage_html_reporter();\n"); + sb.append("BEGIN\n"); + sb.append(" l_reporter.set_reporter_id(?);\n"); + sb.append(" ? := l_reporter.get_lines_cursor();\n"); + sb.append("END;"); + final String plsql = sb.toString(); + return jdbcTemplate.execute(plsql, new CallableStatementCallback() { + @Override + public String doInCallableStatement(final CallableStatement cs) throws SQLException { + cs.setString(1, reporterId); + cs.registerOutParameter(2, OracleTypes.CURSOR); + cs.execute(); + final StringBuilder sb = new StringBuilder(); + final ResultSet rs = (ResultSet) cs.getObject(2); + while (rs.next()) { + final String text = rs.getString("text"); + if (text != null) { + sb.append(text); + sb.append('\n'); + } + } + rs.close(); + return sb.toString(); + } + }); + } + private RealtimeReporterEvent convert(final String itemType, final String text) { logger.fine(() -> "\n---- " + itemType + " ----\n" + text); try { From 9605a7107e8e34b7adff5326525778c9f0e7438e Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 10:44:04 +0200 Subject: [PATCH 02/19] add test for produceReportWithCoverage --- .../sqldev/test/dal/RealtimeReporterTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java index ee975abd..151271a6 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java @@ -168,4 +168,19 @@ public void produceAndConsume() { Assert.assertEquals(7, consumer.getConsumedList().stream().filter(it -> it instanceof PostTestEvent).count()); Assert.assertEquals(28, consumer.getConsumedList().size()); } + + @Test + public void produceAndConsumeWithCoverage() { + final RealtimeReporterDao dao = new RealtimeReporterDao(DatabaseTools.getConnection(dataSource)); + final String realtimeReporterId = UUID.randomUUID().toString().replace("-", ""); + final String coverageReporterId = UUID.randomUUID().toString().replace("-", ""); + final TestRealtimerReporterEventConsumer consumer = new TestRealtimerReporterEventConsumer(); + dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, Arrays.asList(":a", ":b"), + Arrays.asList(), Arrays.asList(), Arrays.asList()); + dao.consumeReport(realtimeReporterId, consumer); + logger.fine(consumer.getConsumedList().toString()); + Assert.assertEquals(28, consumer.getConsumedList().size()); + final String html = dao.getHtmlCoverage(coverageReporterId); + Assert.assertTrue(html.trim().endsWith("")); + } } From c18c4841a17cb15fcdc514b0ec5f006edda837c6 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:21:24 +0200 Subject: [PATCH 03/19] execute setup and teardown per test --- .../test/coverage/CodeCoverageReporterTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java index 6aa90a94..0effc9dd 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java @@ -26,9 +26,9 @@ import java.util.List; import java.util.Optional; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.utplsql.sqldev.coverage.CodeCoverageReporter; @@ -40,8 +40,8 @@ public class CodeCoverageReporterTest extends AbstractJdbcTest { - @BeforeClass - public static void setup() { + @Before + public void setup() { StringBuilder sb = new StringBuilder(); sb.append("CREATE OR REPLACE FUNCTION f RETURN INTEGER IS\n"); sb.append("BEGIN\n"); @@ -118,8 +118,8 @@ public void produceReportAndCloseConnection() { content.contains("

SCOTT.F

100 % lines covered

")); } - @AfterClass - public static void teardown() { + @After + public void teardown() { executeAndIgnore(jdbcTemplate, "DROP PACKAGE test_f"); executeAndIgnore(jdbcTemplate, "DROP FUNCTION f"); } From 758872cf5045618e372b04f38aff4c6f8dc35ddc Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:22:05 +0200 Subject: [PATCH 04/19] add null checks for parameters --- .../java/org/utplsql/sqldev/dal/RealtimeReporterDao.java | 6 +++--- sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java index deef2ddd..4ade20cc 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java @@ -110,17 +110,17 @@ public void produceReportWithCoverage(final String realtimeReporterId, final Str sb.append(" a_paths => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(pathList, 31)); sb.append(" ),\n"); - if (!schemaList.isEmpty()) { + if (schemaList != null && !schemaList.isEmpty()) { sb.append(" a_coverage_schemes => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(schemaList, 31)); sb.append(" ),\n"); } - if (!includeObjectList.isEmpty()) { + if (includeObjectList != null && !includeObjectList.isEmpty()) { sb.append(" a_include_objects => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(includeObjectList, 31)); sb.append(" ),\n"); } - if (!excludeObjectList.isEmpty()) { + if (excludeObjectList != null && !excludeObjectList.isEmpty()) { sb.append(" a_exclude_objects => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(excludeObjectList, 31)); sb.append(" ),\n"); diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java index be4d6d09..31123cf2 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java @@ -918,17 +918,17 @@ public String htmlCodeCoverage(final List pathList, final List s sb.append(" a_paths => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(pathList, 16)); sb.append(" ),\n"); - if (!schemaList.isEmpty()) { + if (schemaList != null && !schemaList.isEmpty()) { sb.append(" a_coverage_schemes => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(schemaList, 16)); sb.append(" ),\n"); } - if (!includeObjectList.isEmpty()) { + if (includeObjectList != null && !includeObjectList.isEmpty()) { sb.append(" a_include_objects => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(includeObjectList, 16)); sb.append(" ),\n"); } - if (!excludeObjectList.isEmpty()) { + if (excludeObjectList != null && excludeObjectList.isEmpty()) { sb.append(" a_exclude_objects => ut_varchar2_list(\n"); sb.append(StringTools.getCSV(excludeObjectList, 16)); sb.append(" ),\n"); From 55b872743d9d60f06730cddcb600d61a7859c279 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:23:54 +0200 Subject: [PATCH 05/19] add public openInBrowser method --- .../sqldev/coverage/CodeCoverageReporter.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java index c00a0473..5e9f36c1 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java @@ -84,11 +84,26 @@ private void run() { logger.fine(() -> "Running code coverage reporter for " + pathList + "..."); try { final UtplsqlDao dal = new UtplsqlDao(conn); - final String content = dal.htmlCodeCoverage(pathList, toStringList(schemas), + final String html = dal.htmlCodeCoverage(pathList, toStringList(schemas), toStringList(includeObjects), toStringList(excludeObjects)); + openInBrowser(html); + } finally { + try { + DatabaseTools.closeConnection(conn); + } catch (GenericDatabaseAccessException e) { + // ignore + } + if (frame != null) { + frame.exit(); + } + } + } + + public static void openInBrowser(String html) { + try { final File file = File.createTempFile("utplsql_", ".html"); logger.fine(() -> "Writing result to " + file + "..."); - FileTools.writeFile(file.toPath(), Arrays.asList(content.split(System.lineSeparator())), StandardCharsets.UTF_8); + FileTools.writeFile(file.toPath(), Arrays.asList(html.split(System.lineSeparator())), StandardCharsets.UTF_8); final URL url = file.toURI().toURL(); logger.fine(() -> "Opening " + url.toExternalForm() + " in browser..."); final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; @@ -97,21 +112,12 @@ private void run() { logger.fine(() -> url.toExternalForm() + " opened in browser."); } else { logger.severe( - () -> "Could not launch " + file + "in browser. No default browser defined on this system."); + () -> "Could not launch " + file + " in browser. No default browser defined on this system."); } } catch (Exception e) { - final String msg = "Error while running code coverage for " + pathList + "."; + final String msg = "Error while opening code coverage HTML report in browser."; logger.severe(() -> msg); throw new GenericRuntimeException(msg, e); - } finally { - try { - DatabaseTools.closeConnection(conn); - } catch (GenericDatabaseAccessException e) { - // ignore - } - if (frame != null) { - frame.exit(); - } } } From fd0d3df72df14a38068ec2abee7a5f5f70e7c7b8 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:25:39 +0200 Subject: [PATCH 06/19] reuse code coverage test setup with actual coverage result --- .../utplsql/sqldev/test/dal/RealtimeReporterTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java index 151271a6..e88b5857 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.java @@ -32,6 +32,7 @@ import org.utplsql.sqldev.model.runner.PreSuiteEvent; import org.utplsql.sqldev.model.runner.PreTestEvent; import org.utplsql.sqldev.test.AbstractJdbcTest; +import org.utplsql.sqldev.test.coverage.CodeCoverageReporterTest; public class RealtimeReporterTest extends AbstractJdbcTest { private static final Logger logger = Logger.getLogger(RealtimeReporterTest.class.getName()); @@ -142,13 +143,16 @@ public void setup() { sb.append(" END;\n"); sb.append("END;"); jdbcTemplate.execute(sb.toString()); - } + new CodeCoverageReporterTest().setup(); + } + @After public void teardown() { executeAndIgnore(jdbcTemplate, "DROP PACKAGE junit_utplsql_test1_pkg"); executeAndIgnore(jdbcTemplate, "DROP PACKAGE junit_utplsql_test2_pkg"); executeAndIgnore(jdbcTemplate, "DROP PACKAGE junit_utplsql_test3_pkg"); + new CodeCoverageReporterTest().teardown(); } @Test @@ -175,11 +179,10 @@ public void produceAndConsumeWithCoverage() { final String realtimeReporterId = UUID.randomUUID().toString().replace("-", ""); final String coverageReporterId = UUID.randomUUID().toString().replace("-", ""); final TestRealtimerReporterEventConsumer consumer = new TestRealtimerReporterEventConsumer(); - dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, Arrays.asList(":a", ":b"), - Arrays.asList(), Arrays.asList(), Arrays.asList()); + dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, Arrays.asList(":test_f"), null, null, null); dao.consumeReport(realtimeReporterId, consumer); logger.fine(consumer.getConsumedList().toString()); - Assert.assertEquals(28, consumer.getConsumedList().size()); + Assert.assertEquals(6, consumer.getConsumedList().size()); final String html = dao.getHtmlCoverage(coverageReporterId); Assert.assertTrue(html.trim().endsWith("")); } From 52847964f7c5edda4ff7f48d0ccc905d4b88e879 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:26:38 +0200 Subject: [PATCH 07/19] add support to run tests with code coverage in one run --- .../utplsql/sqldev/runner/UtplsqlRunner.java | 89 +++++++++++++++---- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java index bf0628af..dbc62cb4 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java @@ -27,6 +27,7 @@ import javax.swing.JFrame; +import org.utplsql.sqldev.coverage.CodeCoverageReporter; import org.utplsql.sqldev.dal.RealtimeReporterDao; import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer; import org.utplsql.sqldev.model.DatabaseTools; @@ -48,26 +49,65 @@ public class UtplsqlRunner implements RealtimeReporterEventConsumer { private static final Logger logger = Logger.getLogger(UtplsqlRunner.class.getName()); - private List pathList; + private final boolean withCodeCoverage; + private final List pathList; + private final List schemaList; + private final List includeObjectList; + private final List excludeObjectList; private String connectionName; private Connection producerConn; private Connection consumerConn; - private final String reporterId = UUID.randomUUID().toString().replace("-", ""); + private final String realtimeReporterId = UUID.randomUUID().toString().replace("-", ""); + private final String coverageReporterId = UUID.randomUUID().toString().replace("-", ""); private Run run; private RunnerPanel panel; + private JFrame frame; // for testing purposes only (outside of SQL Developer) private Thread producerThread; private Thread consumerThread; public UtplsqlRunner(final List pathList, final String connectionName) { + this.withCodeCoverage = false; this.pathList = pathList; + this.schemaList = null; + this.includeObjectList = null; + this.excludeObjectList = null; + setConnection(connectionName); + } + + public UtplsqlRunner(final List pathList, final List schemaList, + final List includeObjectList, final List excludeObjectList, final String connectionName) { + this.withCodeCoverage = true; + this.pathList = pathList; + this.schemaList = schemaList; + this.includeObjectList = includeObjectList; + this.excludeObjectList = excludeObjectList; setConnection(connectionName); } /** - * this constructor is intended for tests only + * this constructor is intended for tests only (without code coverage) */ public UtplsqlRunner(final List pathList, final Connection producerConn, final Connection consumerConn) { + this.withCodeCoverage = false; this.pathList = pathList; + this.schemaList = null; + this.includeObjectList = null; + this.excludeObjectList = null; + this.producerConn = producerConn; + this.consumerConn = consumerConn; + } + + /** + * this constructor is intended for tests only (with code coverage) + */ + public UtplsqlRunner(final List pathList, final List schemaList, + final List includeObjectList, final List excludeObjectList, final Connection producerConn, + final Connection consumerConn) { + this.withCodeCoverage = true; + this.pathList = pathList; + this.schemaList = schemaList; + this.includeObjectList = includeObjectList; + this.excludeObjectList = excludeObjectList; this.producerConn = producerConn; this.consumerConn = consumerConn; } @@ -86,6 +126,9 @@ public void dispose() { // running in SQL Developer DatabaseTools.closeConnection(producerConn); DatabaseTools.closeConnection(consumerConn); + if (frame != null) { + frame.setVisible(false); + } } @Override @@ -116,7 +159,7 @@ private String getSysdate() { } private void initRun() { - run = new Run(reporterId, connectionName, pathList); + run = new Run(realtimeReporterId, connectionName, pathList); run.setStartTime(getSysdate()); run.getCounter().setDisabled(0); run.getCounter().setSuccess(0); @@ -128,14 +171,14 @@ private void initRun() { run.setCurrentTestNumber(0); run.setStatus(UtplsqlResources.getString("RUNNER_INITIALIZING_TEXT")); panel.setModel(run); - panel.update(reporterId); + panel.update(realtimeReporterId); } private void doProcess(final PreRunEvent event) { run.setTotalNumberOfTests(event.getTotalNumberOfTests()); run.put(event.getItems()); run.setStatus(UtplsqlResources.getString("RUNNER_RUNNING_TEXT")); - panel.update(reporterId); + panel.update(realtimeReporterId); } private void doProcess(final PostRunEvent event) { @@ -145,7 +188,7 @@ private void doProcess(final PostRunEvent event) { run.setErrorStack(event.getErrorStack()); run.setServerOutput(event.getServerOutput()); run.setStatus(UtplsqlResources.getString("RUNNER_FINNISHED_TEXT")); - panel.update(reporterId); + panel.update(realtimeReporterId); } private void doProcess(final PreSuiteEvent event) { @@ -189,7 +232,7 @@ private void doProcess(final PostSuiteEvent event) { sb.append(event.getServerOutput()); test.setServerOutput(sb.toString()); } - panel.update(reporterId); + panel.update(realtimeReporterId); } private void doProcess(final PreTestEvent event) { @@ -203,7 +246,7 @@ private void doProcess(final PreTestEvent event) { run.setStatus(event.getId() + "..."); run.setCurrentTestNumber(event.getTestNumber()); run.setCurrentTest(test); - panel.update(reporterId); + panel.update(realtimeReporterId); } private void doProcess(final PostTestEvent event) { @@ -234,29 +277,37 @@ private void doProcess(final PostTestEvent event) { run.getCounter().setSuccess(run.getCounter().getSuccess() + event.getCounter().getSuccess()); run.getCounter().setFailure(run.getCounter().getFailure() + event.getCounter().getFailure()); run.getCounter().setError(run.getCounter().getError() + event.getCounter().getError()); - panel.update(reporterId); + panel.update(realtimeReporterId); } private void produce() { try { - logger.fine(() -> "Running utPLSQL tests and producing events via reporter id " + reporterId + "..."); + logger.fine(() -> "Running utPLSQL tests and producing events via reporter id " + realtimeReporterId + "..."); final RealtimeReporterDao dao = new RealtimeReporterDao(producerConn); - dao.produceReport(reporterId, pathList); - logger.fine(() -> "All events produced for reporter id " + reporterId + "."); + if (withCodeCoverage) { + dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList); + } else { + dao.produceReport(realtimeReporterId, pathList); + } + logger.fine(() -> "All events produced for reporter id " + realtimeReporterId + "."); } catch (Exception e) { - logger.severe(() -> "Error while producing events for reporter id " + reporterId + ": " + logger.severe(() -> "Error while producing events for reporter id " + realtimeReporterId + ": " + (e != null ? e.getMessage() : "???")); } } private void consume() { try { - logger.fine(() -> "Consuming events from reporter id " + reporterId + " in realtime..."); + logger.fine(() -> "Consuming events from reporter id " + realtimeReporterId + " in realtime..."); final RealtimeReporterDao dao = new RealtimeReporterDao(consumerConn); - dao.consumeReport(reporterId, this); + dao.consumeReport(realtimeReporterId, this); logger.fine(() -> "All events consumed."); + if (withCodeCoverage) { + String html = dao.getHtmlCoverage(coverageReporterId); + CodeCoverageReporter.openInBrowser(html); + } } catch (Exception e) { - logger.severe(() -> "Error while consuming events for reporter id " + reporterId + ": " + logger.severe(() -> "Error while consuming events for reporter id " + realtimeReporterId + ": " + (e != null ? e.getMessage() : "???")); } if (run.getTotalNumberOfTests() < 0) { @@ -264,7 +315,7 @@ private void consume() { run.setExecutionTime(Double.valueOf(System.currentTimeMillis() - Double.valueOf(run.getStart())) / 1000); run.setEndTime(getSysdate()); run.setTotalNumberOfTests(0); - panel.update(reporterId); + panel.update(realtimeReporterId); } if (isRunningInSqlDeveloper()) { dispose(); @@ -285,7 +336,7 @@ private boolean initGUI() { RunnerFactory.showDockable(); panel = dockable.getRunnerPanel(); } else { - final JFrame frame = new JFrame("utPLSQL Runner Panel"); + frame = new JFrame("utPLSQL Runner Panel"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel = new RunnerPanel(); frame.add(panel.getGUI()); From b1985e61c268484de1f8ace2a0ad7b3fdd9c4404 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 17:27:20 +0200 Subject: [PATCH 08/19] add test with code coverage --- .../sqldev/test/runner/UtplsqlRunnerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.java index c3531381..eba5aec7 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.java @@ -27,6 +27,7 @@ import org.utplsql.sqldev.model.SystemTools; import org.utplsql.sqldev.runner.UtplsqlRunner; import org.utplsql.sqldev.test.AbstractJdbcTest; +import org.utplsql.sqldev.test.coverage.CodeCoverageReporterTest; public class UtplsqlRunnerTest extends AbstractJdbcTest { @@ -108,11 +109,13 @@ public void setup() { sb.append(" END;\n"); sb.append("END;"); jdbcTemplate.execute(sb.toString()); + new CodeCoverageReporterTest().setup(); } @After public void teardown() { executeAndIgnore(jdbcTemplate, "DROP PACKAGE junit_utplsql_test1_pkg"); + new CodeCoverageReporterTest().teardown(); } @Test @@ -140,4 +143,30 @@ public void runTestsWithMaxTime() { Assert.assertNotNull(runner); runner.dispose(); } + + @Test + public void runTestsWithCodeCoverage() { + final SingleConnectionDataSource ds1 = new SingleConnectionDataSource(); + ds1.setDriverClassName("oracle.jdbc.OracleDriver"); + ds1.setUrl(dataSource.getUrl()); + ds1.setUsername(dataSource.getUsername()); + ds1.setPassword(dataSource.getPassword()); + final Connection producerConn = DatabaseTools.getConnection(ds1); + + final SingleConnectionDataSource ds2 = new SingleConnectionDataSource(); + ds2.setDriverClassName("oracle.jdbc.OracleDriver"); + ds2.setUrl(dataSource.getUrl()); + ds2.setUsername(dataSource.getUsername()); + ds2.setPassword(dataSource.getPassword()); + final Connection consumerConn = DatabaseTools.getConnection(ds2); + + UtplsqlRunner runner = new UtplsqlRunner(Arrays.asList(":test_f"), null, null, null, producerConn, consumerConn); + runner.runTestAsync(); + + SystemTools.waitForThread(runner.getProducerThread(), 200000); + SystemTools.waitForThread(runner.getConsumerThread(), 200000); + SystemTools.sleep(4 * 1000); + Assert.assertNotNull(runner); + runner.dispose(); + } } From e2804830ca71981d583ef2d79fb96b0bdaffb731 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 18:44:46 +0200 Subject: [PATCH 09/19] run code coverage via realtime reporter if possible --- .../sqldev/coverage/CodeCoverageReporter.java | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java index 5e9f36c1..147d544e 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java @@ -25,16 +25,22 @@ import java.util.List; import java.util.logging.Logger; +import org.utplsql.sqldev.dal.RealtimeReporterDao; import org.utplsql.sqldev.dal.UtplsqlDao; import org.utplsql.sqldev.exception.GenericDatabaseAccessException; import org.utplsql.sqldev.exception.GenericRuntimeException; import org.utplsql.sqldev.model.DatabaseTools; import org.utplsql.sqldev.model.FileTools; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.runner.UtplsqlRunner; import org.utplsql.sqldev.ui.coverage.CodeCoverageReporterDialog; +import oracle.ide.config.Preferences; + public class CodeCoverageReporter { private static final Logger logger = Logger.getLogger(CodeCoverageReporter.class.getName()); + private String connectionName; private Connection conn; private List pathList; private List includeObjectList; @@ -50,6 +56,7 @@ public CodeCoverageReporter(final List pathList, final List incl setConnection(connectionName); } + // constructor for testing purposes only public CodeCoverageReporter(final List pathList, final List includeObjectList, final Connection conn) { this.pathList = pathList; @@ -64,7 +71,8 @@ private void setConnection(final String connectionName) { throw new NullPointerException(); } else { // must be closed manually - conn = DatabaseTools.cloneConnection(connectionName); + this.connectionName = connectionName; + this.conn = DatabaseTools.getConnection(connectionName); } } @@ -83,19 +91,43 @@ private ArrayList toStringList(final String s) { private void run() { logger.fine(() -> "Running code coverage reporter for " + pathList + "..."); try { - final UtplsqlDao dal = new UtplsqlDao(conn); - final String html = dal.htmlCodeCoverage(pathList, toStringList(schemas), + final RealtimeReporterDao dao = new RealtimeReporterDao(conn); + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + if (preferences.isUseRealtimeReporter() && dao.isSupported()) { + runCodeCoverageWithRealtimeReporter(); + } else { + runCodeCoverageStandalone(); + } + } finally { + if (frame != null) { + frame.exit(); + } + } + } + + private void runCodeCoverageWithRealtimeReporter() { + final UtplsqlRunner runner = new UtplsqlRunner(pathList, toStringList(schemas), toStringList(includeObjects), + toStringList(excludeObjects), connectionName); + runner.runTestAsync(); + } + + private void runCodeCoverageStandalone() { + Connection coverageConn = null; + try { + coverageConn = conn != null ? conn : DatabaseTools.cloneConnection(connectionName); + final UtplsqlDao dao = new UtplsqlDao(coverageConn); + final String html = dao.htmlCodeCoverage(pathList, toStringList(schemas), toStringList(includeObjects), toStringList(excludeObjects)); openInBrowser(html); } finally { try { - DatabaseTools.closeConnection(conn); + if (coverageConn != null && conn == null) { + // close only if connection has been cloned + DatabaseTools.closeConnection(coverageConn); + } } catch (GenericDatabaseAccessException e) { // ignore } - if (frame != null) { - frame.exit(); - } } } From 22610ee3ddcee87be47b914aec1d784deb7b6fd6 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 18:45:10 +0200 Subject: [PATCH 10/19] no dedicated connection required anymore --- .../test/coverage/CodeCoverageReporterTest.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java index 0effc9dd..789a4dd3 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java @@ -20,7 +20,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.sql.Connection; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -30,7 +29,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.utplsql.sqldev.coverage.CodeCoverageReporter; import org.utplsql.sqldev.exception.GenericRuntimeException; import org.utplsql.sqldev.model.DatabaseTools; @@ -98,19 +96,11 @@ private Path getNewestOutputFile() { @Test public void produceReportAndCloseConnection() { - // create temporary dataSource, closed by reporter - SingleConnectionDataSource ds = new SingleConnectionDataSource(); - ds.setDriverClassName("oracle.jdbc.OracleDriver"); - ds.setUrl(dataSource.getUrl()); - ds.setUsername(dataSource.getUsername()); - ds.setPassword(dataSource.getPassword()); - final Connection conn = DatabaseTools.getConnection(ds); final List pathList = Arrays.asList(":test_f"); final List includeObjectList = Arrays.asList("f"); - final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, includeObjectList, conn); + final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, includeObjectList, DatabaseTools.getConnection(dataSource)); final Thread run = reporter.runAsync(); SystemTools.waitForThread(run, 20000); - Assert.assertTrue(DatabaseTools.isConnectionClosed(conn)); final Path outputFile = this.getNewestOutputFile(); Assert.assertNotNull(outputFile); final String content = new String(FileTools.readFile(outputFile), StandardCharsets.UTF_8); From e677de759f455895f6752e9beed582b6d466d0b2 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 19:13:41 +0200 Subject: [PATCH 11/19] configure default fetch size of 100 (except for consumeReport) --- .../sqldev/dal/RealtimeReporterDao.java | 43 +++++++++++-------- .../org/utplsql/sqldev/dal/UtplsqlDao.java | 2 + 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java index 4ade20cc..042d6810 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java @@ -64,7 +64,7 @@ public class RealtimeReporterDao { public RealtimeReporterDao(final Connection conn) { this.conn = conn; jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)); - jdbcTemplate.setFetchSize(1); + jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS); } public boolean isSupported() { @@ -143,26 +143,31 @@ public void consumeReport(final String reporterId, final RealtimeReporterEventCo sb.append(" ? := l_reporter.get_lines_cursor();\n"); sb.append("END;"); final String plsql = sb.toString(); - jdbcTemplate.execute(plsql, new CallableStatementCallback() { - @Override - public Void doInCallableStatement(final CallableStatement cs) throws SQLException { - cs.setString(1, reporterId); - cs.registerOutParameter(2, OracleTypes.CURSOR); - cs.execute(); - final ResultSet rs = (ResultSet) cs.getObject(2); - while (rs.next()) { - final String itemType = rs.getString("item_type"); - final Clob textClob = rs.getClob("text"); - final String textString = textClob.getSubString(1, ((int) textClob.length())); - final RealtimeReporterEvent event = convert(itemType, textString); - if (event != null) { - consumer.process(event); + jdbcTemplate.setFetchSize(1); + try { + jdbcTemplate.execute(plsql, new CallableStatementCallback() { + @Override + public Void doInCallableStatement(final CallableStatement cs) throws SQLException { + cs.setString(1, reporterId); + cs.registerOutParameter(2, OracleTypes.CURSOR); + cs.execute(); + final ResultSet rs = (ResultSet) cs.getObject(2); + while (rs.next()) { + final String itemType = rs.getString("item_type"); + final Clob textClob = rs.getClob("text"); + final String textString = textClob.getSubString(1, ((int) textClob.length())); + final RealtimeReporterEvent event = convert(itemType, textString); + if (event != null) { + consumer.process(event); + } } + rs.close(); + return null; } - rs.close(); - return null; - } - }); + }); + } finally { + jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS); + } } public String getHtmlCoverage(final String reporterId) { diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java index 31123cf2..48bfc696 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java @@ -42,6 +42,7 @@ public class UtplsqlDao { public static final int FIRST_VERSION_WITH_ANNOTATION_API = 3001003; public static final int FIRST_VERSION_WITHOUT_INTERNAL_API = 3001008; public static final int FIRST_VERSION_WITH_HAS_SUITES_API = 3001008; + public static final int FETCH_ROWS = 100; private JdbcTemplate jdbcTemplate; // cache fields private Boolean cachedDbaViewAccessible; @@ -50,6 +51,7 @@ public class UtplsqlDao { public UtplsqlDao(final Connection conn) { jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)); + jdbcTemplate.setFetchSize(FETCH_ROWS); } /** From 667b5fcdf453db9ed1181c262bb34e0b3adaf8bb Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 21:26:23 +0200 Subject: [PATCH 12/19] add resources for code coverage (icon and tooltip text) --- .../org/utplsql/sqldev/resources/UtplsqlResources.properties | 2 ++ .../org/utplsql/sqldev/resources/UtplsqlResources_de.properties | 1 + 2 files changed, 3 insertions(+) diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties b/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties index 9c0f4c08..141984bf 100644 --- a/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties +++ b/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties @@ -22,6 +22,7 @@ CHECKMARK_ICON=/org/utplsql/sqldev/resources/images/checkmark.png STATUS_ICON=/org/utplsql/sqldev/resources/images/status.png # progress.gif - the animated version - does not work PROGRESS_ICON=/org/utplsql/sqldev/resources/images/progress.png +CODE_COVERAGE_ICON=/org/utplsql/sqldev/resources/images/coverage.png # Translatable text PREF_LABEL=utPLSQL @@ -79,6 +80,7 @@ RUNNER_REFRESH_TOOLTIP=Reset ordering and refresh RUNNER_RERUN_TOOLTIP=Rerun all tests RUNNER_CLEAR_BUTTON=Clear run history RUNNER_RERUN_WORKSHEET_TOOLTIP=Rerun all tests in a new worksheet +RUNNER_CODE_COVERAGE_TOOLTIP=Rerun all tests with code coverage RUNNER_TESTS_LABEL=Tests RUNNER_FAILURES_LABEL=Failures RUNNER_ERRORS_LABEL=Errors diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources_de.properties b/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources_de.properties index a18feede..44bcbdcf 100644 --- a/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources_de.properties +++ b/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources_de.properties @@ -55,6 +55,7 @@ RUNNER_VIEW_TITLE=utPLSQL RUNNER_REFRESH_TOOLTIP=Sortierung zur�cksetzen und aktualisieren RUNNER_RERUN_TOOLTIP=Alle Tests erneut ausf�hren RUNNER_RERUN_WORKSHEET_TOOLTIP=Alle Tests in einem neuen Arbeitsblatt erneut ausf�hren +RUNNER_CODE_COVERAGE_TOOLTIP=Alle Tests mit Codeabdeckung ausf�hren RUNNER_CLEAR_BUTTON=Run History l�schen RUNNER_TESTS_LABEL=Tests RUNNER_FAILURES_LABEL=Fehlschl�ge From c2e7b12dc8041710b5b5faf817876d6beaa3fe0d Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 21:27:40 +0200 Subject: [PATCH 13/19] calculate default schemas and provide getter; handle test env. --- .../sqldev/coverage/CodeCoverageReporter.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java index 147d544e..41957a46 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java @@ -22,7 +22,11 @@ import java.sql.Connection; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.logging.Logger; import org.utplsql.sqldev.dal.RealtimeReporterDao; @@ -53,6 +57,7 @@ public CodeCoverageReporter(final List pathList, final List incl final String connectionName) { this.pathList = pathList; this.includeObjectList = includeObjectList; + setDefaultSchema(); setConnection(connectionName); } @@ -62,6 +67,7 @@ public CodeCoverageReporter(final List pathList, final List incl this.pathList = pathList; this.includeObjectList = includeObjectList; this.conn = conn; + setDefaultSchema(); } private void setConnection(final String connectionName) { @@ -75,6 +81,31 @@ private void setConnection(final String connectionName) { this.conn = DatabaseTools.getConnection(connectionName); } } + + private void setDefaultSchema() { + if (includeObjectList != null && !includeObjectList.isEmpty()) { + // use the owner with the most hits in includeObjectList + HashMap owners = new HashMap<>(); + for (String entry : includeObjectList) { + String[] obj = entry.toUpperCase().split("\\."); + if (obj.length == 2) { + // only if objectOwner and objectName are available + Integer count = owners.get(obj[0]); + if (count == null) { + count = 1; + } else { + count++; + } + owners.put(obj[0], count); + } + } + Optional> top = owners.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()).findFirst(); + if (top.isPresent()) { + schemas = top.get().getKey(); + } + } + } private ArrayList toStringList(final String s) { final ArrayList list = new ArrayList<>(); @@ -92,8 +123,14 @@ private void run() { logger.fine(() -> "Running code coverage reporter for " + pathList + "..."); try { final RealtimeReporterDao dao = new RealtimeReporterDao(conn); - final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); - if (preferences.isUseRealtimeReporter() && dao.isSupported()) { + PreferenceModel preferences; + try { + preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + } catch (NoClassDefFoundError error) { + // not running in SQL Developer (in tests) + preferences = PreferenceModel.getInstance(null); + } + if (preferences.isUseRealtimeReporter() && dao.isSupported() && connectionName != null) { runCodeCoverageWithRealtimeReporter(); } else { runCodeCoverageStandalone(); @@ -181,6 +218,10 @@ public void setSchemas(final String schemas) { this.schemas = schemas; } + public String getSchemas() { + return schemas; + } + public void setIncludeObjects(final String includeObjects) { this.includeObjects = includeObjects; } From acfb60e6dd1966015503f3d5d6449b492c942b69 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 21:28:11 +0200 Subject: [PATCH 14/19] populate default value for schemas under test --- .../utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java index 029c8264..525e5516 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java @@ -86,7 +86,7 @@ public CodeCoverageReporterDialog(final CodeCoverageReporter reporter) { pathsTextArea.setEditable(false); pathsTextArea.setEnabled(false); addParam(UtplsqlResources.getString("WINDOW_PATHS_LABEL"), StringTools.getSimpleCSV(reporter.getPathList()), pathsTextArea, 50, 2); - addParam(UtplsqlResources.getString("WINDOW_SCHEMAS_LABEL"), "", schemasTextField, 0, 0); + addParam(UtplsqlResources.getString("WINDOW_SCHEMAS_LABEL"), reporter.getSchemas(), schemasTextField, 0, 0); addParam(UtplsqlResources.getString("WINDOW_INCLUDE_OBJECS_LABEL"), StringTools.getSimpleCSV(reporter.getIncludeObjectList()), includeObjectsTextArea, 66, 4); addParam(UtplsqlResources.getString("WINDOW_EXCLUDE_OBJECS_LABEL"), "", excludeObjectsTextArea, 34, 1); final JScrollPane scrollPane = new JScrollPane(paneParams); From 6f5e7cb7ab15c566992cc952b065e3f598ec8bdc Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 21:28:36 +0200 Subject: [PATCH 15/19] test calcuation of default schemas under test --- .../coverage/CodeCoverageReporterTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java index 789a4dd3..0f774b0a 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java @@ -107,7 +107,35 @@ public void produceReportAndCloseConnection() { Assert.assertTrue( content.contains("

SCOTT.F

100 % lines covered

")); } + + @Test + public void defaultSchemaCalculationMixedCase() { + final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), + Arrays.asList("scott.a", "scott.b", "hR.a", "HR.B", "hr.c"), DatabaseTools.getConnection(dataSource)); + Assert.assertEquals("HR", reporter.getSchemas()); + } + + @Test + public void defaultSchemaCalculationWithoutIncludeObjects() { + final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), + Arrays.asList(), DatabaseTools.getConnection(dataSource)); + Assert.assertEquals(null, reporter.getSchemas()); + } + @Test + public void defaultSchemaCalculationWithoutOwnerInformation() { + final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), + Arrays.asList("a", "b", "c"), DatabaseTools.getConnection(dataSource)); + Assert.assertEquals(null, reporter.getSchemas()); + } + + @Test + public void defaultSchemaCalculationWithJustOneOwner() { + final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), + Arrays.asList("a", "b", "scott.c"), DatabaseTools.getConnection(dataSource)); + Assert.assertEquals("SCOTT", reporter.getSchemas()); + } + @After public void teardown() { executeAndIgnore(jdbcTemplate, "DROP PACKAGE test_f"); From 89565a4264765c116495a075b54af0f5f45b37ce Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 21:29:22 +0200 Subject: [PATCH 16/19] add code coverage to toolbar of realtime reporter (all tests) --- .../utplsql/sqldev/ui/runner/RunnerPanel.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java index 783b6415..b119917c 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java @@ -26,11 +26,14 @@ import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.sql.Connection; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.swing.BorderFactory; import javax.swing.Box; @@ -65,6 +68,7 @@ import javax.swing.table.TableRowSorter; import org.springframework.web.util.HtmlUtils; +import org.utplsql.sqldev.coverage.CodeCoverageReporter; import org.utplsql.sqldev.dal.UtplsqlDao; import org.utplsql.sqldev.model.DatabaseTools; import org.utplsql.sqldev.model.LimitedLinkedHashMap; @@ -689,6 +693,32 @@ private void initializeGUI() { worksheet.runTestAsync(); }); toolbar.add(rerunWorksheetButton); + final ToolbarButton codeCoverageButton = new ToolbarButton(UtplsqlResources.getIcon("CODE_COVERAGE_ICON")); + codeCoverageButton.setToolTipText(UtplsqlResources.getString("RUNNER_CODE_COVERAGE_TOOLTIP")); + codeCoverageButton.setBorder(buttonBorder); + codeCoverageButton.addActionListener(event -> { + final Connection conn = DatabaseTools.getConnection(currentRun.getConnectionName()); + final UtplsqlDao dao = new UtplsqlDao(conn); + final HashSet testPackages = new HashSet<>(); + // create unique list of all test packages + for (Test t : currentRun.getTests().values()) { + testPackages.add(t.getOwnerName() + "." + t.getObjectName()); + } + // add dependencies of every test package + final HashSet includeObjects = new HashSet<>(); + for (String testPackage : testPackages) { + String[] obj = testPackage.split("\\."); + includeObjects.addAll(dao.includes(obj[0], obj[1])); + } + // remove test packages + for (String testPackage : testPackages) { + includeObjects.remove(testPackage.toUpperCase()); + } + final CodeCoverageReporter reporter = new CodeCoverageReporter(currentRun.getPathList(), + includeObjects.stream().sorted().collect(Collectors.toList()), currentRun.getConnectionName()); + reporter.showParameterWindow(); + }); + toolbar.add(codeCoverageButton); toolbar.add(Box.createHorizontalGlue()); runComboBoxModel = new DefaultComboBoxModel<>(); runComboBox = new JComboBox<>(runComboBoxModel); From d58904562b01b26cb995930061519447a059d4cc Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 22:22:56 +0200 Subject: [PATCH 17/19] use list of schemas instead of top schema as default --- .../sqldev/coverage/CodeCoverageReporter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java index 41957a46..8c86ea7a 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java @@ -25,9 +25,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.utplsql.sqldev.dal.RealtimeReporterDao; import org.utplsql.sqldev.dal.UtplsqlDao; @@ -99,11 +98,10 @@ private void setDefaultSchema() { owners.put(obj[0], count); } } - Optional> top = owners.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()).findFirst(); - if (top.isPresent()) { - schemas = top.get().getKey(); - } + List sortedOwners = owners.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()).map(Map.Entry::getKey) + .collect(Collectors.toList()); + schemas = String.join(", ", sortedOwners); } } From 84ffaeb616b6f94a573414072839b51bb7ce386b Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 22:23:12 +0200 Subject: [PATCH 18/19] amend test based on new default logic --- .../sqldev/test/coverage/CodeCoverageReporterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java index 0f774b0a..72a8476f 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.java @@ -112,7 +112,7 @@ public void produceReportAndCloseConnection() { public void defaultSchemaCalculationMixedCase() { final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), Arrays.asList("scott.a", "scott.b", "hR.a", "HR.B", "hr.c"), DatabaseTools.getConnection(dataSource)); - Assert.assertEquals("HR", reporter.getSchemas()); + Assert.assertEquals("HR, SCOTT", reporter.getSchemas()); } @Test @@ -126,7 +126,7 @@ public void defaultSchemaCalculationWithoutIncludeObjects() { public void defaultSchemaCalculationWithoutOwnerInformation() { final CodeCoverageReporter reporter = new CodeCoverageReporter(Arrays.asList(":something"), Arrays.asList("a", "b", "c"), DatabaseTools.getConnection(dataSource)); - Assert.assertEquals(null, reporter.getSchemas()); + Assert.assertEquals("", reporter.getSchemas()); } @Test From 64cbad0d75ee1372560785351c9357a65fa18be7 Mon Sep 17 00:00:00 2001 From: Philipp Salvisberg Date: Sat, 30 May 2020 23:12:26 +0200 Subject: [PATCH 19/19] add code coverage context menu on overview table --- .../utplsql/sqldev/ui/runner/RunnerPanel.java | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java index b119917c..a7650ea1 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java @@ -117,6 +117,7 @@ public class RunnerPanel { private JTable testOverviewTable; private JMenuItem testOverviewRunMenuItem; private JMenuItem testOverviewRunWorksheetMenuItem; + private JMenuItem testOverviewCodeCoverageMenuItem; private JCheckBoxMenuItem showTestDescriptionCheckBoxMenuItem; private JCheckBoxMenuItem showWarningIndicatorCheckBoxMenuItem; private JCheckBoxMenuItem showInfoIndicatorCheckBoxMenuItem; @@ -200,6 +201,7 @@ private void resetDerived() { testOverviewTable.getRowSorter().setSortKeys(null); testOverviewRunMenuItem.setEnabled(false); testOverviewRunWorksheetMenuItem.setEnabled(false); + testOverviewCodeCoverageMenuItem.setEnabled(false); testIdTextArea.setText(null); testOwnerTextField.setText(null); testPackageTextField.setText(null); @@ -656,6 +658,42 @@ private JPanel makeLabelledCounterComponent(final JLabel label, final JComponent groupPanel.setPreferredSize(dim); return groupPanel; } + + private void runCodeCoverage(boolean selectedOnly) { + final Connection conn = DatabaseTools.getConnection(currentRun.getConnectionName()); + final UtplsqlDao dao = new UtplsqlDao(conn); + final List pathList = new ArrayList<>(); + final HashSet testPackages = new HashSet<>(); + if (selectedOnly) { + // pathList and unique testPackages based on selected tests + for (final int rowIndex : testOverviewTable.getSelectedRows()) { + final int row = testOverviewTable.convertRowIndexToModel(rowIndex); + final Test test = testOverviewTableModel.getTest(row); + final String path = test.getOwnerName() + "." + test.getObjectName() + "." + test.getProcedureName(); + pathList.add(path); + testPackages.add(test.getOwnerName() + "." + test.getObjectName()); + } + } else { + // pathList and unique testPackages based on currentRun + pathList.addAll(currentRun.getPathList()); + for (Test t : currentRun.getTests().values()) { + testPackages.add(t.getOwnerName() + "." + t.getObjectName()); + } + } + // add dependencies of every test package (one DB call per test package) + final HashSet includeObjects = new HashSet<>(); + for (String testPackage : testPackages) { + String[] obj = testPackage.split("\\."); + includeObjects.addAll(dao.includes(obj[0], obj[1])); + } + // remove test packages + for (String testPackage : testPackages) { + includeObjects.remove(testPackage.toUpperCase()); + } + final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, + includeObjects.stream().sorted().collect(Collectors.toList()), currentRun.getConnectionName()); + reporter.showParameterWindow(); + } private void initializeGUI() { // Base panel containing all components @@ -696,28 +734,7 @@ private void initializeGUI() { final ToolbarButton codeCoverageButton = new ToolbarButton(UtplsqlResources.getIcon("CODE_COVERAGE_ICON")); codeCoverageButton.setToolTipText(UtplsqlResources.getString("RUNNER_CODE_COVERAGE_TOOLTIP")); codeCoverageButton.setBorder(buttonBorder); - codeCoverageButton.addActionListener(event -> { - final Connection conn = DatabaseTools.getConnection(currentRun.getConnectionName()); - final UtplsqlDao dao = new UtplsqlDao(conn); - final HashSet testPackages = new HashSet<>(); - // create unique list of all test packages - for (Test t : currentRun.getTests().values()) { - testPackages.add(t.getOwnerName() + "." + t.getObjectName()); - } - // add dependencies of every test package - final HashSet includeObjects = new HashSet<>(); - for (String testPackage : testPackages) { - String[] obj = testPackage.split("\\."); - includeObjects.addAll(dao.includes(obj[0], obj[1])); - } - // remove test packages - for (String testPackage : testPackages) { - includeObjects.remove(testPackage.toUpperCase()); - } - final CodeCoverageReporter reporter = new CodeCoverageReporter(currentRun.getPathList(), - includeObjects.stream().sorted().collect(Collectors.toList()), currentRun.getConnectionName()); - reporter.showParameterWindow(); - }); + codeCoverageButton.addActionListener(event -> runCodeCoverage(false)); toolbar.add(codeCoverageButton); toolbar.add(Box.createHorizontalGlue()); runComboBoxModel = new DefaultComboBoxModel<>(); @@ -908,6 +925,7 @@ private void initializeGUI() { syncDetailTab(); testOverviewRunMenuItem.setEnabled(true); testOverviewRunWorksheetMenuItem.setEnabled(true); + testOverviewCodeCoverageMenuItem.setEnabled(true); } }); testOverviewTable.addMouseListener(new MouseAdapter() { @@ -976,6 +994,9 @@ public Component getTableCellRendererComponent(final JTable table, final Object worksheet.runTestAsync(); }); testOverviewPopupMenu.add(testOverviewRunWorksheetMenuItem); + testOverviewCodeCoverageMenuItem = new JMenuItem(UtplsqlResources.getString("MENU_CODE_COVERAGE_LABEL"), UtplsqlResources.getIcon("CODE_COVERAGE_ICON")); + testOverviewCodeCoverageMenuItem.addActionListener(event -> runCodeCoverage(true)); + testOverviewPopupMenu.add(testOverviewCodeCoverageMenuItem); testOverviewPopupMenu.add(new JSeparator()); showSuccessfulTestsCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL").replace("?", ""), true); showSuccessfulTestsCheckBoxMenuItem.addActionListener(event -> {