diff --git a/.gitignore b/.gitignore index c75ab6bc..410561c1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ **/.project **/.classpath **/.settings +**/.idea # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/README.md b/README.md index b651af8c..79565df2 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Please file your bug reports, enhancement requests, questions and other support ## How to Build -1. [Download](http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html) and install SQL Developer 18.3.0 +1. [Download](http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html) and install SQL Developer 19.1.0 2. [Download](https://maven.apache.org/download.cgi) and install Apache Maven 3.5.4 3. [Download](https://git-scm.com/downloads) and install a git command line client 4. Clone the utPLSQL-SQLDeveloper repository @@ -122,7 +122,7 @@ Please file your bug reports, enhancement requests, questions and other support 6. Run maven build by the following command - mvn -Dsqldev.basedir=/Applications/SQLDeveloper18.3.0.app/Contents/Resources/sqldeveloper -DskipTests=true clean package + mvn -Dsqldev.basedir=/Applications/SQLDeveloper19.1.0.app/Contents/Resources/sqldeveloper -DskipTests=true clean package Amend the parameter sqldev.basedir to match the path of your SQL Developer installation. This folder is used to reference Oracle jar files which are not available in public Maven repositories 7. The resulting file ```utplsql_for_SQLDev_x.x.x-SNAPSHOT.zip``` in the ```target``` directory can be installed within SQL Developer diff --git a/images/code_coverage_dialog.png b/images/code_coverage_dialog.png index 7b451cda..b514dddf 100644 Binary files a/images/code_coverage_dialog.png and b/images/code_coverage_dialog.png differ diff --git a/images/generate_utplsql_test.png b/images/generate_utplsql_test.png index 033b3f0c..2ba8f9c4 100644 Binary files a/images/generate_utplsql_test.png and b/images/generate_utplsql_test.png differ diff --git a/images/generate_utplsql_test_from_template.png b/images/generate_utplsql_test_from_template.png index 29fcf2b1..40e7fc40 100644 Binary files a/images/generate_utplsql_test_from_template.png and b/images/generate_utplsql_test_from_template.png differ diff --git a/images/preferences.png b/images/preferences.png index 81aa410f..f4f03084 100644 Binary files a/images/preferences.png and b/images/preferences.png differ diff --git a/images/run_utplsql_test.png b/images/run_utplsql_test.png index af8ba3e1..c636b565 100644 Binary files a/images/run_utplsql_test.png and b/images/run_utplsql_test.png differ diff --git a/sqldev/extension.xml b/sqldev/extension.xml index c9c27d8e..835e262c 100644 --- a/sqldev/extension.xml +++ b/sqldev/extension.xml @@ -88,6 +88,9 @@ + + + @@ -158,10 +161,17 @@ - org.utplsql.sqldev.PreferencePanel + org.utplsql.sqldev.ui.preference.PreferencePanel + + + + + + + diff --git a/sqldev/pom.xml b/sqldev/pom.xml index 47c1a48f..0ca63099 100644 --- a/sqldev/pom.xml +++ b/sqldev/pom.xml @@ -5,7 +5,7 @@ org.utplsql org.utplsql.sqldev - 0.7.2-SNAPSHOT + 1.0.0-SNAPSHOT bundle UTF-8 @@ -13,7 +13,7 @@ 1.8 2.15.0 - /Applications/SQLDeveloper18.3.0.app/Contents/Resources/sqldeveloper + /Applications/SQLDeveloper19.1.0.app/Contents/Resources/sqldeveloper utplsql_for_SQLDev_${project.version} @@ -60,13 +60,6 @@ system ${sqldev.basedir}/ide/lib/uic.jar - - oracle - oracle.sqldeveloper.utils - 12.2.0 - system - ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.utils.jar - oracle oracle.ide.navigator @@ -144,7 +137,22 @@ system ${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.java.core.jar - + + + oracle + jewt4.jar + 12.2.1 + system + ${sqldev.basedir}/modules/oracle.bali.jewt/jewt4.jar + + + oracle + share.jar + 12.2.1 + system + ${sqldev.basedir}/modules/oracle.bali.share/share.jar + + org.osgi @@ -164,6 +172,11 @@ spring-jdbc 5.1.0.RELEASE + + org.springframework + spring-web + 5.1.0.RELEASE + org.oddgen org.oddgen.sqldev diff --git a/sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporter.xtend b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend similarity index 93% rename from sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporter.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend index 55b1098a..1b4acb50 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporter.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev +package org.utplsql.sqldev.coverage import java.awt.Desktop import java.io.File @@ -27,6 +27,7 @@ import java.util.List import java.util.logging.Logger import oracle.dbtools.raptor.utils.Connections import org.utplsql.sqldev.dal.UtplsqlDao +import org.utplsql.sqldev.ui.coverage.CodeCoverageReporterDialog class CodeCoverageReporter { static val Logger logger = Logger.getLogger(CodeCoverageReporter.name); @@ -34,7 +35,7 @@ class CodeCoverageReporter { var Connection conn var List pathList var List includeObjectList - var CodeCoverageReporterWindow frame + var CodeCoverageReporterDialog frame var String schemas var String includeObjects var String excludeObjects @@ -100,7 +101,7 @@ class CodeCoverageReporter { } } - def setFrame(CodeCoverageReporterWindow frame) { + def setFrame(CodeCoverageReporterDialog frame) { this.frame = frame; } @@ -136,15 +137,16 @@ class CodeCoverageReporter { this.excludeObjects = excludeObjects } - def runAsync() { + def Thread runAsync() { val Runnable runnable = [|run] val thread = new Thread(runnable) thread.name = "code coverage reporter" thread.start + return thread } def showParameterWindow() { - CodeCoverageReporterWindow.createAndShow(this) + CodeCoverageReporterDialog.createAndShow(this) } } \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend new file mode 100644 index 00000000..442027ac --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend @@ -0,0 +1,283 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.dal + +import java.io.StringReader +import java.sql.CallableStatement +import java.sql.Connection +import java.sql.ResultSet +import java.sql.SQLException +import java.util.List +import java.util.logging.Logger +import javax.xml.parsers.DocumentBuilderFactory +import oracle.jdbc.OracleTypes +import org.springframework.dao.DataAccessException +import org.springframework.jdbc.core.CallableStatementCallback +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.jdbc.datasource.SingleConnectionDataSource +import org.utplsql.sqldev.model.XMLTools +import org.utplsql.sqldev.model.runner.Counter +import org.utplsql.sqldev.model.runner.Expectation +import org.utplsql.sqldev.model.runner.PostEvent +import org.utplsql.sqldev.model.runner.PostRunEvent +import org.utplsql.sqldev.model.runner.PostSuiteEvent +import org.utplsql.sqldev.model.runner.PostTestEvent +import org.utplsql.sqldev.model.runner.PreRunEvent +import org.utplsql.sqldev.model.runner.PreSuiteEvent +import org.utplsql.sqldev.model.runner.PreTestEvent +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent +import org.utplsql.sqldev.model.runner.Suite +import org.utplsql.sqldev.model.runner.Test +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import org.xml.sax.InputSource + +class RealtimeReporterDao { + static val Logger logger = Logger.getLogger(RealtimeReporterDao.name); + static val FIRST_VERSION_WITH_REALTIME_REPORTER = 3001004 + val extension XMLTools xmlTools = new XMLTools + var Connection conn + var JdbcTemplate jdbcTemplate + + new(Connection connection) { + conn = connection + jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) + jdbcTemplate.fetchSize = 1 + } + + def isSupported() { + return new UtplsqlDao(conn).normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_REALTIME_REPORTER + } + + def produceReport(String reporterId, List pathList) { + var plsql = ''' + DECLARE + l_reporter ut_realtime_reporter := ut_realtime_reporter(); + BEGIN + l_reporter.set_reporter_id(?); + l_reporter.output_buffer.init(); + sys.dbms_output.enable(NULL); + ut_runner.run( + a_paths => ut_varchar2_list( + «FOR path : pathList SEPARATOR ","» + '«path»' + «ENDFOR» + ), + a_reporters => ut_reporters(l_reporter) + ); + sys.dbms_output.disable; + END; + ''' + jdbcTemplate.update(plsql, #[reporterId]) + } + + def consumeReport(String reporterId, RealtimeReporterEventConsumer consumer) { + val plsql = ''' + DECLARE + l_reporter ut_realtime_reporter := ut_realtime_reporter(); + BEGIN + l_reporter.set_reporter_id(?); + ? := l_reporter.get_lines_cursor(); + END; + ''' + jdbcTemplate.execute(plsql, new CallableStatementCallback() { + override doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { + cs.setString(1, reporterId) + cs.registerOutParameter(2, OracleTypes.CURSOR) + cs.execute + val rs = cs.getObject(2) as ResultSet + while(rs.next) { + val itemType = rs.getString("item_type") + val textClob = rs.getClob("text") + val textString = textClob.getSubString(1, textClob.length as int) + val event = convert(itemType, textString) + if (event !== null) { + consumer.process(event) + } + } + rs.close + return null + } + }) + } + + private def RealtimeReporterEvent convert(String itemType, String text) { + logger.fine(''' + ---- «itemType» ---- + «text» + ''') + val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = docBuilder.parse(new InputSource(new StringReader(text))) + var RealtimeReporterEvent event + if (itemType == "pre-run") { + event = doc.convertToPreRunEvent + } else if (itemType == "post-run") { + event = doc.convertToPostRunEvent + } else if (itemType == "pre-suite") { + event = doc.convertToPreSuiteEvent + } else if (itemType == "post-suite") { + event = doc.convertToPostSuiteEvent + } else if (itemType == "pre-test") { + event = doc.convertToPreTestEvent + } else if (itemType == "post-test") { + event = doc.convertToPostTestEvent + } + return event + } + + private def RealtimeReporterEvent convertToPreRunEvent(Document doc) { + val event = new PreRunEvent + event.totalNumberOfTests = Integer.valueOf(doc.getNode("/event/totalNumberOfTests")?.textContent) + val nodeList = doc.getNodeList("/event/items/*") + for (i : 0 ..< nodeList.length) { + val node = nodeList.item(i) + if (node.nodeName == "suite") { + val suite = new Suite + event.items.add(suite) + suite.populate(node) + } else if (node.nodeName == "test") { + val test = new Test + event.items.add(test) + test.populate(node) + } + } + return event + } + + private def RealtimeReporterEvent convertToPostRunEvent(Document doc) { + val event = new PostRunEvent + event.populate(doc.getNode("/event/run")) + return event + } + + private def RealtimeReporterEvent convertToPreSuiteEvent(Document doc) { + val event = new PreSuiteEvent + val node = doc.getNode("/event/suite") + if (node instanceof Element) { + event.id = node.attributes?.getNamedItem("id")?.nodeValue + } + return event + } + + private def RealtimeReporterEvent convertToPostSuiteEvent(Document doc) { + val event = new PostSuiteEvent + val node = doc.getNode("/event/suite") + if (node instanceof Element) { + event.id = node.attributes?.getNamedItem("id")?.nodeValue + event.populate(node) + } + return event + } + + private def RealtimeReporterEvent convertToPreTestEvent(Document doc) { + val event = new PreTestEvent + val node = doc.getNode("/event/test") + if (node instanceof Element) { + event.id = node.attributes?.getNamedItem("id")?.nodeValue + event.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) + event.totalNumberOfTests = Integer.valueOf(node.getElementsByTagName("totalNumberOfTests")?.item(0)?.textContent) + } + return event + } + + private def RealtimeReporterEvent convertToPostTestEvent(Document doc) { + val event = new PostTestEvent + val node = doc.getNode("/event/test") + if (node instanceof Element) { + event.id = node.attributes?.getNamedItem("id")?.nodeValue + event.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) + event.totalNumberOfTests = Integer.valueOf(node.getElementsByTagName("totalNumberOfTests")?.item(0)?.textContent) + event.populate(node) + val failedExpectations = node.getNodeList("failedExpectations/expectation") + for (i : 0 ..< failedExpectations.length) { + val expectationNode = failedExpectations.item(i) + val expectation = new Expectation + event.failedExpectations.add(expectation) + expectation.populate(expectationNode) + } + } + return event + } + + private def void populate(Suite suite, Node node) { + if (node instanceof Element) { + suite.id = node.attributes?.getNamedItem("id")?.nodeValue + suite.name = node.getElementsByTagName("name")?.item(0)?.textContent + suite.description = node.getElementsByTagName("description")?.item(0)?.textContent + val nodeList = node.getNodeList("items/*") + for (i : 0 ..< nodeList.length) { + val childNode = nodeList.item(i) + if (childNode.nodeName == "suite") { + val childSuite = new Suite + suite.items.add(childSuite) + childSuite.populate(childNode) + } else if (childNode.nodeName == "test") { + val childTest = new Test + suite.items.add(childTest) + childTest.populate(childNode) + } + } + } + } + + private def void populate(Test test, Node node) { + if (node instanceof Element) { + test.id = node.attributes?.getNamedItem("id")?.nodeValue + test.executableType = node.getElementsByTagName("executableType")?.item(0)?.textContent + test.ownerName = node.getElementsByTagName("ownerName")?.item(0)?.textContent + test.objectName = node.getElementsByTagName("objectName")?.item(0)?.textContent + test.procedureName = node.getElementsByTagName("procedureName")?.item(0)?.textContent + test.disabled = node.getElementsByTagName("disabled")?.item(0)?.textContent == "true" + test.name = node.getElementsByTagName("name")?.item(0)?.textContent + test.description = node.getElementsByTagName("description")?.item(0)?.textContent + test.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) + } + } + + private def void populate(PostEvent event, Node node) { + if (node instanceof Element) { + event.startTime = node.getElementsByTagName("startTime")?.item(0)?.textContent + event.endTime = node.getElementsByTagName("endTime")?.item(0)?.textContent + event.executionTime = Double.valueOf(node.getElementsByTagName("executionTime")?.item(0)?.textContent) + event.counter.populate(node) + event.errorStack = node.getElementsByTagName("errorStack")?.item(0)?.textContent + event.serverOutput = node.getElementsByTagName("serverOutput")?.item(0)?.textContent + event.warnings = node.getElementsByTagName("warnings")?.item(0)?.textContent + } + } + + private def void populate(Counter counter, Node node) { + if (node instanceof Element) { + val counterNode = node.getElementsByTagName("counter")?.item(0) + if (counterNode instanceof Element) { + counter.disabled = Integer.valueOf(counterNode.getElementsByTagName("disabled")?.item(0)?.textContent) + counter.success = Integer.valueOf(counterNode.getElementsByTagName("success")?.item(0)?.textContent) + counter.failure = Integer.valueOf(counterNode.getElementsByTagName("failure")?.item(0)?.textContent) + counter.error = Integer.valueOf(counterNode.getElementsByTagName("error")?.item(0)?.textContent) + counter.warning = Integer.valueOf(counterNode.getElementsByTagName("warning")?.item(0)?.textContent) + } + } + } + + private def void populate(Expectation expectation, Node node) { + if (node instanceof Element) { + expectation.description = node.getElementsByTagName("description")?.item(0)?.textContent + expectation.message = node.getElementsByTagName("message")?.item(0)?.textContent + expectation.caller = node.getElementsByTagName("caller")?.item(0)?.textContent + } + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.xtend new file mode 100644 index 00000000..9a1d7727 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.xtend @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.dal + +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent + +interface RealtimeReporterEventConsumer { + + def void process(RealtimeReporterEvent event) + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend index 941d3f85..d0768738 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend @@ -35,6 +35,8 @@ class UtplsqlDao { public static val UTPLSQL_PACKAGE_NAME = "UT" public static val FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API = 3000004 public static val FIRST_VERSION_WITH_ANNOTATION_API = 3001003 + public static val FIRST_VERSION_WITHOUT_INTERNAL_API = 3001008 + public static val NOT_YET_AVAILABLE = 9009009 var Connection conn var JdbcTemplate jdbcTemplate // cache fields @@ -173,16 +175,41 @@ class UtplsqlDao { */ def boolean containsUtplsqlTest(String owner, String objectName, String subobjectName) { try { - var Integer found - if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { - // using API available since 3.1.3 + if (normalizedUtPlsqlVersionNumber >= NOT_YET_AVAILABLE && objectName !== null && subobjectName !== null) { + // use faster check function available since v3.1.3 (FIRST_VERSION_WITH_ANNOTATION_API) + // disabled (NOT_YET_AVAILABLE) due to wrong results in v3.1.7 + val sql = ''' + DECLARE + l_return VARCHAR2(1) := '0'; + BEGIN + IF ut_runner.is_test(?, ?, ?) THEN + l_return := '1'; + END IF; + ? := l_return; + END; + ''' + val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { + override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { + cs.setString(1, owner) + cs.setString(2, objectName) + cs.setString(3, subobjectName) + cs.registerOutParameter(4, Types.VARCHAR); + cs.execute + val ret = cs.getString(4) + return ret == "1" + } + }) + return ret + } else if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { + // using API available since 3.1.3, can handle nulls in objectName and subobjectName val sql = ''' SELECT count(*) FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?))) WHERE item_type IN ('UT_TEST', 'UT_SUITE') AND (item_name = upper(?) or ? IS NULL) ''' - found = jdbcTemplate.queryForObject(sql, Integer, #[owner, objectName, subobjectName, subobjectName]) + val found = jdbcTemplate.queryForObject(sql, Integer, #[owner, objectName, subobjectName, subobjectName]) + return found > 0 } else { // using internal API (deprecated) val sql = ''' @@ -209,20 +236,71 @@ class UtplsqlDao { END ) > 0 ''' - found = jdbcTemplate.queryForObject(sql, Integer, #[subobjectName, subobjectName, owner, objectName, objectName]) - } - return found > 0 + val found = jdbcTemplate.queryForObject(sql, Integer, #[subobjectName, subobjectName, owner, objectName, objectName]) + return found > 0 + } } catch (EmptyResultDataAccessException e) { return false } } def boolean containsUtplsqlTest(String owner) { - return containsUtplsqlTest(owner, null, null) + if (normalizedUtPlsqlVersionNumber >= NOT_YET_AVAILABLE) { + // use faster check function available since v3.1.3 (FIRST_VERSION_WITH_ANNOTATION_API) + // disabled (NOT_YET_AVAILABLE) due to wrong results in v3.1.7 + val sql = ''' + DECLARE + l_return VARCHAR2(1) := '0'; + BEGIN + IF ut_runner.has_suites(?) THEN + l_return := '1'; + END IF; + ? := l_return; + END; + ''' + val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { + override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { + cs.setString(1, owner) + cs.registerOutParameter(2, Types.VARCHAR); + cs.execute + val ret = cs.getString(2) + return ret == "1" + } + }) + return ret + } else { + return containsUtplsqlTest(owner, null, null) + } } def boolean containsUtplsqlTest(String owner, String objectName) { - return containsUtplsqlTest(owner, objectName, null) + if (normalizedUtPlsqlVersionNumber >= NOT_YET_AVAILABLE) { + // use faster check function available since v3.1.3 (FIRST_VERSION_WITH_ANNOTATION_API) + // disabled (NOT_YET_AVAILABLE) due to wrong results in v3.1.7 + val sql = ''' + DECLARE + l_return VARCHAR2(1) := '0'; + BEGIN + IF ut_runner.is_suite(?, ?) THEN + l_return := '1'; + END IF; + ? := l_return; + END; + ''' + val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { + override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { + cs.setString(1, owner) + cs.setString(2, objectName) + cs.registerOutParameter(3, Types.VARCHAR); + cs.execute + val ret = cs.getString(3) + return ret == "1" + } + }) + return ret + } else { + return containsUtplsqlTest(owner, objectName, null) + } } /** @@ -364,7 +442,6 @@ class UtplsqlDao { AND generated = 'N' ''' } - val jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) val nodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Node), #[objectType]) return nodes } @@ -687,7 +764,6 @@ class UtplsqlDao { FROM tree ''' } - val jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) val nodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Node)) return nodes } @@ -856,9 +932,73 @@ class UtplsqlDao { AND referenced_owner NOT LIKE 'APEX\_______' AND referenced_type IN ('PACKAGE', 'TYPE', 'PROCEDURE', 'FUNCTION', 'TRIGGER') ''' - val jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) val deps = jdbcTemplate.queryForList(sql, String, #[name]) return deps } + /** + * gets source of an object from the database via DBMS_METADATA + * + * @param owner owner of the object (schema) + * @param objectType expected object types are PACKAGE, PACKAGE BODY + * @param objectName name of the object + * @return the source code of the object + * @throws DataAccessException if there is a problem + */ + def getSource(String owner, String objectType, String objectName) { + // dbms_metadata uses slightly different objectTypes + val fixedObjectType = if (objectType == "PACKAGE") { + "PACKAGE_SPEC" + } else if (objectType == "PACKAGE BODY") { + "PACKAGE_BODY" + } else { + objectType + } + val sql = ''' + BEGIN + ? := sys.dbms_metadata.get_ddl( + schema => ?, + object_type => ?, + name => ? + ); + END; + ''' + val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { + override String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { + cs.registerOutParameter(1, Types.CLOB); + cs.setString(2, owner) + cs.setString(3, fixedObjectType) + cs.setString(4, objectName) + cs.execute + return cs.getString(1) + } + }) + return ret + } + + /** + * gets the object type of a database object + * + * The object types "PACKAGE BODY", "TYPE BODY" have higher priority. + * "PACKAGE" OR "TYPE" will be returned only when no body exists. + * + * @param owner owner of the object (schema) + * @param objectName name of the object + * @return the object type, e.g. PACKAGE BODY, TYPE BODY, PROCEDURE, FUNCTION + */ + def getObjectType(String owner, String objectName) { + val sql = ''' + SELECT object_type + FROM ( + SELECT object_type + FROM «IF dbaViewAccessible»dba«ELSE»all«ENDIF»_objects + WHERE owner = ? + AND object_name = ? + ORDER BY decode(object_type, 'PACKAGE', 10, 'TYPE', 10, 'SYNONYM', 20, 1) + ) + WHERE rownum = 1 + ''' + val objectType = jdbcTemplate.queryForObject(sql, #[owner, objectName], String) + return objectType + } } \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend index 2796bd15..7ae8a4f2 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend @@ -36,14 +36,16 @@ import oracle.ide.config.Preferences import oracle.ide.controller.Controller import oracle.ide.controller.IdeAction import oracle.ide.editor.Editor -import org.utplsql.sqldev.CodeCoverageReporter -import org.utplsql.sqldev.UtplsqlWorksheet +import org.utplsql.sqldev.coverage.CodeCoverageReporter +import org.utplsql.sqldev.dal.RealtimeReporterDao import org.utplsql.sqldev.dal.UtplsqlDao import org.utplsql.sqldev.model.URLTools import org.utplsql.sqldev.model.oddgen.GenContext import org.utplsql.sqldev.model.preference.PreferenceModel import org.utplsql.sqldev.oddgen.TestTemplate import org.utplsql.sqldev.parser.UtplsqlParser +import org.utplsql.sqldev.runner.UtplsqlRunner +import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner class UtplsqlController implements Controller { static final Logger logger = Logger.getLogger(UtplsqlController.name); @@ -197,7 +199,7 @@ class UtplsqlController implements Controller { set.add(path) } val ret = new ArrayList - val p = Pattern.compile("((((\\w+)\\.)?\\w+)\\.)?\\w+") + val p = Pattern.compile("(((([^\\.]+)\\.)?[^\\.]+)\\.)?[^\\.]+") for (path : set) { val m = p.matcher(path) if (m.matches()) { @@ -264,6 +266,7 @@ class UtplsqlController implements Controller { def runTest(Context context) { val view = context.view val node = context.node + val preferences = PreferenceModel.getInstance(Preferences.preferences) logger.finer('''Run utPLSQL from view «view?.class?.name» and node «node?.class?.name».''') if (view instanceof Editor) { val component = view.defaultFocusComponent @@ -278,20 +281,35 @@ class UtplsqlController implements Controller { } logger.fine('''connectionName: «connectionName»''') // issue 59 - always use a connection to ensure the utPL/SQL annotation API is used - val parser = new UtplsqlParser(component.text, Connections.instance.getConnection(connectionName), owner) + val conn = Connections.instance.getConnection(connectionName) + val parser = new UtplsqlParser(component.text, conn, owner) val position = component.caretPosition val path = parser.getPathAt(position) - val utPlsqlWorksheet = new UtplsqlWorksheet(path.pathList, connectionName) - utPlsqlWorksheet.runTestAsync + val rrDao = new RealtimeReporterDao(conn) + if (preferences.useRealtimeReporter && rrDao.supported) { + val runner = new UtplsqlRunner(path.pathList, connectionName) + runner.runTestAsync + + } else { + val worksheet = new UtplsqlWorksheetRunner(path.pathList, connectionName) + worksheet.runTestAsync + } } } else if (view instanceof DBNavigatorWindow) { val url=context.URL if (url !== null) { val connectionName = url.connectionName logger.fine('''connectionName: «connectionName»''') + val conn = Connections.instance.getConnection(connectionName) + val rrDao = new RealtimeReporterDao(conn) val pathList=context.pathList.dedupPathList - val utPlsqlWorksheet = new UtplsqlWorksheet(pathList, connectionName) - utPlsqlWorksheet.runTestAsync + if (preferences.useRealtimeReporter && rrDao.supported) { + val runner = new UtplsqlRunner(pathList, connectionName) + runner.runTestAsync + } else { + val worksheet = new UtplsqlWorksheetRunner(pathList, connectionName) + worksheet.runTestAsync + } } } } @@ -388,7 +406,7 @@ class UtplsqlController implements Controller { populateGenContext(genContext, preferences) val testTemplate = new TestTemplate(genContext) val code = testTemplate.generate.toString - UtplsqlWorksheet.openWithCode(code, connectionName) + UtplsqlWorksheetRunner.openWithCode(code, connectionName) } } } @@ -400,7 +418,7 @@ class UtplsqlController implements Controller { val connectionName = url.connectionName val testTemplate = new TestTemplate(context.genContext) val code = testTemplate.generate.toString - UtplsqlWorksheet.openWithCode(code, connectionName) + UtplsqlWorksheetRunner.openWithCode(code, connectionName) } } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend index 6a921ff1..f4356033 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend @@ -17,7 +17,7 @@ package org.utplsql.sqldev.model import org.eclipse.xtext.xbase.lib.util.ToStringBuilder -class AbstractModel { +abstract class AbstractModel { override toString() { new ToStringBuilder(this).addAllFields.toString } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend new file mode 100644 index 00000000..2d3605d9 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model + +import java.util.LinkedHashMap +import java.util.Map + +class LimitedLinkedHashMap extends LinkedHashMap { + val int maxEntries + + new (int maxEntries) { + super(maxEntries + 1, 1.0f, false) + + this.maxEntries = maxEntries; + } + + override removeEldestEntry(Map.Entry eldest) { + return size > maxEntries + } + + def getMaxEntries() { + return maxEntries + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend new file mode 100644 index 00000000..c1fc822f --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model + +import java.util.List + +// converted to Xtend based on Java code on https://www.geeksforgeeks.org/longest-common-prefix-using-binary-search/ +class PrefixTools { + def static int findMinLength(String[] arr, int n) { + var int min = Integer.MAX_VALUE + for (var int i = 0; i < n; i++) { + if ({ + val _rdIndx_arr = i + arr.get(_rdIndx_arr) + }.length() < min) { + min = { + val _rdIndx_arr = i + arr.get(_rdIndx_arr) + }.length() + } + } + return min + } + + def static boolean allContainsPrefix(String[] arr, int n, String str, int start, int end) { + for (var int i = 0; i < n; i++) { + var String arr_i = { + val _rdIndx_arr = i + arr.get(_rdIndx_arr) + } + for (var int j = start; j <= end; j++) { + if (arr_i.charAt(j) !== str.charAt(j)) { + return false + } + } + } + return true + } + + def static String commonPrefix(String[] arr, int n) { + var int index = findMinLength(arr, n) + var String prefix = "" + var int low = 0 + var int high = index + while (low <= high) { + var int mid = low + (high - low) / 2 + if (allContainsPrefix(arr, n, arr.get(0), low, mid)) { + prefix = prefix + arr.get(0).substring(low, mid + 1) + low = mid + 1 + } else { + high = mid - 1 + } + } + return prefix + } + + def static String commonPrefix(List list) { + try { + if (list.size === 0) { + return "" + } else if (list.size === 1) { + val pos = list.get(0).lastIndexOf("."); + if (pos > 0) { + return list.get(0).substring(0, pos + 1) + } else { + return "" + } + } else { + var String[] testArray = newArrayOfSize(list.size) + var prefix = commonPrefix(list.toArray(testArray), list.size) + return prefix + } + } catch (Exception e) { + return "" + } + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend new file mode 100644 index 00000000..de07c01e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend @@ -0,0 +1,37 @@ +/* Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model + +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory +import org.w3c.dom.Node +import org.w3c.dom.NodeList + +class XMLTools { + val xpathFactory = XPathFactory.newInstance() + val xpath = xpathFactory.newXPath() + + def getNodeList(Node doc, String xpathString) { + val expr = xpath.compile(xpathString); + val NodeList nodeList = expr.evaluate(doc, XPathConstants.NODESET) as NodeList + return nodeList + } + + def getNode(Node doc, String xpathString) { + val expr = xpath.compile(xpathString); + val Node node = expr.evaluate(doc, XPathConstants.NODE) as Node + return node + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend index 94768379..a913f70b 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend @@ -22,4 +22,5 @@ import org.utplsql.sqldev.model.AbstractModel class Unit extends AbstractModel { String name Integer position + Integer positionOfName } \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend index 18b0b293..524c8c4f 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend @@ -33,11 +33,20 @@ class PreferenceModel extends HashStructureAdapter { return new PreferenceModel(findOrCreate(prefs, DATA_KEY)) } + static final String KEY_USE_REALTIME_REPORTER = "useRealtimeRorter" static final String KEY_UNSHARED_WORKSHEET = "unsharedWorksheet" static final String KEY_RESET_PACKAGE = "resetPackage" static final String KEY_CLEAR_SCREEN = "clearScreen" static final String KEY_AUTO_EXECUTE = "autoExecute" static final String KEY_CHECK_RUN_UTPLSQL_TEST = "checkRunUtplsqlTest" + static final String KEY_NUMBER_OF_RUNS_IN_HISTORY = "numberOfRunsInHistory" + static final String KEY_SHOW_DISABLED_COUNTER = "showDisabledCounter" + static final String KEY_SHOW_WARNINGS_COUNTER = "showWarningsCounter" + static final String KEY_SHOW_INFO_COUNTER = "showInfoCounter" + static final String KEY_SHOW_WARNING_INDICATOR = "showWarningIndicator" + static final String KEY_SHOW_INFO_INDICATOR = "showInfoIndicator" + static final String KEY_SHOW_TEST_DESCRIPTION = "showTestDescription" + static final String KEY_SYNC_DETAIL_TAB = "syncDetailTab" static final String KEY_TEST_PACKAGE_PREFIX = "testPackagePrefix" static final String KEY_TEST_PACKAGE_SUFFIX = "testPackageSuffix" static final String KEY_TEST_UNIT_PREFIX = "testUnitPrefix" @@ -52,6 +61,14 @@ class PreferenceModel extends HashStructureAdapter { static final String KEY_OUTPUT_DIRECTORY = "outputDirectory" static final String KEY_DELETE_EXISTING_FILES="deleteExistingFiles" static final String KEY_ROOT_FOLDER_IN_ODDGEN_VIEW = "rootFolderInOddgenView" + + def isUseRealtimeReporter() { + return getHashStructure.getBoolean(PreferenceModel.KEY_USE_REALTIME_REPORTER, true) + } + + def setUseRealtimeReporter(boolean useRealtimeReporter) { + getHashStructure.putBoolean(PreferenceModel.KEY_USE_REALTIME_REPORTER, useRealtimeReporter) + } def isUnsharedWorksheet() { return getHashStructure.getBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, true) @@ -93,6 +110,70 @@ class PreferenceModel extends HashStructureAdapter { getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, checkRunUtplsqlTest) } + def getNumberOfRunsInHistory() { + return getHashStructure.getInt(PreferenceModel.KEY_NUMBER_OF_RUNS_IN_HISTORY, 10) + } + + def setNumberOfRunsInHistory(int runs) { + getHashStructure.putInt(PreferenceModel.KEY_NUMBER_OF_RUNS_IN_HISTORY, runs) + } + + def isShowDisabledCounter() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_DISABLED_COUNTER, false) + } + + def setShowDisabledCounter(boolean showDisabledCounter) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_DISABLED_COUNTER, showDisabledCounter) + } + + def isShowWarningsCounter() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_WARNINGS_COUNTER, false) + } + + def setShowWarningsCounter(boolean showWarningCounter) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_WARNINGS_COUNTER, showWarningCounter) + } + + def isShowInfoCounter() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_INFO_COUNTER, false) + } + + def setShowInfoCounter(boolean showInfoCounter) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_INFO_COUNTER, showInfoCounter) + } + + def isShowWarningIndicator() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_WARNING_INDICATOR, false) + } + + def setShowWarningIndicator(boolean showWarningIndicator) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_WARNING_INDICATOR, showWarningIndicator) + } + + def isShowInfoIndicator() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_INFO_INDICATOR, false) + } + + def setShowInfoIndicator(boolean showInfoIndicator) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_INFO_INDICATOR, showInfoIndicator) + } + + def isShowTestDescription() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_TEST_DESCRIPTION, false) + } + + def setShowTestDescription(boolean showTestDescription) { + getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_TEST_DESCRIPTION, showTestDescription) + } + + def isSyncDetailTab() { + return getHashStructure.getBoolean(PreferenceModel.KEY_SYNC_DETAIL_TAB, true) + } + + def setSyncDetailTab(boolean syncDetailTab) { + getHashStructure.putBoolean(PreferenceModel.KEY_SYNC_DETAIL_TAB, syncDetailTab) + } + def getTestPackagePrefix() { return getHashStructure.getString(PreferenceModel.KEY_TEST_PACKAGE_PREFIX, "test_") } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend new file mode 100644 index 00000000..0d496e42 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.model.AbstractModel + +@Accessors +class Counter extends AbstractModel { + Integer disabled + Integer success + Integer failure + Integer error + Integer warning +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend new file mode 100644 index 00000000..b39d5577 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.regex.Pattern +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.model.AbstractModel + +@Accessors +class Expectation extends AbstractModel { + String description + String message + String caller + + def getFailureText() { + return ''' + «message.trim» + «caller.trim» + '''.toString.trim + } + + def getShortFailureText() { + return '''«IF description !== null»«description» (line «callerLine»)«ELSE»Line «callerLine»«ENDIF»'''.toString + } + + def getCallerLine() { + var Integer line = null + val p = Pattern.compile("(?i)\"[^\\\"]+\",\\s+line\\s*([0-9]+)") + val m = p.matcher(caller) + if (m.find) { + line = Integer.valueOf(m.group(1)) + } + return line + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend new file mode 100644 index 00000000..01db70a6 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.model.AbstractModel + +@Accessors +abstract class Item extends AbstractModel { + String id + String startTime + String endTime + Double executionTime + Counter counter + String errorStack + String serverOutput + String warnings +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend new file mode 100644 index 00000000..67d9c61c --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +abstract class PostEvent extends RealtimeReporterEvent { + String startTime + String endTime + Double executionTime + Counter counter + String errorStack + String serverOutput + String warnings + + new() { + counter = new Counter + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend new file mode 100644 index 00000000..ee5edc30 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend @@ -0,0 +1,22 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PostRunEvent extends PostEvent { +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend new file mode 100644 index 00000000..10a674ed --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend @@ -0,0 +1,23 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PostSuiteEvent extends PostEvent { + String id +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend new file mode 100644 index 00000000..bd28ab6c --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.ArrayList +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PostTestEvent extends PostEvent { + String id + Integer testNumber + Integer totalNumberOfTests + List failedExpectations + + new() { + failedExpectations = new ArrayList + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend new file mode 100644 index 00000000..5750cd8e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.ArrayList +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PreRunEvent extends RealtimeReporterEvent { + List items + Integer totalNumberOfTests + + new() { + items = new ArrayList + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend new file mode 100644 index 00000000..0304f071 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend @@ -0,0 +1,23 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PreSuiteEvent extends RealtimeReporterEvent { + String id +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend new file mode 100644 index 00000000..a54e3c5e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class PreTestEvent extends RealtimeReporterEvent { + String id + Integer testNumber + Integer totalNumberOfTests +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend new file mode 100644 index 00000000..5b6ec099 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import org.utplsql.sqldev.model.AbstractModel + +abstract class RealtimeReporterEvent extends AbstractModel { +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend new file mode 100644 index 00000000..d8873eb4 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.LinkedHashMap +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.model.AbstractModel + +@Accessors +class Run extends AbstractModel { + String reporterId + String connectionName + List pathList + Integer currentTestNumber + Test currentTest + Integer totalNumberOfTests + String startTime + String endTime + Double executionTime + Counter counter + Integer infoCount + String errorStack + String serverOutput + LinkedHashMap tests + String status + + new(String reporterId, String connectionName, List pathList) { + this.reporterId = reporterId + this.connectionName = connectionName + this.pathList = pathList + this.counter = new Counter + this.tests = new LinkedHashMap + } + + def getName() { + val time = startTime.substring(11,19) + val conn = connectionName?.substring(15) + return '''«time» («conn»)''' + } + + def void put(List items) { + for (item : items) { + if (item instanceof Test) { + this.tests.put(item.id, item) + } + if (item instanceof Suite) { + item.items.put + } + } + } + + def getTest(String id) { + return tests.get(id) + } + + def getTotalNumberOfCompletedTests() { + return counter.disabled + counter.success + counter.failure + counter.error + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend new file mode 100644 index 00000000..d75efadc --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.ArrayList +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors + +@Accessors +class Suite extends Item { + String name + String description + List items + + new() { + items = new ArrayList + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend new file mode 100644 index 00000000..30070e85 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.model.runner + +import java.util.List +import javax.swing.Icon +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.resources.UtplsqlResources + +@Accessors +class Test extends Item { + String executableType + String ownerName + String objectName + String procedureName + Boolean disabled + String name + String description + Integer testNumber + List failedExpectations + + def getStatusIcon() { + var Icon icon = null + if (startTime !== null && endTime === null ) { + icon = UtplsqlResources.getIcon("PROGRESS_ICON") + } else { + if (counter !== null) { + if (counter.success > 0) { + icon = UtplsqlResources.getIcon("SUCCESS_ICON") + } else if (counter.error > 0) { + icon = UtplsqlResources.getIcon("ERROR_ICON") + } else if (counter.failure > 0) { + icon = UtplsqlResources.getIcon("FAILURE_ICON") + } else if (counter.disabled > 0) { + icon = UtplsqlResources.getIcon("DISABLED_ICON") + } + } + } + return icon + } + + def getWarningIcon() { + var Icon icon = null + if (counter !== null) { + if (counter.warning > 0) { + icon = UtplsqlResources.getIcon("WARNING_ICON") + } + } + return icon + } + + def getInfoIcon() { + var Icon icon = null + if (serverOutput !== null && serverOutput.length > 0) { + icon = UtplsqlResources.getIcon("INFO_ICON") + } + return icon + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend new file mode 100644 index 00000000..3eb38765 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend @@ -0,0 +1,60 @@ +/* Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.parser + +import oracle.dbtools.parser.LexerToken +import oracle.dbtools.raptor.navigator.plsql.PlSqlArguments +import oracle.dbtools.raptor.navigator.plsql.PlsqlStructureParser + +/* + * Cannot use this class within SQL Developer because the + * package oracle.dbtools.parser is not exported in sqldeveloper OSGI bundle (extension) + * (throws ClassNotFoundException at runtime). + * + * The dbtools-common.jar contains the necessary packages, + * but it cannot be distributed with the utPLSQL extension + * without violating the Oracle license agreement. + */ +class SqlDevParser { + def getMembers(String plsql) { + val tokens = LexerToken.parse(plsql) + val parser = new PlsqlStructureParser + parser.parse(tokens, PlSqlArguments.sort) + return parser.children + } + + private def getStartLine(String plsql, int offset) { + var int line = 1 + for (var i = 0; i < plsql.length; i++) { + val c = plsql.substring(i, i+1) + if (i > offset) { + return line + } else if (c == '\n') { + line = line + 1 + } + } + return line + } + + def getMemberStartLine(String plsql, String memberName) { + val members = plsql.members + val member = members.findFirst[it.name.equalsIgnoreCase(memberName)] + if (member !== null) { + return getStartLine(plsql, member.codeOffset) + } else { + 1 + } + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend index 6ac25a20..736be98e 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend @@ -110,6 +110,7 @@ class UtplsqlParser { val u = new Unit u.name = m.group(4) u.position = m.start + u.positionOfName = m.start(4) units.add(u) } } @@ -221,5 +222,32 @@ class UtplsqlParser { } return "" } - + + private def getStartLine(int position) { + var int line = 1 + for (var i = 0; i < plsql.length; i++) { + val c = plsql.substring(i, i+1) + if (i > position) { + return line + } else if (c == '\n') { + line = line + 1 + } + } + return line + } + + /** + * get the line of a PL/SQL package unit + * + * @param unitName name of the unit. Only procedures are supported + * @return the line where the procedure is defined + */ + def getLineOf(String unitName) { + for (u : units) { + if (u.name.equalsIgnoreCase(unitName)) { + return u.positionOfName.startLine + } + } + return 1 + } } \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend new file mode 100644 index 00000000..8e7bf49a --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend @@ -0,0 +1,305 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.runner + +import java.awt.Dimension +import java.awt.Toolkit +import java.sql.Connection +import java.text.SimpleDateFormat +import java.util.Date +import java.util.List +import java.util.UUID +import java.util.logging.Logger +import javax.swing.JFrame +import oracle.dbtools.raptor.utils.Connections +import org.utplsql.sqldev.dal.RealtimeReporterDao +import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer +import org.utplsql.sqldev.model.runner.PostRunEvent +import org.utplsql.sqldev.model.runner.PostSuiteEvent +import org.utplsql.sqldev.model.runner.PostTestEvent +import org.utplsql.sqldev.model.runner.PreRunEvent +import org.utplsql.sqldev.model.runner.PreSuiteEvent +import org.utplsql.sqldev.model.runner.PreTestEvent +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent +import org.utplsql.sqldev.model.runner.Run +import org.utplsql.sqldev.resources.UtplsqlResources +import org.utplsql.sqldev.ui.runner.RunnerFactory +import org.utplsql.sqldev.ui.runner.RunnerPanel +import org.utplsql.sqldev.ui.runner.RunnerView + +class UtplsqlRunner implements RealtimeReporterEventConsumer { + + static val Logger logger = Logger.getLogger(UtplsqlRunner.name); + + var List pathList + var String connectionName + var Connection producerConn + var Connection consumerConn + val String reporterId = UUID.randomUUID().toString.replace("-", "") + var Run run + var RunnerPanel panel + var Thread producerThread + var Thread consumerThread + + new(List pathList, String connectionName) { + this.pathList = pathList + setConnection(connectionName) + } + + /** + * this constructor is intended for tests only + */ + new(List pathList, Connection producerConn, Connection consumerConn) { + this.pathList = pathList + this.producerConn = producerConn + this.consumerConn = consumerConn + } + + private def setConnection(String connectionName) { + if (connectionName === null) { + throw new RuntimeException("Cannot initialize a RealtimeConsumer without a ConnectionName") + } else { + this.producerConn = Connections.instance.cloneConnection(Connections.instance.getConnection(connectionName)) + this.consumerConn = Connections.instance.cloneConnection(Connections.instance.getConnection(connectionName)) + } + this.connectionName = connectionName + } + + def dispose() { + // running in SQL Developer + if (!producerConn.closed) { + producerConn.close; + } + if (!consumerConn.closed) { + consumerConn.close; + } + } + + override void process(RealtimeReporterEvent event) { + logger.fine(event.toString) + event.doProcess + } + + private def getSysdate() { + val dateTime = new Date(System.currentTimeMillis); + val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000'"); + return df.format(dateTime) + + } + + private def initRun() { + run = new Run(reporterId, connectionName, pathList) + run.startTime = sysdate + run.counter.disabled = 0 + run.counter.success = 0 + run.counter.failure = 0 + run.counter.error = 0 + run.counter.warning = 0 + run.infoCount = 0 + run.totalNumberOfTests = -1 + run.currentTestNumber = 0 + run.status = UtplsqlResources.getString("RUNNER_INITIALIZING_TEXT") + panel.model = run + panel.update(reporterId) + } + + private def dispatch doProcess(PreRunEvent event) { + run.totalNumberOfTests = event.totalNumberOfTests + run.put(event.items) + run.status = UtplsqlResources.getString("RUNNER_RUNNING_TEXT") + panel.update(reporterId) + } + + private def dispatch doProcess(PostRunEvent event) { + run.startTime = event.startTime + run.endTime = event.endTime + run.executionTime = event.executionTime + run.errorStack = event.errorStack + run.serverOutput = event.serverOutput + run.status = String.format(UtplsqlResources.getString("RUNNER_FINNISHED_TEXT"), event.executionTime) + panel.update(reporterId) + } + + private def dispatch doProcess(PreSuiteEvent event) { + // ignore + } + + private def dispatch doProcess(PostSuiteEvent event) { + val test = run.currentTest + // Errors on suite levels are reported as warnings by the utPLSQL framework, + // since an error on suite level does not affect a status of a test. + // It is possible that the test is OK, but contains error messages on suite level(s) + // Populating test.errorStack would be a) wrong and b) redundant + if (event.warnings !== null) { + if (test.counter.warning == 0) { + test.counter.warning = 1 + run.counter.warning = run.counter.warning + 1 + } + test.warnings = ''' + «IF test.warnings !== null» + «test.warnings» + + «ENDIF» + For suite «event.id»: + + «event.warnings» + '''.toString.trim + } + if (event.serverOutput !== null) { + if (test.serverOutput === null) { + run.infoCount = run.infoCount + 1 + } + test.serverOutput = ''' + «IF test.serverOutput !== null» + «test.serverOutput» + + «ENDIF» + For suite «event.id»: + + «event.serverOutput» + '''.toString.trim + } + panel.update(reporterId) + } + + private def dispatch doProcess(PreTestEvent event) { + val test = run.getTest(event.id) + if (test === null) { + logger.severe('''Could not find test id "«event.id»" when processing PreTestEvent «event.toString».''') + } else { + test.startTime = sysdate + } + run.status = event.id + run.currentTestNumber = event.testNumber + run.currentTest = test + panel.update(reporterId) + } + + private def dispatch doProcess(PostTestEvent event) { + val test = run.getTest(event.id) + if (test === null) { + logger.severe('''Could not find test id "«event.id»"" when processing PostTestEvent «event.toString».''') + } else { + test.startTime = event.startTime + test.endTime = event.endTime + test.executionTime = event.executionTime + test.counter = event.counter + test.errorStack = event.errorStack + test.serverOutput = event.serverOutput + if (test.serverOutput !== null) { + run.infoCount = run.infoCount + 1 + } + test.failedExpectations = event.failedExpectations + test.warnings = event.warnings + if (test.warnings !== null) { + // it does not matter how many rows are used by utPLSQL to store a warning event + test.counter.warning = 1 + } else { + test.counter.warning = 0 + } + } + run.counter.disabled = run.counter.disabled + event.counter.disabled + run.counter.success = run.counter.success + event.counter.success + run.counter.failure = run.counter.failure + event.counter.failure + run.counter.error = run.counter.error + event.counter.error + run.counter.warning = run.counter.warning + test.counter.warning + panel.update(reporterId) + } + + private def void produce() { + try { + logger.fine('''Running utPLSQL tests and producing events via reporter id «reporterId»...''') + val dao = new RealtimeReporterDao(producerConn) + dao.produceReport(reporterId, pathList) + logger.fine('''All events produced for reporter id «reporterId».''') + } catch (Exception e) { + logger.severe('''Error while producing events for reporter id «reporterId»: «e?.message»''') + } + } + + private def void consume() { + try { + logger.fine('''Consuming events from reporter id «reporterId» in realtime...''') + val dao = new RealtimeReporterDao(consumerConn) + dao.consumeReport(reporterId, this) + logger.fine('''All events consumed.''') + } catch (Exception e) { + logger.severe('''Error while consuming events for reporter id «reporterId»: «e?.message»''') + } + if (run.totalNumberOfTests < 0) { + run.status = UtplsqlResources.getString("RUNNER_NO_TESTS_FOUND_TEXT") + run.totalNumberOfTests = 0 + panel.update(reporterId) + } + if (isRunningInSqlDeveloper) { + dispose + } + } + + private def isRunningInSqlDeveloper() { + return connectionName !== null + } + + private def initGUI() { + var RunnerView dockable = null + if (runningInSqlDeveloper && (dockable = RunnerFactory.dockable as RunnerView) === null) { + logger.severe('''Error getting utPLSQL dockable. Cannot run utPLSQL test.''') + return false + } else { + if (runningInSqlDeveloper) { + RunnerFactory.showDockable; + panel = dockable.runnerPanel + } else { + val frame = new JFrame("utPLSQL Runner Panel") + frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE; + panel = new RunnerPanel + frame.add(panel.getGUI) + frame.preferredSize = new Dimension(600, 800) + frame.pack + val dim = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); + frame.setVisible(true) + } + initRun + } + return true + } + + def runTestAsync() { + // start tests when the GUI has been successfully initialized. + if (initGUI) { + // the producer + val Runnable producer = [|produce] + producerThread = new Thread(producer) + producerThread.name = "realtime producer" + producerThread.start + // the consumer + val Runnable consumer = [|consume] + consumerThread = new Thread(consumer) + consumerThread.name = "realtime consumer" + consumerThread.start + } + } + + def getProducerThread() { + return producerThread + } + + def getConsumerThread() { + return consumerThread + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend similarity index 96% rename from sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend index 1e80f4de..bd03229a 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev +package org.utplsql.sqldev.runner import java.util.List import java.util.logging.Logger @@ -28,8 +28,8 @@ import oracle.ide.controller.IdeAction import org.utplsql.sqldev.model.preference.PreferenceModel import org.utplsql.sqldev.resources.UtplsqlResources -class UtplsqlWorksheet { - static val Logger logger = Logger.getLogger(UtplsqlWorksheet.name); +class UtplsqlWorksheetRunner { + static val Logger logger = Logger.getLogger(UtplsqlWorksheetRunner.name); var PreferenceModel preferences var String connectionName diff --git a/sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend similarity index 85% rename from sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend index 2c47302b..adc057a2 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev +package org.utplsql.sqldev.ui.common import java.io.File import java.util.logging.Logger -import javax.swing.JComboBox import javax.swing.JFileChooser import javax.swing.JFrame import javax.swing.JTextField @@ -46,11 +45,4 @@ class DirectoryChooser { } } - def static void choose (JFrame parentFrame, String title, JComboBox comboBox) { - val dir = choose(parentFrame, title, comboBox.editor.item as String); - if (dir !== null) { - comboBox.editor.item = dir - } - } - } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporterWindow.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend similarity index 94% rename from sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporterWindow.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend index c9949738..1c57eb6e 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/CodeCoverageReporterWindow.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev +package org.utplsql.sqldev.ui.coverage import java.awt.Component import java.awt.Dimension @@ -38,9 +38,10 @@ import javax.swing.JTextField import javax.swing.ScrollPaneConstants import javax.swing.SwingUtilities import org.springframework.core.task.SimpleAsyncTaskExecutor +import org.utplsql.sqldev.coverage.CodeCoverageReporter import org.utplsql.sqldev.resources.UtplsqlResources -class CodeCoverageReporterWindow extends JFrame implements ActionListener, FocusListener { +class CodeCoverageReporterDialog extends JFrame implements ActionListener, FocusListener { var CodeCoverageReporter reporter var JButton runButton @@ -55,14 +56,14 @@ class CodeCoverageReporterWindow extends JFrame implements ActionListener, Focus def static createAndShow(CodeCoverageReporter reporter) { SwingUtilities.invokeLater(new Runnable() { override run() { - CodeCoverageReporterWindow.createAndShowWithinEventThread(reporter); + CodeCoverageReporterDialog.createAndShowWithinEventThread(reporter); } }); } - def private static createAndShowWithinEventThread(CodeCoverageReporter reporter) { + private def static createAndShowWithinEventThread(CodeCoverageReporter reporter) { // create and layout the dialog - val frame = new CodeCoverageReporterWindow(reporter) + val frame = new CodeCoverageReporterDialog(reporter) reporter.frame = frame frame.pack // center dialog @@ -75,7 +76,7 @@ class CodeCoverageReporterWindow extends JFrame implements ActionListener, Focus new(CodeCoverageReporter reporter) { super(UtplsqlResources.getString("WINDOW_CODE_COVERAGE_REPORT_LABEL")) this.reporter = reporter - val pane = this.getContentPane(); + val pane = getContentPane(); pane.setLayout(new GridBagLayout()); val c = new GridBagConstraints(); @@ -169,7 +170,7 @@ class CodeCoverageReporterWindow extends JFrame implements ActionListener, Focus } def exit() { - this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } override actionPerformed(ActionEvent e) { diff --git a/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend similarity index 58% rename from sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend index 82aff318..9633fac0 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev +package org.utplsql.sqldev.ui.preference import java.awt.event.ActionEvent import java.awt.event.ActionListener import java.util.Map -import javax.swing.BorderFactory import javax.swing.JButton import javax.swing.JCheckBox import javax.swing.JPanel import javax.swing.JSpinner +import javax.swing.JTabbedPane import javax.swing.JTextField import javax.swing.SpinnerNumberModel import javax.swing.table.DefaultTableModel @@ -33,14 +33,26 @@ import oracle.ide.panels.TraversalException import oracle.javatools.ui.layout.FieldLayoutBuilder import org.utplsql.sqldev.model.preference.PreferenceModel import org.utplsql.sqldev.resources.UtplsqlResources +import org.utplsql.sqldev.ui.common.DirectoryChooser class PreferencePanel extends DefaultTraversablePanel { val JPanel runTestPanel = new JPanel(); + val JCheckBox useRealtimeReporterCheckBox = new JCheckBox val JCheckBox unsharedWorksheetCheckBox = new JCheckBox val JCheckBox resetPackageCheckBox = new JCheckBox val JCheckBox clearScreenCheckBox = new JCheckBox val JCheckBox autoExecuteCheckBox = new JCheckBox val JCheckBox checkRunUtplsqlTestCheckBox = new JCheckBox + val JPanel realtimeReporterPanel = new JPanel + val SpinnerNumberModel numberOfRunsInHistoryModel = new SpinnerNumberModel(1, 1, 100, 1); + val JSpinner numberOfRunsInHistorySpinner = new JSpinner(numberOfRunsInHistoryModel); + val JCheckBox showDisabledCounterCheckBox = new JCheckBox + val JCheckBox showWarningsCounterCheckBox = new JCheckBox + val JCheckBox showInfoCounterCheckBox = new JCheckBox + val JCheckBox showWarningIndicatorCheckBox = new JCheckBox + val JCheckBox showInfoIndicatorCheckBox = new JCheckBox + val JCheckBox showTestDescriptionCheckBox = new JCheckBox + val JCheckBox syncDetailTabCheckBox = new JCheckBox val JPanel generateTestPanel = new JPanel(); val JTextField testPackagePrefixTextField = new JTextField val JTextField testPackageSuffixTextField = new JTextField @@ -67,95 +79,132 @@ class PreferencePanel extends DefaultTraversablePanel { layoutControls() } - def private layoutControls() { + private def layoutControls() { // run test group - runTestPanel.border = BorderFactory.createTitledBorder(UtplsqlResources.getString("MENU_RUN_TEST_LABEL")) - val FieldLayoutBuilder b1 = new FieldLayoutBuilder(runTestPanel) - b1.alignLabelsLeft = true - b1.add( - b1.field.label.withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")).component( + val FieldLayoutBuilder runTab = new FieldLayoutBuilder(runTestPanel) + runTab.alignLabelsLeft = true + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_LABEL")).component( + useRealtimeReporterCheckBox)) + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")).component( unsharedWorksheetCheckBox)) - b1.add( - b1.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component( + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component( resetPackageCheckBox)) - b1.add( - b1.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component( + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component( clearScreenCheckBox)) - b1.add( - b1.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component( + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component( autoExecuteCheckBox)) - b1.add( - b1.field.label.withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")).component( + runTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")).component( checkRunUtplsqlTestCheckBox)) + runTab.addVerticalSpring + + // realtime reporter group + val FieldLayoutBuilder rrTab = new FieldLayoutBuilder(realtimeReporterPanel) + rrTab.alignLabelsLeft = true + + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL")).component( + numberOfRunsInHistorySpinner)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL")).component( + showDisabledCounterCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL")).component( + showWarningsCounterCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL")).component( + showInfoCounterCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL")).component( + showWarningIndicatorCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL")).component( + showInfoIndicatorCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL")).component( + showTestDescriptionCheckBox)) + rrTab.add( + runTab.field.label.withText(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL")).component( + syncDetailTabCheckBox)) + rrTab.addVerticalSpring + // generate test group - generateTestPanel.border = BorderFactory.createTitledBorder(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL")) - val FieldLayoutBuilder b2 = new FieldLayoutBuilder(generateTestPanel) - b2.alignLabelsLeft = true - b2.stretchComponentsWithNoButton = true - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL")).component( + val FieldLayoutBuilder generateTab = new FieldLayoutBuilder(generateTestPanel) + generateTab.alignLabelsLeft = true + generateTab.stretchComponentsWithNoButton = true + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL")).component( testPackagePrefixTextField)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL")).component( testPackageSuffixTextField)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL")).component( testUnitPrefixTextField)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL")).component( testUnitSuffixTextField)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL")).component( numberOfTestsPerUnitSpinner)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL")).component( generateCommentsCheckBox)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL")).component( disableTestsCheckBox)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL")).component( suitePathTextField)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL")).component( indentSpacesSpinner)) - b2.add( - b2.field.label.withText(UtplsqlResources.getString("PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL")).component( + generateTab.add( + generateTab.field.label.withText(UtplsqlResources.getString("PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL")).component( checkGenerateUtplsqlTestCheckBox).button(createCodeTemplatesButton).withText( UtplsqlResources.getString("PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL"))) + generateTab.addVerticalSpring + // oddgen group - oddgenPanel.border = BorderFactory.createTitledBorder("oddgen") - val FieldLayoutBuilder b3 = new FieldLayoutBuilder(oddgenPanel) - b3.alignLabelsLeft = true - b3.stretchComponentsWithNoButton = true - b3.add( - b3.field.label.withText(UtplsqlResources.getString("PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL")).component( + val FieldLayoutBuilder oddgenTab = new FieldLayoutBuilder(oddgenPanel) + oddgenTab.alignLabelsLeft = true + oddgenTab.stretchComponentsWithNoButton = true + oddgenTab.add( + oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL")).component( rootFolderInOddgenViewTextField)) - b3.add( - b3.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL")).component( + oddgenTab.add( + oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL")).component( generateFilesCheckBox)) - b3.add( - b3.field.label.withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL")).component( + oddgenTab.add( + oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL")).component( outputDirectoryTextField).button(outputDirectoryBrowse).withText( UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_BUTTON_LABEL"))) - b3.add( - b3.field.label.withText(UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL")).component( + oddgenTab.add( + oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL")).component( deleteExistingFilesCheckBox)) - - // putting everything together + oddgenTab.addVerticalSpring + + // putting groups into tabbed panes + val tabbedPane = new JTabbedPane() + tabbedPane.add(UtplsqlResources.getString("MENU_RUN_TEST_LABEL"), runTestPanel) + tabbedPane.add(UtplsqlResources.getString("MENU_REALTIME_REPORTER_LABEL"), realtimeReporterPanel) + tabbedPane.add(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL"), generateTestPanel) + tabbedPane.add("oddgen", oddgenPanel) val FieldLayoutBuilder builder = new FieldLayoutBuilder(this) builder.alignLabelsLeft = true - builder.addVerticalField("", runTestPanel) - builder.addVerticalField("", generateTestPanel) - builder.addVerticalField("", oddgenPanel) + builder.addVerticalField("", tabbedPane) builder.addVerticalSpring // register action listener for create code template button createCodeTemplatesButton.addActionListener(new ActionListener() { override actionPerformed(ActionEvent event) { saveCodeTemplates - } - + } }) // register action listener for directory chooser @@ -261,11 +310,20 @@ class PreferencePanel extends DefaultTraversablePanel { override onEntry(TraversableContext traversableContext) { var PreferenceModel info = traversableContext.userInformation + useRealtimeReporterCheckBox.selected = info.useRealtimeReporter unsharedWorksheetCheckBox.selected = info.unsharedWorksheet resetPackageCheckBox.selected = info.resetPackage clearScreenCheckBox.selected = info.clearScreen autoExecuteCheckBox.selected = info.autoExecute checkRunUtplsqlTestCheckBox.selected = info.checkRunUtplsqlTest + numberOfRunsInHistorySpinner.value = info.numberOfRunsInHistory + showDisabledCounterCheckBox.selected = info.showDisabledCounter + showWarningsCounterCheckBox.selected = info.showWarningsCounter + showInfoCounterCheckBox.selected = info.showInfoCounter + showWarningIndicatorCheckBox.selected = info.showWarningIndicator + showInfoIndicatorCheckBox.selected = info.showInfoIndicator + showTestDescriptionCheckBox.selected = info.showTestDescription + syncDetailTabCheckBox.selected = info.syncDetailTab testPackagePrefixTextField.text = info.testPackagePrefix testPackageSuffixTextField.text = info.testPackageSuffix testUnitPrefixTextField.text = info.testUnitPrefix @@ -286,11 +344,20 @@ class PreferencePanel extends DefaultTraversablePanel { override onExit(TraversableContext traversableContext) throws TraversalException { var PreferenceModel info = traversableContext.userInformation + info.useRealtimeReporter = useRealtimeReporterCheckBox.selected info.unsharedWorksheet = unsharedWorksheetCheckBox.selected info.resetPackage = resetPackageCheckBox.selected info.clearScreen = clearScreenCheckBox.selected info.autoExecute = autoExecuteCheckBox.selected + info.numberOfRunsInHistory = numberOfRunsInHistorySpinner.value as Integer info.checkRunUtplsqlTest = checkRunUtplsqlTestCheckBox.selected + info.showDisabledCounter = showDisabledCounterCheckBox.selected + info.showWarningsCounter = showWarningsCounterCheckBox.selected + info.showInfoCounter = showInfoCounterCheckBox.selected + info.showWarningIndicator = showWarningIndicatorCheckBox.selected + info.showInfoIndicator = showInfoIndicatorCheckBox.selected + info.showTestDescription = showTestDescriptionCheckBox.selected + info.syncDetailTab = syncDetailTabCheckBox.selected info.testPackagePrefix = testPackagePrefixTextField.text info.testPackageSuffix = testPackageSuffixTextField.text info.testUnitPrefix = testUnitPrefixTextField.text @@ -308,7 +375,7 @@ class PreferencePanel extends DefaultTraversablePanel { super.onExit(traversableContext) } - def private static PreferenceModel getUserInformation(TraversableContext tc) { + private def static PreferenceModel getUserInformation(TraversableContext tc) { return PreferenceModel.getInstance(tc.propertyStorage) } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend new file mode 100644 index 00000000..f78658a3 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.util.AbstractMap + +class ComboBoxItem extends AbstractMap.SimpleEntry { + new(K key, V value) { + super(key, value) + } + + override toString() { + return value.toString + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend new file mode 100644 index 00000000..acd9418b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.util.List +import javax.swing.table.DefaultTableModel +import org.utplsql.sqldev.model.runner.Expectation +import org.utplsql.sqldev.resources.UtplsqlResources + +class FailuresTableModel extends DefaultTableModel { + List failedExpectations + + new() { + super() + } + + def setModel(List failedExpectations) { + this.failedExpectations = failedExpectations + } + + def getExpectation(int row) { + return failedExpectations.get(row) + } + + override getRowCount() { + if (failedExpectations === null) { + return 0 + } + return failedExpectations.size() + } + + override getColumnCount() { + return 2 + } + + override getValueAt(int row, int col) { + val expectation = failedExpectations.get(row) + if (expectation === null) { + return null + } + switch (col) { + case 0: { + return row + 1 + } + case 1: { + return expectation.shortFailureText + } + default: { + return null + } + } + } + + override getColumnName(int col) { + return #["#", UtplsqlResources.getString("RUNNER_ASSERT_DESCRIPTION_COLUMN")].get(col) + } + + override isCellEditable(int row, int column) { + return false + } + + override getColumnClass(int col) { + switch (col) { + case 0: { + return Integer + } + case 1: { + return String + } + default: { + return String + } + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend new file mode 100644 index 00000000..1fb4adaf --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.awt.Color +import java.awt.GradientPaint +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Insets +import javax.swing.JToolBar +import javax.swing.UIManager +import javax.swing.border.BevelBorder +import javax.swing.border.EmptyBorder + +class GradientToolbar extends JToolBar { + + private def isOracleLookAndFeel() { + val laf = UIManager.lookAndFeel?.name + if (laf == "Oracle Look and Feel version 2") { + return true + } else { + return false + } + } + + new() { + super() + if (oracleLookAndFeel) { + this.border = new EmptyBorder(new Insets(2, 2, 2, 2)) // top, left, bottom, right + } else { + this.border = new BevelBorder(BevelBorder.RAISED) + } + } + + override paintComponent(Graphics g) { + if (oracleLookAndFeel) { + // emulate Oracle toolbar + // 1. default for non-opaque components + if (!opaque) { + super.paintComponent(g) + return + } + + // 2. paint gradient background from top to bottom with separator line at the bottom + val g2d = g as Graphics2D + val w = width + val h = height - 1 + val h2 = height / 2 as int + val colorTop = new Color(237, 237, 237) + val colorMiddle = new Color(244, 244, 244) + val colorBottom = new Color(254, 254, 254) + val colorBottomLine = Color.LIGHT_GRAY + val gp1 = new GradientPaint(0, 0, colorTop, 0, h2, colorMiddle) + g2d.paint = gp1 + g2d.fillRect(0, 0, w, h2) + val gp2 = new GradientPaint(0, h2, colorMiddle, 0, h, colorBottom) + g2d.paint = gp2 + g2d.fillRect(0, h2, w, h) + g2d.paint = colorBottomLine + g2d.fillRect(0, h, w, h+1) + + // 3. do rest, changing opaque to ensure background is not overwritten + setOpaque(false) + super.paintComponent(g) + setOpaque(true) + } else { + // default logic + super.paintComponent(g) + } + } + +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend new file mode 100644 index 00000000..61915559 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import oracle.ide.docking.DockStation +import oracle.ide.docking.DockableFactory +import oracle.ide.docking.DockingParam +import oracle.ide.layout.ViewId + +class RunnerFactory implements DockableFactory { + public static val FACTORY_NAME = "UTPLSQL_RUNNER_FACTORY" + + var RunnerView dockable + + override install() { + val dockStation = DockStation.getDockStation(); + val dp = new DockingParam(); + val referencedViewId = new ViewId("DatabaseNavigatorWindow", "DatabaseNavigatorWindow") + val referencedDockable = dockStation.findDockable(referencedViewId) + dp.tabbedWith = referencedDockable + dockStation.dock(getLocalDockable(), dp); + } + + override getDockable(ViewId viewId) { + if (viewId === RunnerView.VIEW_ID) { + return localDockable + } + return null + } + + private def getLocalDockable() { + if (dockable === null) { + dockable = new RunnerView + } + return dockable + } + + static def getDockable() { + val dockStation = DockStation.dockStation + val dockable = dockStation.findDockable(RunnerView.VIEW_ID) + return dockable as RunnerView + } + + static def void showDockable() { + val dockStation = DockStation.dockStation + dockStation.setDockableVisible(getDockable(), true) + } +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend new file mode 100644 index 00000000..e3d2d73c --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend @@ -0,0 +1,1227 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.awt.Color +import java.awt.Component +import java.awt.Dimension +import java.awt.FlowLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Insets +import java.awt.event.ActionEvent +import java.awt.event.ActionListener +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import java.text.DecimalFormat +import java.util.ArrayList +import java.util.regex.Pattern +import javax.swing.BorderFactory +import javax.swing.Box +import javax.swing.DefaultComboBoxModel +import javax.swing.JCheckBoxMenuItem +import javax.swing.JComboBox +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JMenuItem +import javax.swing.JPanel +import javax.swing.JPopupMenu +import javax.swing.JProgressBar +import javax.swing.JScrollPane +import javax.swing.JSeparator +import javax.swing.JSplitPane +import javax.swing.JTabbedPane +import javax.swing.JTable +import javax.swing.SwingConstants +import javax.swing.UIManager +import javax.swing.border.EmptyBorder +import javax.swing.event.HyperlinkEvent +import javax.swing.event.HyperlinkListener +import javax.swing.event.ListSelectionEvent +import javax.swing.event.ListSelectionListener +import javax.swing.plaf.basic.BasicProgressBarUI +import javax.swing.table.DefaultTableCellRenderer +import oracle.dbtools.raptor.controls.grid.DefaultDrillLink +import oracle.dbtools.raptor.utils.Connections +import oracle.ide.config.Preferences +import oracle.javatools.ui.table.ToolbarButton +import org.springframework.web.util.HtmlUtils +import org.utplsql.sqldev.dal.UtplsqlDao +import org.utplsql.sqldev.model.LimitedLinkedHashMap +import org.utplsql.sqldev.model.preference.PreferenceModel +import org.utplsql.sqldev.model.runner.Run +import org.utplsql.sqldev.parser.UtplsqlParser +import org.utplsql.sqldev.resources.UtplsqlResources +import org.utplsql.sqldev.runner.UtplsqlRunner +import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner + +class RunnerPanel implements ActionListener, MouseListener, HyperlinkListener { + static val GREEN = new Color(0, 153, 0) + static val RED = new Color(153, 0, 0) + static val INDICATOR_WIDTH = 20 + static val OVERVIEW_TABLE_ROW_HEIGHT = 20 + static val TEXTPANE_DIM = new Dimension(100, 100) + LimitedLinkedHashMap runs = new LimitedLinkedHashMap(10) + Run currentRun + JPanel basePanel + ToolbarButton refreshButton + ToolbarButton rerunButton + ToolbarButton rerunWorksheetButton + DefaultComboBoxModel> runComboBoxModel + ToolbarButton clearButton + JComboBox> runComboBox + JLabel statusLabel + JLabel testCounterValueLabel + JLabel errorCounterValueLabel + JLabel failureCounterValueLabel + JLabel disabledCounterValueLabel + JLabel warningsCounterValueLabel + JLabel infoCounterValueLabel + JCheckBoxMenuItem showDisabledCounterCheckBoxMenuItem + JCheckBoxMenuItem showWarningsCounterCheckBoxMenuItem + JCheckBoxMenuItem showInfoCounterCheckBoxMenuItem + JProgressBar progressBar; + TestOverviewTableModel testOverviewTableModel + JTable testOverviewTable + JMenuItem testOverviewRunMenuItem + JMenuItem testOverviewRunWorksheetMenuItem + JCheckBoxMenuItem showTestDescriptionCheckBoxMenuItem + JCheckBoxMenuItem showWarningIndicatorCheckBoxMenuItem + JCheckBoxMenuItem showInfoIndicatorCheckBoxMenuItem + JCheckBoxMenuItem syncDetailTabCheckBoxMenuItem + RunnerTextField testOwnerTextField + RunnerTextField testPackageTextField + RunnerTextField testProcedureTextField + RunnerTextArea testDescriptionTextArea + RunnerTextArea testIdTextArea + RunnerTextField testStartTextField + FailuresTableModel failuresTableModel + JTable failuresTable + RunnerTextPane testFailureMessageTextPane + RunnerTextPane testErrorStackTextPane + RunnerTextPane testWarningsTextPane + RunnerTextPane testServerOutputTextPane + JTabbedPane testDetailTabbedPane + + def Component getGUI() { + if (basePanel === null) { + initializeGUI() + } + if (!basePanel.showing) { + applyPreferences + } + return basePanel + } + + private def resetDerived() { + testOverviewTable.rowSorter.sortKeys = null + testOverviewRunMenuItem.enabled = false + testOverviewRunWorksheetMenuItem.enabled = false + testIdTextArea.text = null + testOwnerTextField.text = null + testPackageTextField.text = null + testProcedureTextField.text = null + testDescriptionTextArea.text = null + testStartTextField.text = null + failuresTableModel.model = null + failuresTableModel.fireTableDataChanged + testFailureMessageTextPane.text = null + testErrorStackTextPane.text = null + testWarningsTextPane.text = null + testServerOutputTextPane.text = null + } + + private def refreshRunsComboBox() { + if (runs.size > 0) { + runComboBox.removeActionListener(this) + runComboBoxModel.removeAllElements + for (var i = runs.size - 1 ; i >= 0; i--) { + val entry = runs.entrySet.get(i) + val item = new ComboBoxItem(entry.key, entry.value.name) + runComboBoxModel.addElement(item) + } + runComboBox.selectedIndex = 0 + runComboBox.addActionListener(this) + } + } + + private def applyShowNumberOfRunsInHistory(int maxRuns) { + if (maxRuns != runs.maxEntries) { + val newRuns = new LimitedLinkedHashMap(maxRuns) + for (entry : runs.entrySet) { + newRuns.put(entry.key, entry.value) + } + runs = newRuns + } + } + + private def applyShowDisabledCounter(boolean show) { + disabledCounterValueLabel.parent.visible = showDisabledCounterCheckBoxMenuItem.selected + } + + private def applyShowWarningsCounter(boolean show) { + warningsCounterValueLabel.parent.visible = showWarningsCounterCheckBoxMenuItem.selected + } + + private def applyShowInfoCounter(boolean show) { + infoCounterValueLabel.parent.visible = showInfoCounterCheckBoxMenuItem.selected + } + + private def applyShowTestDescription(boolean show) { + testOverviewTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.selected) + val idColumn = testOverviewTable.columnModel.getColumn(3) + idColumn.headerValue = testOverviewTableModel.testIdColumnName + testOverviewTable.tableHeader.repaint + } + + private def applyShowWarningIndicator(boolean show) { + val col = testOverviewTable.columnModel.getColumn(1) + if (show) { + col.width = INDICATOR_WIDTH + col.minWidth = INDICATOR_WIDTH + col.maxWidth = INDICATOR_WIDTH + col.preferredWidth = INDICATOR_WIDTH + } else { + col.width = 0 + col.minWidth = 0 + col.maxWidth = 0 + col.preferredWidth = 0 + } + } + + private def applyShowInfoIndicator(boolean show) { + val col = testOverviewTable.columnModel.getColumn(2) + if (show) { + col.width = INDICATOR_WIDTH + col.minWidth = INDICATOR_WIDTH + col.maxWidth = INDICATOR_WIDTH + col.preferredWidth = INDICATOR_WIDTH + } else { + col.width = 0 + col.minWidth = 0 + col.maxWidth = 0 + col.preferredWidth = 0 + } + } + + private def openSelectedTest() { + val rowIndex = testOverviewTable.selectedRow + if (rowIndex != -1) { + val row = testOverviewTable.convertRowIndexToModel(rowIndex) + val test = testOverviewTableModel.getTest(row) + val dao = new UtplsqlDao(Connections.instance.getConnection(currentRun.connectionName)) + val source = dao.getSource(test.ownerName, "PACKAGE", test.objectName.toUpperCase).trim + val parser = new UtplsqlParser(source) + val line = parser.getLineOf(test.procedureName) + openEditor(test.ownerName, "PACKAGE", test.objectName.toUpperCase, line, 1) + } + } + + private def openSelectedFailure() { + val rowIndex = failuresTable.selectedRow + if (rowIndex != -1) { + val row = failuresTable.convertRowIndexToModel(rowIndex) + val expectation = failuresTableModel.getExpectation(row) + val test = testOverviewTableModel.getTest(testOverviewTable.convertRowIndexToModel(testOverviewTable.selectedRow)) + openEditor(test.ownerName, "PACKAGE BODY", test.objectName.toUpperCase, expectation.callerLine, 1) + } + } + + private def getHtml(String text) { + val html = ''' + + + + + + «getLinkedAndFormattedText(text)» + + + ''' + return html + } + + private def openLink(String link) { + val parts = link.split("/") + val type = parts.get(0) + val ownerName = parts.get(1) + val objectName = parts.get(2) + var line = Integer.parseInt(parts.get(3)) + val dao = new UtplsqlDao(Connections.instance.getConnection(currentRun.connectionName)) + val objectType = if (type=="UNKNOWN") {dao.getObjectType(ownerName, objectName)} else {type} + if (parts.size == 5) { + val procedureName = parts.get(4) + val source = dao.getSource(ownerName, objectType, objectName).trim + val parser = new UtplsqlParser(source) + line = parser.getLineOf(procedureName) + } + openEditor(ownerName, objectType, objectName.toUpperCase, line, 1) + } + + private def openEditor(String owner, String type, String name, int line, int col) { + var drillLink = new DefaultDrillLink + drillLink.connName = currentRun.connectionName + // argument order is based on SQLDEV:LINK that can be used in SQL query result tables (editors, reports) + drillLink.args = #[owner, type, name, String.valueOf(line), String.valueOf(col), "OpenEditor", "oracle.dbtools.raptor.controls.grid.DefaultDrillLink"] + drillLink.performDrill + } + + private def syncDetailTab() { + if (syncDetailTabCheckBoxMenuItem.selected) { + val rowIndex = testOverviewTable.selectedRow + if (rowIndex != -1) { + val row = testOverviewTable.convertRowIndexToModel(rowIndex) + val test = testOverviewTableModel.getTest(row) + var int tabIndex + if (test.counter?.failure !== null && test.counter.failure > 0) { + tabIndex = 1 + } else if (test.counter?.error !== null && test.counter.error > 0) { + tabIndex = 2 + } else if (test.counter?.warning !== null && test.counter.warning > 0) { + tabIndex = 3 + } else if (test.serverOutput !== null && test.serverOutput.length > 0) { + tabIndex = 4 + } else { + tabIndex = 0 + } + testDetailTabbedPane.selectedIndex = tabIndex + } + } + } + + private def getPreferenceModel() { + var PreferenceModel preferences + try { + preferences = PreferenceModel.getInstance(Preferences.preferences) + } catch (NoClassDefFoundError e) { + preferences = PreferenceModel.getInstance(null) + } + return preferences + } + + private def applyPreferences() { + val PreferenceModel preferences = preferenceModel + applyShowNumberOfRunsInHistory(preferences.numberOfRunsInHistory) + showDisabledCounterCheckBoxMenuItem.selected = preferences.showDisabledCounter + applyShowDisabledCounter(showDisabledCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem) + showWarningsCounterCheckBoxMenuItem.selected = preferences.showWarningsCounter + applyShowWarningsCounter(showWarningsCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem) + showInfoCounterCheckBoxMenuItem.selected = preferences.showInfoCounter + applyShowInfoCounter(showInfoCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem) + showTestDescriptionCheckBoxMenuItem.selected = preferences.showTestDescription + applyShowTestDescription(showTestDescriptionCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem) + showWarningIndicatorCheckBoxMenuItem.selected = preferences.showWarningIndicator + applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem) + showInfoIndicatorCheckBoxMenuItem.selected = preferences.showInfoIndicator + applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem) + syncDetailTabCheckBoxMenuItem.selected = preferences.syncDetailTab + fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem) + } + + def setModel(Run run) { + runs.put(run.reporterId, run) + refreshRunsComboBox + setCurrentRun(run) + } + + private def setCurrentRun(Run run) { + if (run !== currentRun) { + currentRun = run + testOverviewTableModel.setModel(run.tests, showTestDescriptionCheckBoxMenuItem.selected) + resetDerived + val item = new ComboBoxItem(currentRun.reporterId, currentRun.name) + runComboBox.selectedItem = item + } + } + + def synchronized update(String reporterId) { + setCurrentRun(runs.get(reporterId)) + val row = currentRun.currentTestNumber - 1 + val header = testOverviewTableModel.testIdColumnName + val idColumn = testOverviewTable.columnModel.getColumn(3) + if (idColumn.headerValue != header) { + idColumn.headerValue = header + testOverviewTable.tableHeader.repaint + } + if (row < 0) { + testOverviewTableModel.fireTableDataChanged + } else { + if (testOverviewTableModel.rowCount > row) { + testOverviewTableModel.fireTableRowsUpdated(row, row) + val positionOfCurrentTest = testOverviewTable.getCellRect(row, 0, true); + testOverviewTable.scrollRectToVisible = positionOfCurrentTest + } + } + statusLabel.text = currentRun.status + testCounterValueLabel.text = '''«currentRun.totalNumberOfCompletedTests»«IF currentRun.totalNumberOfTests >= 0»/«currentRun.totalNumberOfTests»«ENDIF»''' + errorCounterValueLabel.text = '''«currentRun.counter.error»''' + failureCounterValueLabel.text = '''«currentRun.counter.failure»''' + disabledCounterValueLabel.text = '''«currentRun.counter.disabled»''' + warningsCounterValueLabel.text = '''«currentRun.counter.warning»''' + infoCounterValueLabel.text = '''«currentRun.infoCount»''' + if (currentRun.totalNumberOfTests == 0) { + progressBar.value = 100 + } else { + progressBar.value = Math.round(100 * currentRun.totalNumberOfCompletedTests / currentRun.totalNumberOfTests) + } + if (currentRun.counter.error > 0 || currentRun.counter.failure > 0) { + progressBar.foreground = RED + } else { + progressBar.foreground = GREEN + } + } + + private def getPathListFromSelectedTests() { + val pathList = new ArrayList + for (rowIndex : testOverviewTable.selectedRows) { + val row = testOverviewTable.convertRowIndexToModel(rowIndex) + val test = testOverviewTableModel.getTest(row) + val path = '''«test.ownerName».«test.objectName».«test.procedureName»''' + pathList.add(path) + } + return pathList + } + + private def isWindowsLookAndFeel() { + val laf = UIManager.lookAndFeel?.name + if (laf == "Windows") { + return true + } else { + return false + } + } + + private def isMacLookAndFeel() { + val laf = UIManager.lookAndFeel?.name + if (laf == "Mac OS X") { + return true + } else { + return false + } + } + + private def void fixCheckBoxMenuItem(JCheckBoxMenuItem item) { + if (windowsLookAndFeel) { + if (item.selected) { + item.icon = UtplsqlResources.getIcon("CHECKMARK_ICON") + } else { + item.icon = null + } + } + } + + override actionPerformed(ActionEvent e) { + if (e.source == refreshButton) { + resetDerived + testDetailTabbedPane.selectedIndex = 0 + testOverviewTableModel.fireTableDataChanged + } else if (e.source == rerunButton) { + val runner = new UtplsqlRunner(currentRun.pathList, currentRun.connectionName) + runner.runTestAsync + } else if (e.source == rerunWorksheetButton) { + val worksheet = new UtplsqlWorksheetRunner(currentRun.pathList, currentRun.connectionName) + worksheet.runTestAsync + } else if (e.source == runComboBox) { + if (currentRun !== null) { + val comboBoxItem = runComboBox.selectedItem as ComboBoxItem + if (currentRun.reporterId != comboBoxItem.key) { + update(comboBoxItem.key) + testDetailTabbedPane.selectedIndex = 0 + } + } + } else if (e.source == clearButton) { + val run = currentRun + runs.clear + currentRun = null + setModel(run) + update(run.reporterId) + } else if (e.source == testOverviewRunMenuItem) { + val runner = new UtplsqlRunner(pathListFromSelectedTests, currentRun.connectionName) + runner.runTestAsync + } else if (e.source == testOverviewRunWorksheetMenuItem) { + val worksheet = new UtplsqlWorksheetRunner(pathListFromSelectedTests, currentRun.connectionName) + worksheet.runTestAsync + } else if (e.source == showDisabledCounterCheckBoxMenuItem) { + applyShowDisabledCounter(showDisabledCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem) + } else if (e.source == showWarningsCounterCheckBoxMenuItem) { + applyShowWarningsCounter( showWarningsCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem) + } else if (e.source == showInfoCounterCheckBoxMenuItem) { + applyShowInfoCounter(showInfoCounterCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem) + } else if (e.source == showTestDescriptionCheckBoxMenuItem) { + applyShowTestDescription(showTestDescriptionCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem) + } else if (e.source == showWarningIndicatorCheckBoxMenuItem) { + applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem) + } else if (e.source == showInfoIndicatorCheckBoxMenuItem) { + applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.selected) + fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem) + } else if (e.source == syncDetailTabCheckBoxMenuItem) { + syncDetailTab + fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem) + } + } + + override mouseClicked(MouseEvent e) { + if (e.clickCount == 2) { + if (e.source == testOverviewTable) { + if (failuresTable.selectedRowCount == 1) { + openSelectedFailure + } else { + openSelectedTest + } + + } else if (e.source == failuresTable) { + if (failuresTable.selectedRowCount == 1) { + openSelectedFailure + } + } + } + } + + override mouseEntered(MouseEvent e) { + } + + override mouseExited(MouseEvent e) { + } + + override mousePressed(MouseEvent e) { + } + + override mouseReleased(MouseEvent e) { + } + + override hyperlinkUpdate(HyperlinkEvent e) { + if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) { + val link = e.description + openLink(link) + } + } + + private static def formatDateTime(String dateTime) { + if (dateTime === null) { + return null + } else { + if (dateTime.length == 26) { + return dateTime.replace("T", " ").substring(0, 23) + } else { + return dateTime + } + } + } + + static class TestOverviewRowListener implements ListSelectionListener { + RunnerPanel p + + new (RunnerPanel p) { + this.p = p + } + + override void valueChanged(ListSelectionEvent event) { + val rowIndex = p.testOverviewTable.selectedRow + if (rowIndex != -1) { + val row = p.testOverviewTable.convertRowIndexToModel(rowIndex) + val test = p.testOverviewTableModel.getTest(row) + p.testOwnerTextField.text = test.ownerName + p.testPackageTextField.text = test.objectName + p.testProcedureTextField.text = test.procedureName + p.testDescriptionTextArea.text = test.description?.trim + p.testIdTextArea.text = test.id + p.testStartTextField.text = formatDateTime(test.startTime) + p.failuresTableModel.model = test.failedExpectations + p.failuresTableModel.fireTableDataChanged + p.testFailureMessageTextPane.text = null + if (test.failedExpectations !== null && test.failedExpectations.size > 0) { + p.failuresTable.setRowSelectionInterval(0, 0) + } + p.testErrorStackTextPane.text = p.getHtml(test.errorStack?.trim) + p.testWarningsTextPane.text = p.getHtml(test.warnings?.trim) + p.testServerOutputTextPane.text = p.getHtml(test.serverOutput?.trim) + p.syncDetailTab + p.testOverviewRunMenuItem.enabled = true + p.testOverviewRunWorksheetMenuItem.enabled = true + } + } + } + + private def getLinkedAndFormattedText(String text) { + if (text === null) { + return "" + } + // Patterns (primarily Asserts, Errors, ServerOutput): + // at "OWNER.PACKAGE.PROCEDURE", line 42 + // at "OWNER.PROCEDURE", line 42 + // at "OWNER.PACKAGE", line 42 + // at package "OWNER.PACKAGE", line 42 + val p1 = Pattern.compile('''\s+(package\s+)?("(\S+?)\.(\S+?)(?:\.(\S+?))?",\s+line\s+([0-9]+))''') + var localText = HtmlUtils.htmlEscape(text) + var m = p1.matcher(localText) + while(m.find) { + val link = '''«m.group(2)»''' + val start = m.start(2) + val end = m.end(2) + localText = '''«localText.substring(0, start)»«link»«localText.substring(end)»''' + m = p1.matcher(localText) + } + // Patterns (primarily Warnings, without line reference, calculate when opening link): + // owner.package.procedure + val p2 = Pattern.compile('''^\s{2}((\S+?)\.(\S+?)\.(\S+?))$''', Pattern.MULTILINE) + m = p2.matcher(localText) + while(m.find) { + val link = '''  Â«m.group(1)»''' + val start = m.start(0) + val end = m.end(0) + localText = '''«localText.substring(0, start)»«link»«localText.substring(end)»''' + m = p2.matcher(localText) + } + // Patterns (Title for warning/info on suite level) + // from suite a.junit_utplsql_test1_pkg: + val p3 = Pattern.compile('''^For suite ([^:]+):$''', Pattern.MULTILINE) + m = p3.matcher(localText) + while(m.find) { + val title = '''For suite "«m.group(1)»"''' + val start = m.start(0) + val end = m.end(0) + localText = '''«localText.substring(0, start)»«title»«localText.substring(end)»''' + m = p3.matcher(localText) + } + val result = ''' + «FOR p : localText.split("\n")» +

«p»

+ «ENDFOR» + ''' + return result + } + + static class FailuresRowListener implements ListSelectionListener { + RunnerPanel p + + new (RunnerPanel p) { + this.p = p + } + + override void valueChanged(ListSelectionEvent event) { + val rowIndex = p.failuresTable.selectedRow + if (rowIndex != -1) { + val row = p.failuresTable.convertRowIndexToModel(rowIndex) + val expectation = p.failuresTableModel.getExpectation(row) + val html = p.getHtml(expectation.failureText) + p.testFailureMessageTextPane.text = html + + } + } + } + + static class TimeFormatRenderer extends DefaultTableCellRenderer { + static val DecimalFormat formatter = new DecimalFormat("#,##0.000") + + override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int col) { + val renderedValue = if (value === null) {null} else {formatter.format(value as Number)} + return super.getTableCellRendererComponent(table, renderedValue, isSelected, hasFocus, row, col) + } + } + + static class TestTableHeaderRenderer extends DefaultTableCellRenderer { + override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int col) { + val renderer = table.tableHeader.defaultRenderer + val label = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col) as JLabel + if (col === 0) { + label.icon = UtplsqlResources.getIcon("STATUS_ICON") + label.horizontalAlignment = JLabel.CENTER + } else if (col === 1) { + label.icon = UtplsqlResources.getIcon("WARNING_ICON") + label.horizontalAlignment = JLabel.CENTER + } else if (col === 2) { + label.icon = UtplsqlResources.getIcon("INFO_ICON") + label.horizontalAlignment = JLabel.CENTER + } else if (col === 3) { + label.icon = null + label.horizontalAlignment = JLabel.LEFT + } else if (col === 4) { + label.icon = null + label.horizontalAlignment = JLabel.RIGHT + } + return label + } + } + + static class FailuresTableHeaderRenderer extends DefaultTableCellRenderer { + override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int col) { + val renderer = table.tableHeader.defaultRenderer + val label = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col) as JLabel + if (col === 0) { + label.horizontalAlignment = JLabel.RIGHT + } else { + label.horizontalAlignment = JLabel.LEFT + } + return label + } + } + + private def makeLabelledCounterComponent (JLabel label, JComponent comp) { + val groupPanel = new JPanel + groupPanel.layout = new GridBagLayout + var GridBagConstraints c = new GridBagConstraints + // label + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 5, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + groupPanel.add(label, c) + // component + c.gridx = 1 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 5, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + groupPanel.add(comp, c) + val dim = new Dimension(134, 24) + groupPanel.minimumSize = dim + groupPanel.preferredSize = dim + return groupPanel + } + + private def initializeGUI() { + // Base panel containing all components + basePanel = new JPanel() + basePanel.setLayout(new GridBagLayout()) + var GridBagConstraints c = new GridBagConstraints() + + // Toolbar + var toolbar = new GradientToolbar + toolbar.floatable = false + val buttonBorder = new EmptyBorder(new Insets(2, 4, 2, 4)) // top, left, bottom, right + refreshButton = new ToolbarButton(UtplsqlResources.getIcon("REFRESH_ICON")) + refreshButton.toolTipText = UtplsqlResources.getString("RUNNER_REFRESH_TOOLTIP") + refreshButton.border = buttonBorder + refreshButton.addActionListener(this) + toolbar.add(refreshButton) + rerunButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_ICON")) + rerunButton.toolTipText = UtplsqlResources.getString("RUNNER_RERUN_TOOLTIP") + rerunButton.border = buttonBorder + rerunButton.addActionListener(this) + toolbar.add(rerunButton) + rerunWorksheetButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")) + rerunWorksheetButton.toolTipText = UtplsqlResources.getString("RUNNER_RERUN_WORKSHEET_TOOLTIP") + rerunWorksheetButton.border = buttonBorder + rerunWorksheetButton.addActionListener(this) + toolbar.add(rerunWorksheetButton) + toolbar.add(Box.createHorizontalGlue()) + runComboBoxModel = new DefaultComboBoxModel>; + runComboBox = new JComboBox>(runComboBoxModel); + runComboBox.editable = false + val comboBoxDim = new Dimension(500, 50) + runComboBox.maximumSize = comboBoxDim + runComboBox.addActionListener(this) + toolbar.add(runComboBox) + clearButton = new ToolbarButton(UtplsqlResources.getIcon("CLEAR_ICON")) + clearButton.toolTipText = UtplsqlResources.getString("RUNNER_CLEAR_HISTORY_BUTTON") + clearButton.border = buttonBorder + clearButton.addActionListener(this) + toolbar.add(clearButton) + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::NORTH + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + basePanel.add(toolbar, c) + + // Status line + statusLabel = new JLabel + c.gridx = 0 + c.gridy = 1 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + basePanel.add(statusLabel, c) + + // Counters + // - Test counter + val counterPanel = new JPanel + counterPanel.layout = new WrapLayout(FlowLayout.LEFT, 0, 0) + val testCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_TESTS_LABEL") + ":", JLabel::LEADING) + testCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(testCounterLabel, testCounterValueLabel)) + // - Failure counter + val failureCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_FAILURES_LABEL") + ":", + UtplsqlResources.getIcon("FAILURE_ICON"), JLabel::LEADING) + failureCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(failureCounterLabel,failureCounterValueLabel)) + // - Error counter + val errorCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_ERRORS_LABEL") + ":", + UtplsqlResources.getIcon("ERROR_ICON"), JLabel::LEADING) + errorCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(errorCounterLabel, errorCounterValueLabel)) + // - Disabled counter + val disabledCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_DISABLED_LABEL") + ":", + UtplsqlResources.getIcon("DISABLED_ICON"), JLabel::LEADING) + disabledCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(disabledCounterLabel, disabledCounterValueLabel)) + // - Warnings counter + val warningsCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_WARNINGS_LABEL") + ":", + UtplsqlResources.getIcon("WARNING_ICON"), JLabel::LEADING) + warningsCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(warningsCounterLabel, warningsCounterValueLabel)) + // - Info counter + val infoCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_INFO_LABEL") + ":", + UtplsqlResources.getIcon("INFO_ICON"), JLabel::LEADING) + infoCounterValueLabel = new JLabel + counterPanel.add(makeLabelledCounterComponent(infoCounterLabel, infoCounterValueLabel)) + // - add everything to basePanel + c.gridx = 0 + c.gridy = 2 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 0, 5, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + basePanel.add(counterPanel,c) + + // Context menu for counters panel + val countersPopupMenu = new JPopupMenu + showDisabledCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL").replace("?",""), true) + showDisabledCounterCheckBoxMenuItem.addActionListener(this) + countersPopupMenu.add(showDisabledCounterCheckBoxMenuItem) + showWarningsCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL").replace("?",""), true) + showWarningsCounterCheckBoxMenuItem.addActionListener(this) + countersPopupMenu.add(showWarningsCounterCheckBoxMenuItem) + showInfoCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL").replace("?",""), true) + showInfoCounterCheckBoxMenuItem.addActionListener(this) + countersPopupMenu.add(showInfoCounterCheckBoxMenuItem) + counterPanel.componentPopupMenu = countersPopupMenu + + // Progress bar + progressBar = new JProgressBar + val progressBarDim = new Dimension(10, 20) + progressBar.preferredSize = progressBarDim + progressBar.minimumSize = progressBarDim + progressBar.stringPainted = false + progressBar.foreground = GREEN + progressBar.UI = new BasicProgressBarUI + c.gridx = 0 + c.gridy = 3 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + basePanel.add(progressBar, c) + + // Test overview + testOverviewTableModel = new TestOverviewTableModel + testOverviewTable = new JTable(testOverviewTableModel) + testOverviewTable.tableHeader.reorderingAllowed = false + testOverviewTable.autoCreateRowSorter = true + testOverviewTable.rowHeight = OVERVIEW_TABLE_ROW_HEIGHT + testOverviewTable.tableHeader.preferredSize = new Dimension(testOverviewTable.tableHeader.getPreferredSize.width, OVERVIEW_TABLE_ROW_HEIGHT) + testOverviewTable.selectionModel.addListSelectionListener(new TestOverviewRowListener(this)) + testOverviewTable.addMouseListener(this) + val testTableHeaderRenderer = new TestTableHeaderRenderer + val overviewTableStatus = testOverviewTable.columnModel.getColumn(0) + overviewTableStatus.minWidth = INDICATOR_WIDTH + overviewTableStatus.preferredWidth = INDICATOR_WIDTH + overviewTableStatus.maxWidth = INDICATOR_WIDTH + overviewTableStatus.headerRenderer = testTableHeaderRenderer + val overviewTableWarning = testOverviewTable.columnModel.getColumn(1) + overviewTableWarning.minWidth = INDICATOR_WIDTH + overviewTableWarning.preferredWidth = INDICATOR_WIDTH + overviewTableWarning.maxWidth = INDICATOR_WIDTH + overviewTableWarning.headerRenderer = testTableHeaderRenderer + val overviewTableInfo = testOverviewTable.columnModel.getColumn(2) + overviewTableInfo.minWidth = INDICATOR_WIDTH + overviewTableInfo.preferredWidth = INDICATOR_WIDTH + overviewTableInfo.maxWidth = INDICATOR_WIDTH + overviewTableInfo.headerRenderer = testTableHeaderRenderer + val overviewTableId = testOverviewTable.columnModel.getColumn(3) + overviewTableId.headerRenderer = testTableHeaderRenderer + val overviewTableTime = testOverviewTable.columnModel.getColumn(4) + overviewTableTime.preferredWidth = 60 + overviewTableTime.maxWidth = 100 + overviewTableTime.headerRenderer = testTableHeaderRenderer + val timeFormatRenderer = new TimeFormatRenderer + timeFormatRenderer.horizontalAlignment = JLabel.RIGHT + overviewTableTime.cellRenderer = timeFormatRenderer + val testOverviewScrollPane = new JScrollPane(testOverviewTable) + + // Context menu for test overview + val testOverviewPopupMenu = new JPopupMenu + testOverviewRunMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_MENUITEM"), UtplsqlResources.getIcon("RUN_ICON")); + testOverviewRunMenuItem.addActionListener(this) + testOverviewPopupMenu.add(testOverviewRunMenuItem) + testOverviewRunWorksheetMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_WORKSHEET_MENUITEM"), UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")); + testOverviewRunWorksheetMenuItem.addActionListener(this) + testOverviewPopupMenu.add(testOverviewRunWorksheetMenuItem) + testOverviewPopupMenu.add(new JSeparator) + showTestDescriptionCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL").replace("?",""), true) + showTestDescriptionCheckBoxMenuItem.addActionListener(this) + testOverviewPopupMenu.add(showTestDescriptionCheckBoxMenuItem) + showWarningIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL").replace("?",""), true) + showWarningIndicatorCheckBoxMenuItem.addActionListener(this) + testOverviewPopupMenu.add(showWarningIndicatorCheckBoxMenuItem) + showInfoIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL").replace("?",""), true) + showInfoIndicatorCheckBoxMenuItem.addActionListener(this) + testOverviewPopupMenu.add(showInfoIndicatorCheckBoxMenuItem) + syncDetailTabCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL").replace("?",""), true) + syncDetailTabCheckBoxMenuItem.addActionListener(this) + testOverviewPopupMenu.add(syncDetailTabCheckBoxMenuItem) + testOverviewTable.componentPopupMenu = testOverviewPopupMenu + + // Test tabbed pane (Test Properties) + val testInfoPanel = new ScrollablePanel + testInfoPanel.setLayout(new GridBagLayout()) + // - Owner + val testOwnerLabel = new JLabel(UtplsqlResources.getString("RUNNER_OWNER_LABEL")) + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 10, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testOwnerLabel, c) + testOwnerTextField = new RunnerTextField + testOwnerTextField.editable = false + c.gridx = 1 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testOwnerTextField, c) + // - Package + val testPackageLabel = new JLabel(UtplsqlResources.getString("RUNNER_PACKAGE_LABEL")) + c.gridx = 0 + c.gridy = 1 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testPackageLabel, c) + testPackageTextField = new RunnerTextField + testPackageTextField.editable = false + c.gridx = 1 + c.gridy = 1 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testPackageTextField, c) + // - Procedure + val testProcedureLabel = new JLabel(UtplsqlResources.getString("RUNNER_PROCEDURE_LABEL")) + c.gridx = 0 + c.gridy = 2 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testProcedureLabel, c) + testProcedureTextField = new RunnerTextField + testProcedureTextField.editable = false + c.gridx = 1 + c.gridy = 2 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testProcedureTextField, c) + // - Description + val testDescriptionLabel = new JLabel(UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")) + testDescriptionLabel.border = BorderFactory.createEmptyBorder(if (macLookAndFeel) {5} else {3}, 0, 0, 0) + c.gridx = 0 + c.gridy = 3 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::NORTHWEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testDescriptionLabel, c) + testDescriptionTextArea = new RunnerTextArea + testDescriptionTextArea.editable = false + testDescriptionTextArea.enabled = true + testDescriptionTextArea.lineWrap = true + testDescriptionTextArea.wrapStyleWord = true + c.gridx = 1 + c.gridy = 3 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testDescriptionTextArea, c) + // - Suitepath (id) + val testIdLabel = new JLabel(UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN")) + testIdLabel.border = BorderFactory.createEmptyBorder(if (macLookAndFeel) {5} else {3}, 0, 0, 0) + c.gridx = 0 + c.gridy = 4 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::NORTHWEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testIdLabel, c) + testIdTextArea = new RunnerTextArea + testIdTextArea.editable = false + testIdTextArea.enabled = true + testIdTextArea.lineWrap = true + testIdTextArea.wrapStyleWord = false + c.gridx = 1 + c.gridy = 4 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testIdTextArea, c) + // - Start + val testStartLabel = new JLabel(UtplsqlResources.getString("RUNNER_START_LABEL")) + c.gridx = 0 + c.gridy = 5 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 10, 10, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::NONE + c.weightx = 0 + c.weighty = 0 + testInfoPanel.add(testStartLabel, c) + testStartTextField = new RunnerTextField + testStartTextField.editable = false + c.gridx = 1 + c.gridy = 5 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(5, 5, 10, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::HORIZONTAL + c.weightx = 1 + c.weighty = 0 + testInfoPanel.add(testStartTextField, c) + // - Vertical spring and scrollbar for info panel + c.gridx = 0 + c.gridy = 6 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 0 + c.weighty = 1 + testInfoPanel.add(Box.createVerticalGlue(), c) + val testPropertiesScrollPane = new JScrollPane(testInfoPanel) + + // Failures tabbed pane (failed expectations) + // - failures table (number and description) + failuresTableModel = new FailuresTableModel + failuresTable = new JTable(failuresTableModel) + failuresTable.tableHeader.reorderingAllowed = false + failuresTable.selectionModel.addListSelectionListener(new FailuresRowListener(this)) + failuresTable.addMouseListener(this) + val failuresTableHeaderRenderer = new FailuresTableHeaderRenderer + val failuresTableNumber = failuresTable.columnModel.getColumn(0) + failuresTableNumber.headerRenderer = failuresTableHeaderRenderer + failuresTableNumber.preferredWidth = 30 + failuresTableNumber.maxWidth = 30 + val failuresDescription = failuresTable.columnModel.getColumn(1) + failuresDescription.headerRenderer = failuresTableHeaderRenderer + val failuresTableScrollPane = new JScrollPane(failuresTable) + // - failures details + testFailureMessageTextPane = new RunnerTextPane + testFailureMessageTextPane.editable = false + testFailureMessageTextPane.enabled = true + testFailureMessageTextPane.contentType = "text/html" + testFailureMessageTextPane.minimumSize = TEXTPANE_DIM + testFailureMessageTextPane.preferredSize = TEXTPANE_DIM + testFailureMessageTextPane.addHyperlinkListener(this) + val testFailureMessageScrollPane = new JScrollPane(testFailureMessageTextPane) + c.gridx = 1 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 5, 0, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 1 + c.weighty = 6 + + // - split pane + val failuresSplitPane = new JSplitPane(SwingConstants.HORIZONTAL, failuresTableScrollPane, testFailureMessageScrollPane) + failuresSplitPane.resizeWeight = 0.2 + + // Errors tabbed pane (Error Stack) + val testErrorStackPanel = new JPanel + testErrorStackPanel.setLayout(new GridBagLayout()) + testErrorStackTextPane = new RunnerTextPane + testErrorStackTextPane.editable = false + testErrorStackTextPane.enabled = true + testErrorStackTextPane.contentType = "text/html" + testErrorStackTextPane.minimumSize = TEXTPANE_DIM + testErrorStackTextPane.preferredSize = TEXTPANE_DIM + testErrorStackTextPane.addHyperlinkListener(this) + val testErrorStackScrollPane = new JScrollPane(testErrorStackTextPane) + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 1 + c.weighty = 1 + testErrorStackPanel.add(testErrorStackScrollPane, c) + + // Warnings tabbed pane + val testWarningsPanel = new JPanel + testWarningsPanel.setLayout(new GridBagLayout()) + testWarningsTextPane = new RunnerTextPane + testWarningsTextPane.editable = false + testWarningsTextPane.enabled = true + testWarningsTextPane.contentType = "text/html" + testWarningsTextPane.minimumSize = TEXTPANE_DIM + testWarningsTextPane.preferredSize = TEXTPANE_DIM + testWarningsTextPane.addHyperlinkListener(this) + val testWarningsScrollPane = new JScrollPane(testWarningsTextPane) + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 1 + c.weighty = 1 + testWarningsPanel.add(testWarningsScrollPane, c) + + // Info tabbed pane (Server Output) + val testServerOutputPanel = new JPanel + testServerOutputPanel.setLayout(new GridBagLayout()) + testServerOutputTextPane = new RunnerTextPane + testServerOutputTextPane.editable = false + testServerOutputTextPane.enabled = true + testServerOutputTextPane.contentType = "text/html" + testServerOutputTextPane.minimumSize = TEXTPANE_DIM + testServerOutputTextPane.preferredSize = TEXTPANE_DIM + testServerOutputTextPane.addHyperlinkListener(this) + val testServerOutputScrollPane = new JScrollPane(testServerOutputTextPane) + c.gridx = 0 + c.gridy = 0 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 1 + c.weighty = 1 + testServerOutputPanel.add(testServerOutputScrollPane, c) + + // split pane with all tabs + testDetailTabbedPane = new JTabbedPane() + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_TEST_TAB_LABEL"), testPropertiesScrollPane) + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_FAILURES_TAB_LABEL"), failuresSplitPane) + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_ERRORS_TAB_LABEL"), testErrorStackPanel) + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_WARNINGS_TAB_LABEL"), testWarningsPanel) + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_INFO_TAB_LABEL"), testServerOutputPanel) + val horizontalSplitPane = new JSplitPane(SwingConstants.HORIZONTAL, testOverviewScrollPane, testDetailTabbedPane) + horizontalSplitPane.resizeWeight = 0.5 + c.gridx = 0 + c.gridy = 4 + c.gridwidth = 1 + c.gridheight = 1 + c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right + c.anchor = GridBagConstraints::WEST + c.fill = GridBagConstraints::BOTH + c.weightx = 1 + c.weighty = 1 + basePanel.add(horizontalSplitPane, c) + + // fix borders (colors, margins) + if (macLookAndFeel) { + val border = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(3, 3, 3, 3), + BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(219, 219, 219)), + BorderFactory.createEmptyBorder(1, 1, 1, 1) + ) + ) + testDescriptionTextArea.border = border + testIdTextArea.border = border + } else { + val referenceBorder = testOwnerTextField.border + testDescriptionTextArea.border = referenceBorder + testIdTextArea.border = referenceBorder + } + } + +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend new file mode 100644 index 00000000..be9520c7 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend @@ -0,0 +1,40 @@ +package org.utplsql.sqldev.ui.runner + +import java.awt.Graphics +import java.awt.event.FocusEvent +import java.awt.event.FocusListener +import javax.swing.JTextArea +import javax.swing.UIManager + +class RunnerTextArea extends JTextArea implements FocusListener{ + + new() { + super() + this.addFocusListener = this + } + + override paintComponent(Graphics g) { + // default for non-opaque components + if (!opaque) { + super.paintComponent(g) + return + } + + // use value of JTextField for consistency + g.color = UIManager.getColor("TextField.inactiveBackground") + g.fillRect(3, 3, width - 6, height - 6) + + // do rest, changing opaque to ensure background is not overwritten + setOpaque(false) + super.paintComponent(g) + setOpaque(true) + } + + override void focusGained(FocusEvent e) { + this.caret.visible = true + } + + override focusLost(FocusEvent e) { + this.caret.visible = false + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend new file mode 100644 index 00000000..65e326ea --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend @@ -0,0 +1,39 @@ +package org.utplsql.sqldev.ui.runner + +import java.awt.Graphics +import java.awt.event.FocusEvent +import java.awt.event.FocusListener +import javax.swing.JTextField +import javax.swing.UIManager + +class RunnerTextField extends JTextField implements FocusListener { + + new() { + super() + this.addFocusListener = this + } + + override paintComponent(Graphics g) { + // default for non-opaque components + if (!opaque) { + super.paintComponent(g) + return + } + + g.color = UIManager.getColor("TextField.inactiveBackground") + g.fillRect(0, 0, width, height) + + // do rest, changing opaque to ensure background is not overwritten + setOpaque(false) + super.paintComponent(g) + setOpaque(true) + } + + override void focusGained(FocusEvent e) { + this.caret.visible = true + } + + override focusLost(FocusEvent e) { + this.caret.visible = false + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend new file mode 100644 index 00000000..e3e4c663 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.awt.Graphics +import java.awt.event.FocusEvent +import java.awt.event.FocusListener +import javax.swing.JTextPane +import javax.swing.UIManager + +class RunnerTextPane extends JTextPane implements FocusListener{ + + new() { + super() + this.addFocusListener = this + } + + override paintComponent(Graphics g) { + // default for non-opaque components + if (!opaque) { + super.paintComponent(g) + return + } + + // use value of JTextField for consistency + g.color = UIManager.getColor("TextField.inactiveBackground") + g.fillRect(0, 0, width, height) + + // do rest, changing opaque to ensure background is not overwritten + setOpaque(false) + super.paintComponent(g) + setOpaque(true) + } + + override void focusGained(FocusEvent e) { + this.caret.visible = true + } + + override focusLost(FocusEvent e) { + this.caret.visible = false + } + + override setText(String t) { + super.setText(t) + // ensure left parts of long lines are always visible + caretPosition = 0 + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend new file mode 100644 index 00000000..49050d4a --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import oracle.ide.docking.DockableWindow +import oracle.ide.layout.ViewId +import org.utplsql.sqldev.resources.UtplsqlResources + +class RunnerView extends DockableWindow { + static val VIEW_NAME = "UTPLSQL_RUNNER_VIEW" + public static val ViewId VIEW_ID = new ViewId(RunnerFactory.FACTORY_NAME, VIEW_NAME) + var RunnerPanel panel + + override getTitleName() { + return UtplsqlResources.getString("RUNNER_VIEW_TITLE") + } + + override getGUI() { + if (panel === null) { + panel = new RunnerPanel + } + return panel.getGUI() + } + + override getTabIcon() { + return UtplsqlResources.getIcon("UTPLSQL_ICON") + } + + def getRunnerPanel() { + getGUI() + return panel + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend new file mode 100644 index 00000000..fb834008 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.awt.Rectangle +import javax.swing.JPanel +import javax.swing.Scrollable + +/* + * Fixes resizing issues of JTextArea when put into JPanel and JPanel into JScrollPane + * Solution is based on https://stackoverflow.com/questions/15783014/jtextarea-on-jpanel-inside-jscrollpane-does-not-resize-properly/15786939 + */ +class ScrollablePanel extends JPanel implements Scrollable { + + override getPreferredScrollableViewportSize() { + return super.getPreferredSize() + } + + override getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 0 + } + + override getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return 0 + } + + override getScrollableTracksViewportWidth() { + return true + } + + override getScrollableTracksViewportHeight() { + return false + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.xtend new file mode 100644 index 00000000..55c42d8a --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.xtend @@ -0,0 +1,152 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.util.LinkedHashMap +import javax.swing.Icon +import javax.swing.table.DefaultTableModel +import org.utplsql.sqldev.model.PrefixTools +import org.utplsql.sqldev.model.runner.Test +import org.utplsql.sqldev.resources.UtplsqlResources + +class TestOverviewTableModel extends DefaultTableModel { + LinkedHashMap tests + String commonPrefix + boolean commonPrefixCalculated + boolean showDescription + + new() { + super() + } + + private def calcCommonPrefix() { + if (!commonPrefixCalculated && tests !== null && tests.size > 0) { + this.commonPrefix = PrefixTools.commonPrefix(tests.keySet.toList) + fireTableDataChanged() + commonPrefixCalculated = true + } + } + + def setModel(LinkedHashMap tests, boolean showDescription) { + commonPrefixCalculated = false + this.tests = tests + this.showDescription = showDescription + calcCommonPrefix + fireTableDataChanged() + } + + def updateModel(boolean showDescription) { + this.showDescription = showDescription + fireTableDataChanged() + } + + def getTestIdColumnName() { + calcCommonPrefix + if (commonPrefix === null || commonPrefix == "") { + if (showDescription) { + UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL") + } else { + UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN") + } + } else { + if (showDescription) { + '''«UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")» («commonPrefix»)''' + } else { + commonPrefix + } + } + } + + def getTest(int row) { + val entry = tests.entrySet.get(row) + val test = tests.get(entry.key) + return test + } + + override getRowCount() { + if (tests === null) { + return 0 + } + return tests.size() + } + + override getColumnCount() { + return 5 + } + + override getValueAt(int row, int col) { + val test = tests.entrySet.get(row).value + if (test === null) { + return null + } + switch (col) { + case 0: { + return test.statusIcon + } + case 1: { + return test.warningIcon + } + case 2: { + return test.infoIcon + } + case 3: { + return if(showDescription && test.description !== null) { + test.description + } else { + test.id.substring(if(commonPrefix === null) {0} else {commonPrefix.length}) + } + } + case 4: { + return test.executionTime + } + default: { + return null + } + } + } + + override getColumnName(int col) { + return #["", "", "", UtplsqlResources.getString(if (showDescription) {"RUNNER_DESCRIPTION_LABEL"} else {"RUNNER_TEST_ID_COLUMN"}), + UtplsqlResources.getString("RUNNER_TEST_EXECUTION_TIME_COLUMN")].get(col) + } + + override isCellEditable(int row, int column) { + return false + } + + override getColumnClass(int col) { + switch (col) { + case 0: { + return Icon + } + case 1: { + return Icon + } + case 2: { + return Icon + } + case 3: { + return String + } + case 4: { + return Double + } + default: { + return String + } + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend new file mode 100644 index 00000000..dce4695c --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend @@ -0,0 +1,168 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.ui.runner + +import java.awt.Component +import java.awt.Container +import java.awt.Dimension +import java.awt.FlowLayout +import java.awt.Insets +import javax.swing.JScrollPane +import javax.swing.SwingUtilities + +/** + * FlowLayout subclass that fully supports wrapping of components. + * Converted to Xtend based on http://www.camick.com/java/source/WrapLayout.java + */ +class WrapLayout extends FlowLayout { + + /** + * Constructs a new WrapLayout with a left + * alignment and a default 5-unit horizontal and vertical gap. + */ + new() { + super() + } + + /** + * Constructs a new FlowLayout with the specified + * alignment and a default 5-unit horizontal and vertical gap. + * The value of the alignment argument must be one of + * WrapLayout, WrapLayout, + * or WrapLayout. + * @param align the alignment value + */ + new(int align) { + super(align) + } + + /** + * Creates a new flow layout manager with the indicated alignment + * and the indicated horizontal and vertical gaps. + *

+ * The value of the alignment argument must be one of + * WrapLayout, WrapLayout, + * or WrapLayout. + * @param align the alignment value + * @param hgap the horizontal gap between components + * @param vgap the vertical gap between components + */ + new(int align, int hgap, int vgap) { + super(align, hgap, vgap) + } + + /** + * Returns the preferred dimensions for this layout given the + * visible components in the specified target container. + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the + * subcomponents of the specified container + */ + override Dimension preferredLayoutSize(Container target) { + return layoutSize(target, true) + } + + /** + * Returns the minimum dimensions needed to layout the visible + * components contained in the specified target container. + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the + * subcomponents of the specified container + */ + override Dimension minimumLayoutSize(Container target) { + var Dimension minimum = layoutSize(target, false) + minimum.width -= (getHgap() + 1) + return minimum + } + + /** + * Returns the minimum or preferred dimension needed to layout the target + * container. + * @param target target to get layout size for + * @param preferred should preferred size be calculated + * @return the dimension to layout the target container + */ + def private Dimension layoutSize(Container target, boolean preferred) { + synchronized (target.getTreeLock()) { + // Each row must fit with the width allocated to the containter. + // When the container width = 0, the preferred width of the container + // has not yet been calculated so lets ask for the maximum. + var int targetWidth = target.getSize().width + var Container container = target + while (container.getSize().width === 0 && container.getParent() !== null) { + container = container.getParent() + } + targetWidth = container.getSize().width + if(targetWidth === 0) targetWidth = Integer.MAX_VALUE + var int hgap = getHgap() + var int vgap = getVgap() + var Insets insets = target.getInsets() + var int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2) + var int maxWidth = targetWidth - horizontalInsetsAndGap + // Fit components into the allowed width + var Dimension dim = new Dimension(0, 0) + var int rowWidth = 0 + var int rowHeight = 0 + var int nmembers = target.getComponentCount() + for (var int i = 0; i < nmembers; i++) { + var Component m = target.getComponent(i) + if (m.isVisible()) { + var Dimension d = if(preferred) m.getPreferredSize() else m.getMinimumSize() + // Can't add the component to current row. Start a new row. + if (rowWidth + d.width > maxWidth) { + addRow(dim, rowWidth, rowHeight) + rowWidth = 0 + rowHeight = 0 + } + // Add a horizontal gap for all components after the first + if (rowWidth !== 0) { + rowWidth += hgap + } + rowWidth += d.width + rowHeight = Math.max(rowHeight, d.height) + } + } + addRow(dim, rowWidth, rowHeight) + dim.width += horizontalInsetsAndGap + dim.height += insets.top + insets.bottom + vgap * 2 + // When using a scroll pane or the DecoratedLookAndFeel we need to + // make sure the preferred size is less than the size of the + // target containter so shrinking the container size works + // correctly. Removing the horizontal gap is an easy way to do this. + var Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane, target) + if (scrollPane !== null && target.isValid()) { + dim.width -= (hgap + 1) + } + return dim + } + } + + /* + * A new row has been completed. Use the dimensions of this row + * to update the preferred size for the container. + * + * @param dim update the width and height when appropriate + * @param rowWidth the width of the row to add + * @param rowHeight the height of the row to add + */ + def private void addRow(Dimension dim, int rowWidth, int rowHeight) { + dim.width = Math.max(dim.width, rowWidth) + if (dim.height > 0) { + dim.height += getVgap() + } + dim.height += rowHeight + } +} 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 8dfc8eaa..7d8005ac 100644 --- a/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties +++ b/sqldev/src/main/resources/org/utplsql/sqldev/resources/UtplsqlResources.properties @@ -6,13 +6,40 @@ EXTENSION_DESCRIPTION=Extension for running unit tests in SQL Developer. EXTENSION_OWNER=Philipp Salvisberg MIN_SQLDEV_VERSION=12.2.0.19.0.7 +# Icons +UTPLSQL_ICON=/org/utplsql/sqldev/resources/images/utPLSQL.png +SUCCESS_ICON=/org/utplsql/sqldev/resources/images/success.png +ERROR_ICON=/org/utplsql/sqldev/resources/images/error.png +FAILURE_ICON=/org/utplsql/sqldev/resources/images/failure.png +DISABLED_ICON=/org/utplsql/sqldev/resources/images/disabled.png +WARNING_ICON=/org/utplsql/sqldev/resources/images/warning.png +INFO_ICON=/org/utplsql/sqldev/resources/images/info.png +REFRESH_ICON=/org/utplsql/sqldev/resources/images/refresh.png +RUN_ICON=/org/utplsql/sqldev/resources/images/run.png +RUN_WORKSHEET_ICON=/org/utplsql/sqldev/resources/images/run_worksheet.png +CLEAR_ICON=/org/utplsql/sqldev/resources/images/clear.png +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 + # Translatable text PREF_LABEL=utPLSQL +PREF_USE_REALTIME_REPORTER_LABEL=Use realtime reporter? PREF_UNSHARED_WORKSHEET_LABEL=Open an unshared worksheet? PREF_RESET_PACKAGE_LABEL=Reset package before running utPLSQL? PREF_CLEAR_SCREEN_LABEL=Clear script output panel before running utPLSQL? PREF_AUTO_EXECUTE_LABEL=Execute unit test automatically? -PREF_CHECK_RUN_UTPLSQL_TEST_LABEL=Check availability of "Run utPLSQL test" menu option? +PREF_CHECK_RUN_UTPLSQL_TEST_LABEL=Check availability of menu option? +MENU_REALTIME_REPORTER_LABEL=Realtime Reporter +PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL=Number of runs in history +PREF_SHOW_DISABLED_COUNTER_LABEL=Show disabled counter? +PREF_SHOW_WARNINGS_COUNTER_LABEL=Show warnings counter? +PREF_SHOW_INFO_COUNTER_LABEL=Show info counter? +PREF_SHOW_WARNING_INDICATOR_LABEL=Show warning indicator? +PREF_SHOW_INFO_INDICATOR_LABEL=Show info indicator? +PREF_SHOW_TEST_DESCRIPTION_LABEL=Show description (if present)? +PREF_SYNC_DETAIL_TAB_LABEL=Synchronize detail tab based on test status? PREF_TEST_PACKAGE_PREFIX_LABEL=Test package prefix PREF_TEST_PACKAGE_SUFFIX_LABEL=Test package suffix PREF_TEST_UNIT_PREFIX_LABEL=Test unit prefix @@ -22,7 +49,7 @@ PREF_GENERATE_COMMENTS_LABEL=Generate comments? PREF_DISABLE_TESTS_LABEL=Disable tests? PREF_SUITE_PATH_LABEL=Suite path PREF_INDENT_SPACES_LABEL=Indent spaces -PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL=Check availability of "Generate utPLSQL test" menu option? +PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL=Check availability of menu option? PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL=Create code templates PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL=Root folder in Generators view PREF_GENERATE_FILES_LABEL=Generate files? @@ -40,3 +67,33 @@ WINDOW_EXCLUDE_OBJECS_LABEL=Exclude objects WINDOW_RUN_BUTTON=Run WINDOW_CANCEL_BUTTON=Cancel WORKSHEET_TITLE=utPLSQL +RUNNER_VIEW_TITLE=utPLSQL +RUNNER_REFRESH_TOOLTIP=Reset ordering and refresh +RUNNER_RERUN_TOOLTIP=Rerun all tests +RUNNER_CLEAR_BUTTON=Clear history +RUNNER_RERUN_WORKSHEET_TOOLTIP=Rerun all tests in a new worksheet +RUNNER_TESTS_LABEL=Tests +RUNNER_FAILURES_LABEL=Failures +RUNNER_ERRORS_LABEL=Errors +RUNNER_DISABLED_LABEL=Disabled +RUNNER_WARNINGS_LABEL=Warnings +RUNNER_INFO_LABEL=Info +RUNNER_INITIALIZING_TEXT=Initializing... +RUNNER_RUNNING_TEXT=Running tests... +RUNNER_FINNISHED_TEXT=Finished after %.3f seconds. +RUNNER_NO_TESTS_FOUND_TEXT=No tests found. +RUNNER_RUN_MENUITEM=Run test +RUNNER_RUN_WORKSHEET_MENUITEM=Run test in new worksheet +RUNNER_TEST_ID_COLUMN=Suitepath +RUNNER_TEST_EXECUTION_TIME_COLUMN=Time [s] +RUNNER_OWNER_LABEL=Owner +RUNNER_PACKAGE_LABEL=Package +RUNNER_PROCEDURE_LABEL=Procedure +RUNNER_DESCRIPTION_LABEL=Description +RUNNER_START_LABEL=Start +RUNNER_ASSERT_DESCRIPTION_COLUMN=Assert description (failed line) +RUNNER_TEST_TAB_LABEL=Test +RUNNER_FAILURES_TAB_LABEL=Failures +RUNNER_ERRORS_TAB_LABEL=Errors +RUNNER_WARNINGS_TAB_LABEL=Warnings +RUNNER_INFO_TAB_LABEL=Info 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 3fe1fa2d..8d1d7293 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 @@ -2,11 +2,21 @@ # Translatable text PREF_LABEL=utPLSQL +PREF_USE_REALTIME_REPORTER_LABEL=Realtime Reporter verwenden? PREF_UNSHARED_WORKSHEET_LABEL=Arbeitsblatt mit eigener Verbindung öffnen? PREF_RESET_PACKAGE_LABEL=Package vor der Ausführung von utPLSQL zurücksetzen? PREF_CLEAR_SCREEN_LABEL=Skriptausgabe-Fenster vor der Ausführung von utPLSQL leeren? PREF_AUTO_EXECUTE_LABEL=Unit Test automatisch ausführen? -PREF_CHECK_RUN_UTPLSQL_TEST_LABEL=Verfügbarkeit der Menüoption "utPLSQL Test ausführen" prüfen? +PREF_CHECK_RUN_UTPLSQL_TEST_LABEL=Verfügbarkeit der Menüoption prüfen? +MENU_REALTIME_REPORTER_LABEL=Realtime Reporter +PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL=Anzahl Ausführungen in der Historie +PREF_SHOW_DISABLED_COUNTER_LABEL=Deaktiviert-Zähler anzeigen? +PREF_SHOW_WARNINGS_COUNTER_LABEL=Warnungen-Zähler anzeigen? +PREF_SHOW_INFO_COUNTER_LABEL=Info-Zähler anzeigen? +PREF_SHOW_WARNING_INDICATOR_LABEL=Warnung-Indikator anzeigen? +PREF_SHOW_INFO_INDICATOR_LABEL=Info-Indikator anzeigen? +PREF_SHOW_TEST_DESCRIPTION_LABEL=Beschreibung anzeigen (falls vorhanden)? +PREF_SYNC_DETAIL_TAB_LABEL=Detailansicht basierend auf dem Teststatus synchronisieren? PREF_TEST_PACKAGE_PREFIX_LABEL=Test Package Präfix PREF_TEST_PACKAGE_SUFFIX_LABEL=Test Package Suffix PREF_TEST_UNIT_PREFIX_LABEL=Test Unit Präfix @@ -16,7 +26,7 @@ PREF_GENERATE_COMMENTS_LABEL=Kommentare generieren? PREF_DISABLE_TESTS_LABEL=Tests deaktivieren? PREF_SUITE_PATH_LABEL=Suite-Pfad PREF_INDENT_SPACES_LABEL=Einrückungsleerzeichen -PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL=Verfügbarkeit der Menüoption "utPLSQL Test generieren" prüfen? +PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL=Verfügbarkeit der Menüoption prüfen? PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL=Codevorlagen erstellen PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL=Hauptverzeichnis in Generatoren Ansicht PREF_GENERATE_FILES_LABEL=Dateien generieren? @@ -34,3 +44,33 @@ WINDOW_EXCLUDE_OBJECS_LABEL=Exkludierte Objekte WINDOW_RUN_BUTTON=Start WINDOW_CANCEL_BUTTON=Abbrechen WORKSHEET_TITLE=utPLSQL +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_CLEAR_BUTTON=History löschen +RUNNER_TESTS_LABEL=Tests +RUNNER_FAILURES_LABEL=Fehlschläge +RUNNER_ERRORS_LABEL=Fehler +RUNNER_DISABLED_LABEL=Deaktiviert +RUNNER_WARNINGS_LABEL=Warnungen +RUNNER_INFO_LABEL=Info +RUNNER_INITIALIZING_TEXT=Initialisierung... +RUNNER_RUNNING_TEXT=Starte Tests... +RUNNER_FINNISHED_TEXT=Beendet nach %.3f Sekunden. +RUNNER_NO_TESTS_FOUND_TEXT=Keine Tests gefunden. +RUNNER_RUN_MENUITEM=Run testTest ausführen +RUNNER_RUN_WORKSHEET_MENUITEM=Test in neuem Arbeitsblatt ausführuen +RUNNER_TEST_ID_COLUMN_NAME=Suitepath +RUNNER_TEST_EXECUTION_TIME_COLUMN_NAME=Zeit [s] +RUNNER_OWNER_LABEL=Besitzer +RUNNER_PACKAGE_LABEL=Paket +RUNNER_PROCEDURE_LABEL=Prozedur +RUNNER_DESCRIPTION_LABEL=Beschreibung +RUNNER_START_LABEL=Start +RUNNER_ASSERT_DESCRIPTION_COLUMN_NAME=Assert Beschreibung (gescheiterte Zeile) +RUNNER_TEST_TAB_LABEL=Test +RUNNER_FAILURES_TAB_LABEL=Misserfolge +RUNNER_ERRORS_TAB_LABEL=Fehler +RUNNER_WARNINGS_TAB_LABEL=Warnungen +RUNNER_INFO_TAB_LABEL=Info diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/checkmark.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/checkmark.png new file mode 100644 index 00000000..c17f9beb Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/checkmark.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/clear.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/clear.png new file mode 100644 index 00000000..7132e95c Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/clear.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled.png new file mode 100644 index 00000000..b2afa029 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled_32x32.png new file mode 100644 index 00000000..3fcb0ca6 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/disabled_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error.png new file mode 100644 index 00000000..aefaed04 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error_32x32.png new file mode 100644 index 00000000..91278fb8 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/error_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure.png new file mode 100644 index 00000000..27bc6d87 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure_32x32.png new file mode 100644 index 00000000..ed3ecc10 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/failure_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/info.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/info.png new file mode 100644 index 00000000..eaf2ead8 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/info.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.gif b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.gif new file mode 100644 index 00000000..3356ba3e Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.gif differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.png new file mode 100644 index 00000000..ab2c3673 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress_32x32.png new file mode 100644 index 00000000..1110463b Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/progress_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/refresh.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/refresh.png new file mode 100644 index 00000000..2aebd50a Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/refresh.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run.png new file mode 100644 index 00000000..eebce3a8 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run_worksheet.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run_worksheet.png new file mode 100644 index 00000000..7a00f3b9 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/run_worksheet.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/status.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/status.png new file mode 100644 index 00000000..f6d3dd70 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/status.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success.png new file mode 100644 index 00000000..525198af Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success_32x32.png new file mode 100644 index 00000000..efb200a3 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/success_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/terminated_32x32.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/terminated_32x32.png new file mode 100644 index 00000000..defbbff7 Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/terminated_32x32.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL.png index c1d9b945..5014d77c 100644 Binary files a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL.png and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL_400x400.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL_400x400.png index 49f54b2b..0e784ddc 100644 Binary files a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL_400x400.png and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/utPLSQL_400x400.png differ diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/warning.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/warning.png new file mode 100644 index 00000000..63e614db Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/warning.png differ diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/AbstractJdbcTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/AbstractJdbcTest.xtend similarity index 98% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/AbstractJdbcTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/AbstractJdbcTest.xtend index 5341fb3f..015885e0 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/AbstractJdbcTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/AbstractJdbcTest.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.tests +package org.utplsql.sqldev.test import java.io.StringReader import java.util.ArrayList diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/PrefixToolsTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/PrefixToolsTest.xtend new file mode 100644 index 00000000..8c7d7760 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/PrefixToolsTest.xtend @@ -0,0 +1,43 @@ +package org.utplsql.sqldev.test + +import org.junit.Assert +import org.junit.Test +import org.utplsql.sqldev.model.PrefixTools + +class PrefixToolsTest { + @Test + def void two() { + val actual = PrefixTools.commonPrefix(#["junit.test.a", "junit.test.b"]) + val expected = "junit.test." + Assert.assertEquals(expected, actual) + } + + @Test + def void oneWithDot() { + val actual = PrefixTools.commonPrefix(#["junit.test.a"]) + val expected = "junit.test." + Assert.assertEquals(expected, actual) + } + + @Test + def void oneWithoutDot() { + val actual = PrefixTools.commonPrefix(#["junit-test-a"]) + val expected = "" + Assert.assertEquals(expected, actual) + } + + @Test + def void twoOverlapLeft() { + val actual = PrefixTools.commonPrefix(#["a.b.c", "a.b.c.d"]) + val expected = "" + Assert.assertEquals(expected, actual) + } + + @Test + def void twoOverlapRight() { + val actual = PrefixTools.commonPrefix(#["a.b.c.d", "a.b.c"]) + val expected = "" + Assert.assertEquals(expected, actual) + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/ResourceTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/ResourceTest.xtend similarity index 93% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/ResourceTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/ResourceTest.xtend index c09751b3..d283cb5d 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/ResourceTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/ResourceTest.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.tests +package org.utplsql.sqldev.test import org.junit.Assert import org.junit.Test @@ -22,7 +22,7 @@ import org.utplsql.sqldev.resources.UtplsqlResources class ResourceTest { @Test - def void testWindowPathsLabel() { + def void windowPathsLabel() { val actual = UtplsqlResources.getString("WINDOW_PATHS_LABEL") val expected = "utPLSQL paths" Assert.assertEquals(expected, actual) diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/UrlToolsTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/UrlToolsTest.xtend new file mode 100644 index 00000000..1ee8f4ac --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/UrlToolsTest.xtend @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test + +import org.junit.Assert +import org.junit.Test +import org.utplsql.sqldev.model.URLTools + +class UrlToolsTest { + val extension URLTools urlTools = new URLTools + + @Test + def void replacePlusSign() { + Assert.assertEquals("+", "%2B".replaceHexChars) + Assert.assertEquals("++", "%2B%2B".replaceHexChars) + Assert.assertEquals("abc+%xyz", "abc%2B%xyz".replaceHexChars) + } + + @Test + def void replaceAtSign() { + Assert.assertEquals("@", "%40".replaceHexChars) + Assert.assertEquals("@@", "%40%40".replaceHexChars) + Assert.assertEquals("abc@%xyz", "abc%40%xyz".replaceHexChars) + } + + @Test + def void replaceAtAndPlusSign() { + Assert.assertEquals("@+", "%40%2B".replaceHexChars) + Assert.assertEquals("@+@+", "%40%2B%40%2B".replaceHexChars) + Assert.assertEquals("abc@+%xyz", "abc%40%2B%xyz".replaceHexChars) + } + +} diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/CodeCoverageReporterWindowTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterDialogTest.xtend similarity index 74% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/CodeCoverageReporterWindowTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterDialogTest.xtend index 333d69bc..a11c6b20 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/CodeCoverageReporterWindowTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterDialogTest.xtend @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.tests +package org.utplsql.sqldev.test.coverage import org.junit.Test -import org.utplsql.sqldev.CodeCoverageReporter +import org.utplsql.sqldev.coverage.CodeCoverageReporter +import org.utplsql.sqldev.test.AbstractJdbcTest -class CodeCoverageReporterWindowTest extends AbstractJdbcTest{ +class CodeCoverageReporterDialogTest extends AbstractJdbcTest{ @Test - def void layoutTest() { + def void layout() { val reporter = new CodeCoverageReporter(#["SCOTT"], #['a', 'b', 'c'], dataSource.connection) reporter.showParameterWindow - Thread.sleep(2 * 1000) + Thread.sleep(4 * 1000) + reporter.frame.exit } } \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.xtend new file mode 100644 index 00000000..7f91d53b --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/coverage/CodeCoverageReporterTest.xtend @@ -0,0 +1,113 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.coverage + +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.util.Comparator +import org.junit.AfterClass +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test +import org.springframework.jdbc.BadSqlGrammarException +import org.springframework.jdbc.datasource.SingleConnectionDataSource +import org.utplsql.sqldev.coverage.CodeCoverageReporter +import org.utplsql.sqldev.test.AbstractJdbcTest + +class CodeCoverageReporterTest extends AbstractJdbcTest{ + + @BeforeClass + def static void setup() { + jdbcTemplate.execute(''' + CREATE OR REPLACE FUNCTION f RETURN INTEGER IS + BEGIN + RETURN 1; + END f; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE test_f IS + --%suite + + --%test + PROCEDURE f; + END test_f; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY test_f IS + --%test + PROCEDURE f IS + l_expected INTEGER := 1; + l_actual INTEGER; + BEGIN + l_actual := scott.f(); + ut.expect(l_actual).to_equal(l_expected); + END f; + END test_f; + ''') + } + + private def Path getNewestOutputFile() { + val file = File.createTempFile("test", ".txt") + val dir = file.parentFile + file.delete + val last = Files.list(dir.toPath) + .filter([f | !f.toFile.directory]) + .filter([f | f.fileName.toString.startsWith("utplsql_")]) + .filter([f | f.fileName.toString.endsWith(".html")]) + .max(Comparator.comparingLong([f|f.toFile().lastModified()])) + return last.get + } + + @Test + def void produceReportAndCloseConnection() { + // create temporary dataSource, closed by reporter + var ds = new SingleConnectionDataSource() + ds.driverClassName = "oracle.jdbc.OracleDriver" + ds.url = dataSource.url + ds.username = dataSource.username + ds.password = dataSource.password + val conn = ds.connection + val pathList=#[':test_f'] + val includeObjectList = #['f'] + val reporter = new CodeCoverageReporter(pathList, includeObjectList, conn) + val run = reporter.runAsync + run.join(20000) + Assert.assertEquals(true, conn.isClosed) + val outputFile = getNewestOutputFile + Assert.assertTrue(outputFile !== null) + val content = new String(Files.readAllBytes(outputFile), StandardCharsets.UTF_8) + Assert.assertTrue(content.contains('

SCOTT.F

100 % lines covered

')) + } + + @AfterClass + def static void teardown() { + try { + jdbcTemplate.execute("DROP PACKAGE test_f") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP FUNCTION f") + } catch (BadSqlGrammarException e) { + // ignore + } + } + + + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalBugFixTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalBugFixTest.xtend new file mode 100644 index 00000000..d53fe158 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalBugFixTest.xtend @@ -0,0 +1,107 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.dal + +import org.junit.AfterClass +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test +import org.springframework.jdbc.BadSqlGrammarException +import org.utplsql.sqldev.dal.UtplsqlDao +import org.utplsql.sqldev.test.AbstractJdbcTest + +class DalBugFixTest extends AbstractJdbcTest { + + @BeforeClass + @AfterClass + def static void setupAndTeardown() { + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + } + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/54 + def void issue54FolderIconForSuitesWithoutTests() { + setupAndTeardown + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS + -- %suite + + END junit_utplsql_test_pkg; + ''') + val dao = new UtplsqlDao(dataSource.connection) + val actualNodes = dao.runnables() + Assert.assertEquals(4, actualNodes.size) + val pkg = actualNodes.findFirst[it.id == "SCOTT:junit_utplsql_test_pkg"] + Assert.assertEquals("FOLDER_ICON", pkg.iconName) + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + } + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/54 + def void issue54PackageIconForSuitesWithTests() { + setupAndTeardown + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS + -- %suite + + -- %test + PROCEDURE t1; + + END junit_utplsql_test_pkg; + ''') + val dao = new UtplsqlDao(dataSource.connection) + val actualNodes = dao.runnables() + Assert.assertEquals(6, actualNodes.size) + val pkg = actualNodes.findFirst[it.id == "SCOTT:junit_utplsql_test_pkg"] + Assert.assertEquals("PACKAGE_ICON", pkg.iconName) + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + } + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/55 + def void issue55SuiteWithoutTests() { + setupAndTeardown + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS + -- %suite + + END junit_utplsql_test_pkg; + ''') + val dao = new UtplsqlDao(dataSource.connection) + val actualNodes = dao.runnables() + Assert.assertEquals(4, actualNodes.size) + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + } + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/56 + def void issue56SuiteWithoutTests() { + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS + -- %suite + + END junit_utplsql_test_pkg; + ''') + val dao = new UtplsqlDao(dataSource.connection) + Assert.assertTrue(dao.containsUtplsqlTest("scott", "junit_utplsql_test_pkg")) + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalTest.xtend similarity index 82% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalTest.xtend index 19699259..25573974 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/DalTest.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.utplsql.sqldev.tests + package org.utplsql.sqldev.test.dal import java.util.ArrayList import java.util.HashMap @@ -24,6 +24,8 @@ import org.junit.Test import org.springframework.jdbc.BadSqlGrammarException import org.utplsql.sqldev.dal.UtplsqlDao import org.utplsql.sqldev.model.ut.Annotation +import org.utplsql.sqldev.test.AbstractJdbcTest +import org.junit.Ignore class DalTest extends AbstractJdbcTest { @@ -36,6 +38,11 @@ class DalTest extends AbstractJdbcTest { } catch (BadSqlGrammarException e) { // ignore } + try { + jdbcTemplate.execute("DROP PACKAGE BODY junit_utplsql_test_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } try { jdbcTemplate.execute("DROP PACKAGE junit_no_test_pkg") } catch (BadSqlGrammarException e) { @@ -128,7 +135,10 @@ class DalTest extends AbstractJdbcTest { @Test def void containsUtplsqlTest304() { - containsUtplsqlTest("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + containsUtplsqlTest("3.0.4") + } } @Test @@ -136,6 +146,12 @@ class DalTest extends AbstractJdbcTest { containsUtplsqlTest("3.1.3") } + @Test + @Ignore + def void containsUtplsqlTest999() { + containsUtplsqlTest("9.9.9") + } + def void annotations(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -178,7 +194,10 @@ class DalTest extends AbstractJdbcTest { @Test def void annotations304() { - annotations("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + annotations("3.0.4") + } } @Test @@ -186,6 +205,11 @@ class DalTest extends AbstractJdbcTest { annotations("3.1.3") } + @Test + def void annotations999() { + annotations("9.9.9") + } + def void testablesPackages(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -216,7 +240,10 @@ class DalTest extends AbstractJdbcTest { @Test def void testablesPackages304() { - testablesPackages("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + testablesPackages("3.0.4") + } } @Test @@ -224,6 +251,11 @@ class DalTest extends AbstractJdbcTest { testablesPackages("3.1.3") } + @Test + def void testablesPackages999() { + testablesPackages("9.9.9") + } + def void testablesTypes(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -247,7 +279,10 @@ class DalTest extends AbstractJdbcTest { @Test def void testablesTypes304() { - testablesTypes("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + testablesTypes("3.0.4") + } } @Test @@ -255,6 +290,11 @@ class DalTest extends AbstractJdbcTest { testablesTypes("3.1.3") } + @Test + def void testablesTypes999() { + testablesTypes("9.9.9") + } + def void testablesFunctions(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -271,7 +311,10 @@ class DalTest extends AbstractJdbcTest { @Test def void testablesFunctions304() { - testablesFunctions("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + testablesFunctions("3.0.4") + } } @Test @@ -279,6 +322,11 @@ class DalTest extends AbstractJdbcTest { testablesFunctions("3.1.3") } + @Test + def void testablesFunctions999() { + testablesFunctions("9.9.9") + } + def void testablesProcedures(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -295,7 +343,10 @@ class DalTest extends AbstractJdbcTest { @Test def void testablesProcedures304() { - testablesProcedures("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + testablesProcedures("3.0.4") + } } @Test @@ -303,6 +354,11 @@ class DalTest extends AbstractJdbcTest { testablesProcedures("3.1.3") } + @Test + def void testablesProcedures999() { + testablesProcedures("9.9.9") + } + def void runnables(String utPlsqlVersion) { val dao = new UtplsqlDao(dataSource.connection) dao.utPlsqlVersion = utPlsqlVersion @@ -354,13 +410,21 @@ class DalTest extends AbstractJdbcTest { @Test def void runnables304() { - runnables("3.0.4") + val dao = new UtplsqlDao(dataSource.connection) + if (dao.normalizedUtPlsqlVersionNumber < UtplsqlDao.FIRST_VERSION_WITHOUT_INTERNAL_API) { + runnables("3.0.4") + } } @Test def void runnables313() { runnables("3.1.3") } + + @Test + def void runnables999() { + runnables("9.9.9") + } @Test def void dbmsOutput() { @@ -419,7 +483,7 @@ class DalTest extends AbstractJdbcTest { l_actual INTEGER; BEGIN l_actual := junit_f; - ut.expect(l_actual).to_equal(l_expected).to_equal(l_actual); + ut.expect(l_actual).to_equal(l_expected); END f1; END junit_utplsql_test_pkg; ''') @@ -471,69 +535,57 @@ class DalTest extends AbstractJdbcTest { Assert.assertEquals(expected, actual) } - + @Test - def void issue54FolderIconForSuitesWithoutTests() { - setupAndTeardown - jdbcTemplate.execute(''' - CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS - -- %suite - - END junit_utplsql_test_pkg; - ''') + def void getSourceOfPackage() { val dao = new UtplsqlDao(dataSource.connection) - val actualNodes = dao.runnables() - Assert.assertEquals(4, actualNodes.size) - val pkg = actualNodes.findFirst[it.id == "SCOTT:junit_utplsql_test_pkg"] - Assert.assertEquals("FOLDER_ICON", pkg.iconName) - jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") - } - - @Test - def void issue54PackageIconForSuitesWithTests() { - setupAndTeardown jdbcTemplate.execute(''' CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS -- %suite -- %test - PROCEDURE t1; - + PROCEDURE p1; END junit_utplsql_test_pkg; ''') - val dao = new UtplsqlDao(dataSource.connection) - val actualNodes = dao.runnables() - Assert.assertEquals(6, actualNodes.size) - val pkg = actualNodes.findFirst[it.id == "SCOTT:junit_utplsql_test_pkg"] - Assert.assertEquals("PACKAGE_ICON", pkg.iconName) + val actual = dao.getSource("SCOTT", "PACKAGE", "JUNIT_UTPLSQL_TEST_PKG") + Assert.assertTrue(actual.contains("-- %suite")) + Assert.assertTrue(actual.contains("PROCEDURE p1;")) jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") } @Test - def void issue55SuiteWithoutTests() { - setupAndTeardown + def void getSourceOfPackageBody() { + val dao = new UtplsqlDao(dataSource.connection) jdbcTemplate.execute(''' - CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS - -- %suite - + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test_pkg IS + PROCEDURE p1 IS + l_expected INTEGER := 1; + l_actual INTEGER; + BEGIN + l_actual := junit_f; + ut.expect(l_actual).to_equal(l_expected); + END p1; END junit_utplsql_test_pkg; - ''') - val dao = new UtplsqlDao(dataSource.connection) - val actualNodes = dao.runnables() - Assert.assertEquals(4, actualNodes.size) - jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") + '''); + val actual = dao.getSource("SCOTT", "PACKAGE BODY", "JUNIT_UTPLSQL_TEST_PKG") + Assert.assertTrue(actual.contains("PACKAGE BODY")) + Assert.assertTrue(actual.contains("PROCEDURE p1 IS")) + jdbcTemplate.execute("DROP PACKAGE BODY junit_utplsql_test_pkg") } @Test - def void issue56SuiteWithoutTests() { + def void getObjectType() { + val dao = new UtplsqlDao(dataSource.connection) jdbcTemplate.execute(''' CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS -- %suite + -- %test + PROCEDURE p1; END junit_utplsql_test_pkg; ''') - val dao = new UtplsqlDao(dataSource.connection) - Assert.assertTrue(dao.containsUtplsqlTest("scott", "junit_utplsql_test_pkg")) + val actual = dao.getObjectType("SCOTT", "JUNIT_UTPLSQL_TEST_PKG") + Assert.assertEquals("PACKAGE", actual) jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") } diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterFetchSizeTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterFetchSizeTest.xtend new file mode 100644 index 00000000..b713cbc2 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterFetchSizeTest.xtend @@ -0,0 +1,144 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.dal + +import java.util.UUID +import java.util.logging.Logger +import org.junit.AfterClass +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test +import org.springframework.jdbc.BadSqlGrammarException +import org.springframework.jdbc.datasource.SingleConnectionDataSource +import org.utplsql.sqldev.dal.RealtimeReporterDao +import org.utplsql.sqldev.test.AbstractJdbcTest + +class RealtimeReporterFetchSizeTest extends AbstractJdbcTest { + + static val Logger logger = Logger.getLogger(RealtimeReporterFetchSizeTest.name); + + @BeforeClass + def static void setup() { + + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_fetch_size_pkg is + --%suite(JUnit testing) + + --%test(test 1 - 0 seconds) + PROCEDURE test_1_0; + + --%test(test 2 - 1 seconds) + PROCEDURE test_2_1; + + --%test(test 3 - 2 seconds) + PROCEDURE test_3_2; + + --%test(test 4 - 0 seconds) + PROCEDURE test_4_0; + + --%test(test 5 - 0 seconds) + PROCEDURE test_5_0; + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_fetch_size_pkg is + PROCEDURE test_1_0 IS + BEGIN + NULL; + END; + + PROCEDURE test_2_1 IS + BEGIN + dbms_session.sleep(1); + END; + + PROCEDURE test_3_2 IS + BEGIN + dbms_session.sleep(2); + END; + + PROCEDURE test_4_0 IS + BEGIN + NULL; + END; + + PROCEDURE test_5_0 IS + BEGIN + NULL; + END; + END; + ''') + } + + @AfterClass + def static void teardown() { + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_fetch_size_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + } + + private def delayFreeStreamingConsumtionProducer(String reporterId) { + var ds = new SingleConnectionDataSource() + ds.driverClassName = "oracle.jdbc.OracleDriver" + ds.url = dataSource.url + ds.username = dataSource.username + ds.password = dataSource.password + val dao = new RealtimeReporterDao(ds.connection) + dao.produceReport(reporterId, #["junit_utplsql_fetch_size_pkg"]) + } + + @Test + def void delayFreeStreamingConsumtion() { + val long TOLERANCE_MS = 400 + var ds = new SingleConnectionDataSource() + ds.driverClassName = "oracle.jdbc.OracleDriver" + ds.url = dataSource.url + ds.username = dataSource.username + ds.password = dataSource.password + val consumer = new TestRealtimerReporterEventTimedConsumer + val reporterId = UUID.randomUUID().toString.replace("-", ""); + val dao = new RealtimeReporterDao(ds.connection) + val Runnable runnable = [|delayFreeStreamingConsumtionProducer(reporterId)] + val thread = new Thread(runnable) + thread.name = "utPLSQL run test" + thread.start + dao.consumeReport(reporterId, consumer) + logger.fine(consumer.postTestEvents.toString) + Assert.assertEquals(5, consumer.postTestEvents.entrySet.size) + val test_1_0 = consumer.postTestEvents.get("junit_utplsql_fetch_size_pkg.test_1_0") + val test_2_1 = consumer.postTestEvents.get("junit_utplsql_fetch_size_pkg.test_2_1") + val test_3_2 = consumer.postTestEvents.get("junit_utplsql_fetch_size_pkg.test_3_2") + val test_4_0 = consumer.postTestEvents.get("junit_utplsql_fetch_size_pkg.test_4_0") + val test_5_0 = consumer.postTestEvents.get("junit_utplsql_fetch_size_pkg.test_5_0") + val test_2_1_time = test_2_1 - test_1_0 + logger.fine("test_2_1 time [ms]: " + test_2_1_time) + Assert.assertTrue("test_2_1 runtime was too long", test_2_1_time < 1000 + TOLERANCE_MS) + Assert.assertTrue("test_2_1 runtime was too short", test_2_1_time > 1000 - TOLERANCE_MS) + val test_3_2_time = test_3_2 - test_2_1 + logger.fine("test_3_2 time [ms]: " + test_3_2_time) + Assert.assertTrue("test_3_2 runtime was too long", test_3_2_time < 2000 + TOLERANCE_MS) + Assert.assertTrue("test_3_2 runtime was too short", test_3_2_time > 2000 - TOLERANCE_MS) + val test_4_0_time = test_4_0 - test_3_2 + logger.fine("test_4_0 time [ms]: " + test_4_0_time) + Assert.assertTrue("test_4_0 runtime was too long", test_4_0_time < TOLERANCE_MS) + val test_5_0_time = test_5_0 - test_4_0 + logger.fine("test_5_0 time [ms]: " + test_5_0_time) + Assert.assertTrue("test_5_0 runtime was too long", test_5_0_time < TOLERANCE_MS) + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.xtend new file mode 100644 index 00000000..908fe3f2 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/RealtimeReporterTest.xtend @@ -0,0 +1,183 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.dal + +import java.util.UUID +import java.util.logging.Logger +import org.junit.AfterClass +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test +import org.springframework.jdbc.BadSqlGrammarException +import org.utplsql.sqldev.dal.RealtimeReporterDao +import org.utplsql.sqldev.model.runner.PostRunEvent +import org.utplsql.sqldev.model.runner.PostSuiteEvent +import org.utplsql.sqldev.model.runner.PostTestEvent +import org.utplsql.sqldev.model.runner.PreRunEvent +import org.utplsql.sqldev.model.runner.PreSuiteEvent +import org.utplsql.sqldev.model.runner.PreTestEvent +import org.utplsql.sqldev.test.AbstractJdbcTest + +class RealtimeReporterTest extends AbstractJdbcTest { + + static val Logger logger = Logger.getLogger(RealtimeReporterTest.name); + + @BeforeClass + def static void setup() { + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test1_pkg is + --%suite(JUnit testing) + --%suitepath(a) + + --%context(test context) + + --%test(test 1 - OK) + PROCEDURE test_1_ok; + + --%test(test 2 - NOK) + PROCEDURE test_2_nok; + + --%endcontext + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test1_pkg IS + PROCEDURE test_1_ok IS + BEGIN + ut.expect(1).to_equal(1); + END; + + PROCEDURE test_2_nok IS + BEGIN + ut.expect(1).to_equal(2); + END; + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test2_pkg IS + --%suite + --%suitepath(b) + + --%test + PROCEDURE test_3_ok; + + --%test + PROCEDURE test_4_nok; + + --%test + --%disabled + PROCEDURE test_5; + end; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test2_pkg IS + PROCEDURE test_3_ok IS + BEGIN + ut3.ut.expect(2).to_equal(2); + END; + + PROCEDURE test_4_nok IS + BEGIN + ut3.ut.expect(2).to_equal(3); + ut3.ut.expect(2).to_equal(4); + END; + + PROCEDURE test_5 IS + BEGIN + null; + END; + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test3_pkg IS + --%suite + --%suitepath(b) + + --%test + PROCEDURE test_6_with_runtime_error; + + --%test + PROCEDURE test_7_with_serveroutput; + + --%afterall + PROCEDURE print_and_raise; + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test3_pkg IS + PROCEDURE test_6_with_runtime_error is + l_actual INTEGER; + BEGIN + EXECUTE IMMEDIATE 'select 6 from non_existing_table' INTO l_actual; + ut3.ut.expect(6).to_equal(l_actual); + END; + + PROCEDURE test_7_with_serveroutput IS + BEGIN + dbms_output.put_line('before test 7'); + ut3.ut.expect(7).to_equal(7); + dbms_output.put_line('after test 7'); + END; + + PROCEDURE print_and_raise IS + BEGIN + dbms_output.put_line('Now, a no_data_found exception is raised'); + dbms_output.put_line('dbms_output and error stack is reported for this suite.'); + dbms_output.put_line('A runtime error in afterall is counted as a warning.'); + raise no_data_found; + END; + END; + ''') + } + + @AfterClass + def static void teardown() { + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test1_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test2_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test3_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + } + + @Test + def void produceAndConsume() { + val dao = new RealtimeReporterDao(dataSource.connection) + val reporterId = UUID.randomUUID().toString.replace("-", ""); + val consumer = new TestRealtimerReporterEventConsumer + dao.produceReport(reporterId, #[":a", ":b"]) + dao.consumeReport(reporterId, consumer) + logger.fine(consumer.consumedList.toString) + Assert.assertEquals(1, consumer.consumedList.filter[it instanceof PreRunEvent].size) + Assert.assertEquals(1, consumer.consumedList.filter[it instanceof PostRunEvent].size) + // 2 suitepaths (a, b), 1 context, 3 packages -> 6 suites + Assert.assertEquals(6, consumer.consumedList.filter[it instanceof PreSuiteEvent].size) + Assert.assertEquals(6, consumer.consumedList.filter[it instanceof PostSuiteEvent].size) + Assert.assertEquals(7, consumer.consumedList.filter[it instanceof PreTestEvent].size) + Assert.assertEquals(7, consumer.consumedList.filter[it instanceof PostTestEvent].size) + Assert.assertEquals(28, consumer.consumedList.size) + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventConsumer.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventConsumer.xtend new file mode 100644 index 00000000..caa75d71 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventConsumer.xtend @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.dal + +import java.util.ArrayList +import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent + +class TestRealtimerReporterEventConsumer implements RealtimeReporterEventConsumer { + + val consumedList = new ArrayList + + def getConsumedList() { + return consumedList + } + + override void process(RealtimeReporterEvent event) { + consumedList.add(event) + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend new file mode 100644 index 00000000..ff46d749 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.dal + +import java.util.HashMap +import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent +import org.utplsql.sqldev.model.runner.PostTestEvent + +class TestRealtimerReporterEventTimedConsumer implements RealtimeReporterEventConsumer { + + val postTestEvents = new HashMap + + def getPostTestEvents() { + return postTestEvents + } + + override void process(RealtimeReporterEvent event) { + if (event instanceof PostTestEvent) { + postTestEvents.put(event.id, System.currentTimeMillis) + } + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/parser/SqlDevParserTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/SqlDevParserTest.xtend new file mode 100644 index 00000000..a424e7d2 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/SqlDevParserTest.xtend @@ -0,0 +1,123 @@ +package org.utplsql.sqldev.test.parser + +import org.junit.Assert +import org.junit.Test +import org.utplsql.sqldev.parser.SqlDevParser + +class SqlDevParserTest { + val packageSpec = ''' + CREATE OR REPLACE PACKAGE junit_utplsql_test1_pkg is + --%suite(JUnit testing) + --%suitepath(a) + + --%context(test context) + + --%test(test 1 - OK) + PRoCeDURE test_1_ok; + + --%test(test 2 - NOK) + PROCEDURE test_2_nok; + + --%test(test 3 - disabled) + --%disabled + PROCEDURE test_3_disabled; + + --%test(test 4 - errored) + PROCEDURE test_4_errored; + + --%test(test 5 - warnings) + PROCEDURE test_5_warnings; + --%endcontext + + function my_Func (p IN number) RETURN BOOLEAN; + END; + ''' + + val packageBody = ''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test1_pkg IS + PROCEDURE test_1_ok IS + BEGIN + dbms_output.put_line('start test 1'); + dbms_session.sleep(1); + ut.expect(1).to_equal(1); + dbms_output.put_line('end test 1'); + END; + + PROCEDURE test_2_nok IS + BEGIN + dbms_output.put_line('start test 2'); + dbms_session.sleep(2); + ut.expect(1, 'first assert.').to_equal(2); + ut.expect(1, 'second assert.').to_equal(2); + dbms_output.put_line('end test 2'); + END; + + PROCEDURE test_3_disabled IS + BEGIN + NULL; + END; + + PROCEDURE test_4_errored IS + BEGIN + EXECUTE IMMEDIATE 'bla bla'; + END; + + PROCEDURE test_5_warnings IS + BEGIN + COMMIT; -- will raise a warning + ut.expect(1).to_equal(1); + END; + + FUNCTION my_Func (p IN number) RETURN BOOLEAN IS + RETURN TRUE; + END; + END; + ''' + + @Test + def void packageSpecMembers() { + val parser = new SqlDevParser + val actual = parser.getMembers(packageSpec) + Assert.assertEquals(6, actual.length) + val first = actual.get(0) + Assert.assertEquals("PROCEDURE", first.type) + Assert.assertEquals("test_1_ok", first.name) + val last = actual.get(5) + Assert.assertEquals("FUNCTION", last.type) + Assert.assertEquals("my_Func", last.name) + } + + @Test + def void packageBodyMembers() { + val parser = new SqlDevParser + val actual = parser.getMembers(packageBody) + Assert.assertEquals(6, actual.length) + val first = actual.get(0) + Assert.assertEquals("PROCEDURE", first.type) + Assert.assertEquals("test_1_ok", first.name) + val last = actual.get(5) + Assert.assertEquals("FUNCTION", last.type) + Assert.assertEquals("my_Func", last.name) + } + + @Test + def void StartLineSpec() { + val parser = new SqlDevParser + val first = parser.getMemberStartLine(packageSpec, 'test_1_ok') + Assert.assertEquals(8, first) + val last = parser.getMemberStartLine(packageSpec, 'my_func') + Assert.assertEquals(24, last) + } + + @Test + def void StartLineBody() { + val parser = new SqlDevParser + val first = parser.getMemberStartLine(packageBody, 'test_1_ok') + Assert.assertEquals(2, first) + val last = parser.getMemberStartLine(packageBody, 'my_func') + Assert.assertEquals(35, last) + } + + + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserBugFixTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserBugFixTest.xtend new file mode 100644 index 00000000..3ec0c926 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserBugFixTest.xtend @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.parser + +import org.junit.Assert +import org.junit.Test +import org.utplsql.sqldev.parser.UtplsqlParser + +class UtplsqlParserBugFixTest { + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/1 + def issue1MatchingExprInStringLiterals() { + val plsql = ''' + create or replace package body test_expect_not_to_be_null + is + gc_object_name constant varchar2(30) := 't_not_to_be_null_test'; + gc_nested_table_name constant varchar2(30) := 'tt_not_to_be_null_test'; + gc_varray_name constant varchar2(30) := 'tv_not_to_be_null_test'; + + procedure cleanup_expectations + is + begin + ut3.ut_expectation_processor.clear_expectations(); + end; + + procedure create_types + is + pragma autonomous_transaction; + begin + execute immediate 'create type '||gc_object_name||' is object (dummy number)'; + execute immediate ' create type '||gc_nested_table_name||' is table of number'; + execute immediate ' + create type '||gc_varray_name||' is varray(1) of number'; + end; + + procedure drop_types + is + pragma autonomous_transaction; + begin + execute immediate 'drop type '||gc_object_name; + execute immediate ' drop type '||gc_nested_table_name; + execute immediate ' + drop type '||gc_varray_name; + end; + + procedure blob_not_null + is + begin + --Act + execute immediate expectations_helpers.unary_expectation_block('not_to_be_null', 'blob', 'to_blob(''abc'')'); + --Assert + ut.expect(anydata.convertCollection(ut3.ut_expectation_processor.get_failed_expectations())).to_be_empty(); + end; + + --and so on... + + end; + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals("test_expect_not_to_be_null.cleanup_expectations", parser.getPathAt(parser.toPosition(7,1))) + Assert.assertEquals("test_expect_not_to_be_null.create_types", parser.getPathAt(parser.toPosition(13,1))) + // was: '||gc_varray_name||'.drop_types + Assert.assertEquals("test_expect_not_to_be_null.drop_types", parser.getPathAt(parser.toPosition(23,1))) + // was: '||gc_varray_name||'.blob_not_null + Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(33,1))) + } + + @Test + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/7 + def issue7WrongPositionWithWindowsLineSeparator() { + val plsql = ''' + create or replace package test_expect_not_to_be_null + is + --%suite(expectations - not_to_be_null) + --%suitepath(utplsql.core.expectations.unary) + + --%aftereach + procedure cleanup_expectations; + + --%beforeall + procedure create_types; + + --%afterall + procedure drop_types; + + --%test(Gives success for not null blob) + procedure blob_not_null; + + --%test(Gives success for blob with length 0) + procedure blob_0_length; + + -- ... + end test_expect_not_to_be_null; + / + ''' + val parser = new UtplsqlParser(plsql) + // was: test_expect_not_to_be_null.create_types + Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(13,26))) + } + +} diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserTest.xtend similarity index 65% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserTest.xtend index 93be55f4..b49293ac 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/parser/UtplsqlParserTest.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.tests +package org.utplsql.sqldev.test.parser import org.junit.AfterClass import org.junit.Assert @@ -21,6 +21,7 @@ import org.junit.BeforeClass import org.junit.Test import org.springframework.jdbc.BadSqlGrammarException import org.utplsql.sqldev.parser.UtplsqlParser +import org.utplsql.sqldev.test.AbstractJdbcTest class UtplsqlParserTest extends AbstractJdbcTest { @@ -74,7 +75,7 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testPackage() { + def packageWithoutConnection() { val parser = new UtplsqlParser(sqlScript) val objects = parser.getObjects Assert.assertEquals(2, objects.size) @@ -98,11 +99,8 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testPackageWithConnection() { + def packageWithConnection() { val plsql = ''' - /* - * some comment - */ CREATE OR REPLACE PACKAGE pkg IS -- %suite -- %rollback(manual) @@ -129,98 +127,9 @@ class UtplsqlParserTest extends AbstractJdbcTest { Assert.assertEquals("SCOTT.PKG.p", parser.getPathAt(parser.toPosition(19,1))) setupAndTeardown } - - @Test - def issue_1() { - val plsql = ''' - create or replace package body test_expect_not_to_be_null - is - gc_object_name constant varchar2(30) := 't_not_to_be_null_test'; - gc_nested_table_name constant varchar2(30) := 'tt_not_to_be_null_test'; - gc_varray_name constant varchar2(30) := 'tv_not_to_be_null_test'; - - procedure cleanup_expectations - is - begin - ut3.ut_expectation_processor.clear_expectations(); - end; - - procedure create_types - is - pragma autonomous_transaction; - begin - execute immediate 'create type '||gc_object_name||' is object (dummy number)'; - execute immediate ' create type '||gc_nested_table_name||' is table of number'; - execute immediate ' - create type '||gc_varray_name||' is varray(1) of number'; - end; - - procedure drop_types - is - pragma autonomous_transaction; - begin - execute immediate 'drop type '||gc_object_name; - execute immediate ' drop type '||gc_nested_table_name; - execute immediate ' - drop type '||gc_varray_name; - end; - - procedure blob_not_null - is - begin - --Act - execute immediate expectations_helpers.unary_expectation_block('not_to_be_null', 'blob', 'to_blob(''abc'')'); - --Assert - ut.expect(anydata.convertCollection(ut3.ut_expectation_processor.get_failed_expectations())).to_be_empty(); - end; - - --and so on... - - end; - ''' - val parser = new UtplsqlParser(plsql) - Assert.assertEquals("test_expect_not_to_be_null.cleanup_expectations", parser.getPathAt(parser.toPosition(7,1))) - Assert.assertEquals("test_expect_not_to_be_null.create_types", parser.getPathAt(parser.toPosition(13,1))) - // was: '||gc_varray_name||'.drop_types - Assert.assertEquals("test_expect_not_to_be_null.drop_types", parser.getPathAt(parser.toPosition(23,1))) - // was: '||gc_varray_name||'.blob_not_null - Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(33,1))) - } - - @Test - def issue_7() { - val plsql = ''' - create or replace package test_expect_not_to_be_null - is - --%suite(expectations - not_to_be_null) - --%suitepath(utplsql.core.expectations.unary) - - --%aftereach - procedure cleanup_expectations; - - --%beforeall - procedure create_types; - - --%afterall - procedure drop_types; - - --%test(Gives success for not null blob) - procedure blob_not_null; - - --%test(Gives success for blob with length 0) - procedure blob_0_length; - - -- ... - end test_expect_not_to_be_null; - / - ''' - val parser = new UtplsqlParser(plsql) - // was: test_expect_not_to_be_null.create_types - Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(13,26))) - } @Test - def testProcedure() { + def procedure() { val plsql = ''' create or replace procedure z is @@ -234,7 +143,7 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testFunction() { + def function() { val plsql = ''' create or replace procedure z is @@ -254,7 +163,7 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testType() { + def type() { val plsql = ''' create or replace type t force is object ( @@ -272,7 +181,7 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testTypeBody() { + def typeBody() { val plsql = ''' create or replace type body t force is member procedure p(self in t) is @@ -288,7 +197,7 @@ class UtplsqlParserTest extends AbstractJdbcTest { } @Test - def testUnknown() { + def unknown() { val plsql = ''' create or replace unknown u is begin @@ -299,6 +208,90 @@ class UtplsqlParserTest extends AbstractJdbcTest { val parser = new UtplsqlParser(plsql) Assert.assertEquals(null, parser.getObjectAt(0)) } + + @Test + def void StartLineSpec() { + val plsql = ''' + CREATE OR REPLACE PACKAGE junit_utplsql_test1_pkg is + --%suite(JUnit testing) + --%suitepath(a) + + --%context(test context) + + --%test(test 1 - OK) + PRoCeDURE test_1_ok; + + --%test(test 2 - NOK) + PROCEDURE test_2_nok; + + --%test(test 3 - disabled) + --%disabled + PROCEDURE test_3_disabled; + + --%test(test 4 - errored) + PROCEDURE test_4_errored; + + --%test(test 5 - warnings) + PROCEDURE test_5_warnings; + --%endcontext + + function my_Func (p IN number) RETURN BOOLEAN; + END; + ''' + val parser = new UtplsqlParser(plsql) + val first = parser.getLineOf('test_1_ok') + Assert.assertEquals(8, first) + val last = parser.getLineOf('test_5_warnings') + Assert.assertEquals(21, last) + } + + @Test + def void StartLineBody() { + val plsql = ''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test1_pkg IS + PROCEDURE test_1_ok IS + BEGIN + dbms_output.put_line('start test 1'); + dbms_session.sleep(1); + ut.expect(1).to_equal(1); + dbms_output.put_line('end test 1'); + END; + + PROCEDURE test_2_nok IS + BEGIN + dbms_output.put_line('start test 2'); + dbms_session.sleep(2); + ut.expect(1, 'first assert.').to_equal(2); + ut.expect(1, 'second assert.').to_equal(2); + dbms_output.put_line('end test 2'); + END; + + PROCEDURE test_3_disabled IS + BEGIN + NULL; + END; + + PROCEDURE test_4_errored IS + BEGIN + EXECUTE IMMEDIATE 'bla bla'; + END; + + PROCEDURE test_5_warnings IS + BEGIN + COMMIT; -- will raise a warning + ut.expect(1).to_equal(1); + END; + FUNCTION my_Func (p IN number) RETURN BOOLEAN IS + RETURN TRUE; + END; + END; + ''' + val parser = new UtplsqlParser(plsql) + val first = parser.getLineOf('test_1_ok') + Assert.assertEquals(2, first) + val last = parser.getLineOf('test_5_warnings') + Assert.assertEquals(29, last) + } } diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferenceModelTest.xtend similarity index 73% rename from sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend rename to sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferenceModelTest.xtend index 206d690b..4b575fca 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferenceModelTest.xtend @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.tests +package org.utplsql.sqldev.test.preference import org.junit.Assert import org.junit.Test @@ -22,17 +22,27 @@ import org.utplsql.sqldev.model.preference.PreferenceModel class PreferenceModelTest { @Test - def testDefaultValues() { + def defaultValues() { val PreferenceModel model = PreferenceModel.getInstance(null) + Assert.assertTrue(model.useRealtimeReporter) Assert.assertTrue(model.unsharedWorksheet) Assert.assertFalse(model.resetPackage) Assert.assertFalse(model.clearScreen) Assert.assertTrue(model.autoExecute) Assert.assertFalse(model.checkRunUtplsqlTest) + Assert.assertEquals(model.numberOfRunsInHistory, 10) + Assert.assertFalse(model.showDisabledCounter) + Assert.assertFalse(model.showWarningsCounter) + Assert.assertFalse(model.showInfoCounter) + Assert.assertFalse(model.showWarningIndicator) + Assert.assertFalse(model.showInfoIndicator) + Assert.assertFalse(model.isShowTestDescription) + Assert.assertTrue(model.syncDetailTab) Assert.assertEquals("test_", model.testPackagePrefix) Assert.assertEquals("", model.testPackageSuffix) Assert.assertEquals("", model.testUnitPrefix) Assert.assertEquals("", model.testUnitSuffix) + Assert.assertEquals(1, model.numberOfTestsPerUnit) Assert.assertFalse(model.checkGenerateUtplsqlTest) Assert.assertTrue(model.generateComments) Assert.assertFalse(model.disableTests) @@ -40,6 +50,7 @@ class PreferenceModelTest { Assert.assertEquals(3, model.indentSpaces) Assert.assertTrue(model.generateFiles) Assert.assertEquals(PreferenceModel.DEFAULT_OUTPUT_DIRECTORY, model.outputDirectory) + Assert.assertEquals(false, model.deleteExistingFiles) Assert.assertEquals("utPLSQL", model.rootFolderInOddgenView) - } + } } diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferencePanelTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferencePanelTest.xtend new file mode 100644 index 00000000..2f5b66f9 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/preference/PreferencePanelTest.xtend @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.preference + +import java.awt.Dimension +import java.awt.Toolkit +import javax.swing.JFrame +import javax.swing.SwingUtilities +import org.junit.Test +import org.utplsql.sqldev.ui.preference.PreferencePanel + +class PreferencePanelTest { + + @Test + def void layout() { + val frame = new JFrame("Preference Panel") + SwingUtilities.invokeLater(new Runnable() { + override run() { + val panel = new PreferencePanel + frame.add(panel) + frame.preferredSize = new Dimension(600, 400) + frame.pack + val dim = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); + frame.setVisible(true) + } + }); + Thread.sleep(4 * 1000) + frame.dispose + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/runner/ExpectationTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/ExpectationTest.xtend new file mode 100644 index 00000000..7aca6152 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/ExpectationTest.xtend @@ -0,0 +1,46 @@ +package org.utplsql.sqldev.test.runner + +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.utplsql.sqldev.model.runner.Expectation + +class ExpectationTest { + var Expectation exceptionWithDescription + var Expectation exceptionWithoutDescription + + @Before + def void setup() { + exceptionWithDescription = new Expectation + exceptionWithDescription.description = '''This assert must fail''' + exceptionWithDescription.message = '''at: 1 (number) was expected to equal: 2 (number)''' + exceptionWithDescription.caller = '''"SCOTT.JUNIT_UTPLSQL_TEST1_PKG.TEST_2_NOK", line 14 ut.expect(1, 'This assert must fail').to_equal(2);''' + exceptionWithoutDescription = new Expectation + exceptionWithoutDescription.message = exceptionWithDescription.message + exceptionWithoutDescription.caller = exceptionWithDescription.caller + exceptionWithoutDescription.message = '''at: 1 (number) was expected to equal: 2 (number)''' + exceptionWithoutDescription.caller = '''"SCOTT.JUNIT_UTPLSQL_TEST1_PKG.TEST_3_NOK", line 42 ut.expect(1).to_equal(2);''' + } + + @Test + def void failedExpectationCallerLine() { + val actual = exceptionWithDescription.callerLine + val expected = new Integer(14) + Assert.assertEquals(expected, actual) + } + + @Test + def void shortFailureTextWithDescription() { + val actual = exceptionWithDescription.shortFailureText + val expected = 'This assert must fail (line 14)' + Assert.assertEquals(expected, actual) + } + + @Test + def void shortFailureTextWithoutDescription() { + val actual = exceptionWithoutDescription.shortFailureText + val expected = 'Line 42' + Assert.assertEquals(expected, actual) + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerPanelTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerPanelTest.xtend new file mode 100644 index 00000000..45b72543 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerPanelTest.xtend @@ -0,0 +1,97 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.runner + +import java.awt.Toolkit +import java.util.UUID +import javax.swing.JFrame +import javax.swing.SwingUtilities +import org.junit.Before +import org.junit.Test +import org.utplsql.sqldev.model.runner.Run +import org.utplsql.sqldev.resources.UtplsqlResources +import org.utplsql.sqldev.ui.runner.RunnerPanel + +class UtplsqlRunnerPanelTest { + var Run run + + @Before + def void setup() { + val reporterId = UUID.randomUUID().toString.replace("-", "") + run = new Run(null, reporterId, #[]) + run.startTime = "2019-06-09T13:42:42.123456" + run.counter.disabled = 0 + run.counter.success = 0 + run.counter.failure = 0 + run.counter.error = 0 + run.counter.warning = 0 + run.totalNumberOfTests = 5 + run.currentTestNumber = 0 + } + + @Test + def void showGUI() { + val start = System.currentTimeMillis + val frame = new JFrame("utPLSQL Runner Panel") + frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE; + val panel = new RunnerPanel + val gui = panel.getGUI + panel.model = run + + SwingUtilities.invokeLater(new Runnable() { + override run() { + frame.add(gui) + frame.pack + val dim = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); + frame.setVisible(true) + } + }); + + run.status="starting" + panel.update(run.reporterId) + Thread.sleep(3000); + + run.counter.success = run.counter.success + 1 + run.status="utplsql.test.a" + panel.update(run.reporterId) + Thread.sleep(500); + + run.counter.success = run.counter.success + 1 + run.status="utplsql.test.b" + panel.update(run.reporterId) + Thread.sleep(500); + + run.counter.success = run.counter.success + 1 + run.status="utplsql.test.c" + panel.update(run.reporterId) + Thread.sleep(500); + + run.counter.failure = run.counter.failure + 1 + run.status="utplsql.test.d" + panel.update(run.reporterId) + Thread.sleep(500); + + run.counter.success = run.counter.success + 1 + run.status="utplsql.test.e" + val end = System.currentTimeMillis + run.status = String.format(UtplsqlResources.getString("RUNNER_FINNISHED_TEXT"), new Double(end-start)/1000) + panel.update(run.reporterId) + Thread.sleep(2000); + frame.dispose + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.xtend new file mode 100644 index 00000000..23b840b5 --- /dev/null +++ b/sqldev/src/test/java/org/utplsql/sqldev/test/runner/UtplsqlRunnerTest.xtend @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed 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.utplsql.sqldev.test.runner + +import org.junit.AfterClass +import org.junit.BeforeClass +import org.junit.Test +import org.springframework.jdbc.BadSqlGrammarException +import org.springframework.jdbc.datasource.SingleConnectionDataSource +import org.utplsql.sqldev.runner.UtplsqlRunner +import org.utplsql.sqldev.test.AbstractJdbcTest + +class UtplsqlRunnerTest extends AbstractJdbcTest { + + @BeforeClass + def static void setup() { + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test1_pkg is + --%suite(JUnit testing) + --%suitepath(a) + /* tags annotation without parameter will raise a warning */ + --%tags + + --%context(test context) + + --%test(test 1 - OK) + PROCEDURE test_1_ok; + + --%test(test 2 - NOK) + PROCEDURE test_2_nok; + + --%test(test 3 - disabled) + --%disabled + PROCEDURE test_3_disabled; + + --%test(test 4 - errored) + PROCEDURE test_4_errored; + + --%test(test 5 - warnings) + PROCEDURE test_5_warnings; + + --%endcontext + + --%afterall + procedure print_and_raise; + END; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE BODY junit_utplsql_test1_pkg IS + PROCEDURE test_1_ok IS + BEGIN + dbms_output.put_line('start test 1'); + dbms_session.sleep(1); + ut.expect(1).to_equal(1); + dbms_output.put_line('end test 1'); + END; + + PROCEDURE test_2_nok IS + BEGIN + dbms_output.put_line('start test 2'); + dbms_session.sleep(2); + ut.expect(1, 'first assert.').to_equal(2); + ut.expect(1, 'second assert.').to_equal(2); + dbms_output.put_line('end test 2'); + END; + + PROCEDURE test_3_disabled IS + BEGIN + NULL; + END; + + PROCEDURE test_4_errored IS + BEGIN + EXECUTE IMMEDIATE 'bla bla'; + END; + + PROCEDURE test_5_warnings IS + BEGIN + COMMIT; -- will raise a warning + ut.expect(1).to_equal(1); + END; + + PROCEDURE print_and_raise IS + BEGIN + dbms_output.put_line('Now, a no_data_found exception is raised'); + dbms_output.put_line('dbms_output and error stack is reported for this suite.'); + dbms_output.put_line('A runtime error in afterall is counted as a warning.'); + RAISE no_data_found; + END; + END; + ''') + } + + @AfterClass + def static void teardown() { + try { + jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test1_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + } + + @Test + def void runTestsWithMaxTime() { + var ds1 = new SingleConnectionDataSource() + ds1.driverClassName = "oracle.jdbc.OracleDriver" + ds1.url = dataSource.url + ds1.username = dataSource.username + ds1.password = dataSource.password + var ds2 = new SingleConnectionDataSource() + ds2.driverClassName = "oracle.jdbc.OracleDriver" + ds2.url = dataSource.url + ds2.username = dataSource.username + ds2.password = dataSource.password + var runner = new UtplsqlRunner(#[":a"], ds1.connection, ds2.connection) + runner.runTestAsync + runner.producerThread.join(200000) + runner.consumerThread.join(200000) + Thread.sleep(4 * 1000) + runner.dispose + } + +} \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/UrlToolsTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/tests/UrlToolsTest.xtend deleted file mode 100644 index eee16ce3..00000000 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/UrlToolsTest.xtend +++ /dev/null @@ -1,31 +0,0 @@ -package org.utplsql.sqldev.tests - -import org.junit.Assert -import org.junit.Test -import org.utplsql.sqldev.model.URLTools - -class UrlToolsTest { - val extension URLTools urlTools = new URLTools - - @Test - def void testReplacePlusSign() { - Assert.assertEquals("+", "%2B".replaceHexChars) - Assert.assertEquals("++", "%2B%2B".replaceHexChars) - Assert.assertEquals("abc+%xyz", "abc%2B%xyz".replaceHexChars) - } - - @Test - def void testReplaceAtSign() { - Assert.assertEquals("@", "%40".replaceHexChars) - Assert.assertEquals("@@", "%40%40".replaceHexChars) - Assert.assertEquals("abc@%xyz", "abc%40%xyz".replaceHexChars) - } - - @Test - def void testReplaceAtAndPlusSign() { - Assert.assertEquals("@+", "%40%2B".replaceHexChars) - Assert.assertEquals("@+@+", "%40%2B%40%2B".replaceHexChars) - Assert.assertEquals("abc@+%xyz", "abc%40%2B%xyz".replaceHexChars) - } - -}