diff --git a/sqldev/extension.xml b/sqldev/extension.xml index db4d9ff1..ec85bea3 100644 --- a/sqldev/extension.xml +++ b/sqldev/extension.xml @@ -46,7 +46,7 @@ - + @@ -56,6 +56,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -66,12 +96,20 @@ Code-Editor + + + ${MENU_GENERATE_TEST_LABEL} + res:/org/utplsql/sqldev/resources/images/oddgen.png + Code-Editor + + + @@ -82,10 +120,11 @@
+
- +
+ + + +
+ +
+
+
org/utplsql/sqldev/resources/accelerators.xml diff --git a/sqldev/pom.xml b/sqldev/pom.xml index 6bbd1634..26c8f61d 100644 --- a/sqldev/pom.xml +++ b/sqldev/pom.xml @@ -157,6 +157,12 @@ spring-jdbc 5.1.0.RELEASE + + org.oddgen + org.oddgen.sqldev + 0.3.0 + provided + junit junit diff --git a/sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend new file mode 100644 index 00000000..2c47302b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/DirectoryChooser.xtend @@ -0,0 +1,56 @@ +/* + * 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 + +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 + +class DirectoryChooser { + val static Logger logger = Logger.getLogger(DirectoryChooser.name) + + def static choose (JFrame parentFrame, String title, String initialDirectory) { + logger.finest('''parantFrame: «parentFrame»''') + var String ret = null + val chooser = new JFileChooser() + chooser.currentDirectory = new File(initialDirectory) + chooser.dialogTitle = title + chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + chooser.acceptAllFileFilterUsed = false + if (chooser.showOpenDialog(parentFrame) == JFileChooser.APPROVE_OPTION) { + ret = chooser.selectedFile.absolutePath + } + return ret + } + + def static void choose (JFrame parentFrame, String title, JTextField textField) { + val dir = choose(parentFrame, title, textField.text) + if (dir !== null) { + textField.text = dir + } + } + + 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/PreferencePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend index d49d3de5..a235e424 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend @@ -15,44 +15,136 @@ */ package org.utplsql.sqldev +import javax.swing.BorderFactory import javax.swing.JCheckBox +import javax.swing.JPanel +import javax.swing.JSpinner +import javax.swing.JTextField +import javax.swing.SpinnerNumberModel import oracle.ide.panels.DefaultTraversablePanel import oracle.ide.panels.TraversableContext 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 javax.swing.JButton +import java.awt.event.ActionEvent +import java.awt.event.ActionListener class PreferencePanel extends DefaultTraversablePanel { + val JPanel runTestPanel = new JPanel(); 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 generateTestPanel = new JPanel(); + val JTextField testPackagePrefixTextField = new JTextField + val JTextField testPackageSuffixTextField = new JTextField + val JTextField testUnitPrefixTextField = new JTextField + val JTextField testUnitSuffixTextField = new JTextField + val SpinnerNumberModel numberOfTestsPerUnitModel = new SpinnerNumberModel(1, 1, 10, 1); + val JSpinner numberOfTestsPerUnitSpinner = new JSpinner(numberOfTestsPerUnitModel); + val JCheckBox checkGenerateUtplsqlTestCheckBox = new JCheckBox + val JCheckBox generateCommentsCheckBox = new JCheckBox + val JCheckBox disableTestsCheckBox = new JCheckBox + val JTextField suitePathTextField = new JTextField + val SpinnerNumberModel indentSpacesModel = new SpinnerNumberModel(1, 1, 8, 1); + val JSpinner indentSpacesSpinner = new JSpinner(indentSpacesModel); + val JPanel oddgenPanel = new JPanel(); + val JTextField rootFolderInOddgenViewTextField = new JTextField + val JCheckBox generateFilesCheckBox = new JCheckBox + val JTextField outputDirectoryTextField = new JTextField + val JButton outputDirectoryBrowse = new JButton(); new() { layoutControls() } def private layoutControls() { - val FieldLayoutBuilder builder = new FieldLayoutBuilder(this) - builder.alignLabelsLeft = true - builder.add( - builder.field.label.withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")).component( + // 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( unsharedWorksheetCheckBox)) - builder.add( - builder.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component( + b1.add( + b1.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component( resetPackageCheckBox)) - builder.add( - builder.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component( + b1.add( + b1.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component( clearScreenCheckBox)) - builder.add( - builder.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component( + b1.add( + b1.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component( autoExecuteCheckBox)) - builder.add( - builder.field.label.withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")).component( + b1.add( + b1.field.label.withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")).component( checkRunUtplsqlTestCheckBox)) + // generate test group + generateTestPanel.border = BorderFactory.createTitledBorder(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL")) + val FieldLayoutBuilder b2 = new FieldLayoutBuilder(generateTestPanel) + b2.alignLabelsLeft = true + b2.add( + b2.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( + testPackageSuffixTextField)) + b2.add( + b2.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( + testUnitSuffixTextField)) + b2.add( + b2.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( + generateCommentsCheckBox)) + b2.add( + b2.field.label.withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL")).component( + disableTestsCheckBox)) + b2.add( + b2.field.label.withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL")).component( + suitePathTextField)) + b2.add( + b2.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( + checkGenerateUtplsqlTestCheckBox)) + // 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( + rootFolderInOddgenViewTextField)) + b3.add( + b3.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL")).component( + generateFilesCheckBox)) + b3.add( + b3.field.label.withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL")).component( + outputDirectoryTextField).button(outputDirectoryBrowse).withText("Bro&wse")) + + // putting everything together + val FieldLayoutBuilder builder = new FieldLayoutBuilder(this) + builder.alignLabelsLeft = true + builder.addVerticalField("", runTestPanel) + builder.addVerticalField("", generateTestPanel) + builder.addVerticalField("", oddgenPanel) builder.addVerticalSpring + + // register action listener for directory chooser + outputDirectoryBrowse.addActionListener(new ActionListener() { + override actionPerformed(ActionEvent event) { + DirectoryChooser.choose(null, UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"), + outputDirectoryTextField) + } + }) } override onEntry(TraversableContext traversableContext) { @@ -62,6 +154,19 @@ class PreferencePanel extends DefaultTraversablePanel { clearScreenCheckBox.selected = info.clearScreen autoExecuteCheckBox.selected = info.autoExecute checkRunUtplsqlTestCheckBox.selected = info.checkRunUtplsqlTest + testPackagePrefixTextField.text = info.testPackagePrefix + testPackageSuffixTextField.text = info.testPackageSuffix + testUnitPrefixTextField.text = info.testUnitPrefix + testUnitSuffixTextField.text = info.testUnitSuffix + numberOfTestsPerUnitSpinner.value = info.numberOfTestsPerUnit + checkGenerateUtplsqlTestCheckBox.selected = info.checkGenerateUtplsqlTest + generateCommentsCheckBox.selected = info.generateComments + disableTestsCheckBox.selected = info.disableTests + suitePathTextField.text = info.suitePath + indentSpacesSpinner.value = info.indentSpaces + rootFolderInOddgenViewTextField.text = info.rootFolderInOddgenView + generateFilesCheckBox.selected = info.generateFiles + outputDirectoryTextField.text = info.outputDirectory super.onEntry(traversableContext) } @@ -72,6 +177,19 @@ class PreferencePanel extends DefaultTraversablePanel { info.clearScreen = clearScreenCheckBox.selected info.autoExecute = autoExecuteCheckBox.selected info.checkRunUtplsqlTest = checkRunUtplsqlTestCheckBox.selected + info.testPackagePrefix = testPackagePrefixTextField.text + info.testPackageSuffix = testPackageSuffixTextField.text + info.testUnitPrefix = testUnitPrefixTextField.text + info.testUnitSuffix = testUnitSuffixTextField.text + info.numberOfTestsPerUnit = numberOfTestsPerUnitSpinner.value as Integer + info.checkGenerateUtplsqlTest = checkGenerateUtplsqlTestCheckBox.selected + info.generateComments = generateCommentsCheckBox.selected + info.disableTests = disableTestsCheckBox.selected + info.suitePath = suitePathTextField.text + info.indentSpaces = indentSpacesSpinner.value as Integer + info.rootFolderInOddgenView = rootFolderInOddgenViewTextField.text + info.generateFiles = generateFilesCheckBox.selected + info.outputDirectory = outputDirectoryTextField.text super.onExit(traversableContext) } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend b/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend index 59cdf2dd..44e60e90 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend @@ -117,4 +117,12 @@ class UtplsqlWorksheet { thread.start } + def static void openWithCode(String code, String connectionName) { + val worksheet = OpenWorksheetWizard.openNewTempWorksheet(connectionName, code) as Worksheet + if (connectionName === null) { + worksheet.comboConnection = null + } + WorksheetUtil.setWorksheetTabName(worksheet.context.node.URL, UtplsqlResources.getString("WORKSHEET_TITLE")) + } + } 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 3ba5fedf..be00bcf3 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend @@ -17,6 +17,7 @@ import java.sql.Connection import java.util.List +import org.oddgen.sqldev.generators.model.Node import org.springframework.dao.DataAccessException import org.springframework.dao.EmptyResultDataAccessException import org.springframework.jdbc.core.BeanPropertyRowMapper @@ -175,5 +176,90 @@ class UtplsqlDao { val result = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Annotation), #[owner, objectName]) return result } + + /** + * Gets a list of public units in the object type + * + * @param objectType expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE + * @param objectName name of the object + * @return list of the public units in the object type + * @throws DataAccessException if there is a problem + */ + def List units(String objectType, String objectName) { + if (objectType == "PACKAGE" || objectType == "TYPE") { + val sql = ''' + SELECT procedure_name + FROM user_procedures + WHERE object_type = ? + AND object_name = ? + AND procedure_name IS NOT NULL + GROUP BY procedure_name + ORDER BY min(subprogram_id) + ''' + val result = jdbcTemplate.queryForList(sql, String, #[objectType, objectName]) + return result + } else { + return #[objectName] + } + } + /** + * Gets a list of oddgen's nodes as candidates to create utPLSQL test packages. + * Candidates are packages, types, functions and procedures in the current user. + * + * This functions must be called from an oddgen generator only, since the Node is not + * defined in the utPLSQL extension. + * + * @param objectType expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE + * @return list of the oddgen nodes for the requested object type + * @throws DataAccessException if there is a problem + */ + def List testables(String objectType) { + var String sql; + if (objectType == "PACKAGE") { + sql = ''' + SELECT DISTINCT + object_type || '.' || object_name AS id, + object_type AS parent_id, + 1 AS leaf, + 1 AS generatable, + 1 AS multiselectable + FROM user_procedures + WHERE object_type = ? + AND procedure_name IS NOT NULL + AND object_name NOT IN ( + SELECT object_name + FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(USER, 'PACKAGE')) + ) + ''' + } + else if (objectType == "TYPE") { + sql = ''' + SELECT DISTINCT + object_type || '.' || object_name AS id, + object_type AS parent_id, + 1 AS leaf, + 1 AS generatable, + 1 AS multiselectable + FROM user_procedures + WHERE object_type = ? + AND procedure_name IS NOT NULL + ''' + } + else { + sql = ''' + SELECT object_type || '.' || object_name AS id, + object_type AS parent_id, + 1 AS leaf, + 1 AS generatable, + 1 AS multiselectable + FROM user_objects + WHERE object_type = ? + AND generated = 'N' + ''' + } + val jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) + val nodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Node), #[objectType]) + return nodes + } } \ 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 e3454749..b5d683e2 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend @@ -37,6 +37,8 @@ import org.utplsql.sqldev.UtplsqlWorksheet import org.utplsql.sqldev.dal.UtplsqlDao import org.utplsql.sqldev.model.URLTools import org.utplsql.sqldev.model.preference.PreferenceModel +import org.utplsql.sqldev.oddgen.TestTemplate +import org.utplsql.sqldev.oddgen.model.GenContext import org.utplsql.sqldev.parser.UtplsqlParser class UtplsqlController implements Controller { @@ -44,12 +46,17 @@ class UtplsqlController implements Controller { val extension URLTools urlTools = new URLTools public static int UTLPLSQL_TEST_CMD_ID = Ide.findCmdID("utplsql.test") + public static int UTLPLSQL_GENERATE_CMD_ID = Ide.findCmdID("utplsql.generate") public static final IdeAction UTLPLSQL_TEST_ACTION = IdeAction.get(UtplsqlController.UTLPLSQL_TEST_CMD_ID) + public static final IdeAction UTLPLSQL_GENERATE_ACTION = IdeAction.get(UtplsqlController.UTLPLSQL_GENERATE_CMD_ID) override handleEvent(IdeAction action, Context context) { if (action.commandId === UtplsqlController.UTLPLSQL_TEST_CMD_ID) { runTest(context) return true + } else if (action.commandId === UtplsqlController.UTLPLSQL_GENERATE_CMD_ID) { + generateTest(context) + return true } return false } @@ -110,6 +117,33 @@ class UtplsqlController implements Controller { } } return true + } else if (action.commandId === UTLPLSQL_GENERATE_CMD_ID) { + action.enabled = false + // enable if generation is possible + val view = context.view + if (view instanceof Editor) { + val component = view.defaultFocusComponent + if (component instanceof JEditorPane) { + val preferences = PreferenceModel.getInstance(Preferences.preferences) + if (preferences.checkGenerateUtplsqlTest) { + val parser = new UtplsqlParser(component.text) + action.enabled = parser.getObjectAt(component.caretPosition) !== null + } else { + action.enabled = true + } + } + } else if (view instanceof DBNavigatorWindow) { + // multiselection is not supported, use oddgen to generte tests for multiple objects + if (context.selection.length == 1) { + val element = context.selection.get(0) + if (element instanceof PlSqlNode) { + val ot = element.objectType + if (ot.startsWith("PACKAGE") || ot.startsWith("TYPE") || ot == "FUNCTION" || ot == "PROCEDURE") { + action.enabled = true + } + } + } + } } return false } @@ -159,6 +193,36 @@ class UtplsqlController implements Controller { logger.fine('''url: «url»''') return url } + + private def void populateGenContext(GenContext genContext, PreferenceModel preferences) { + genContext.generateFiles = preferences.generateFiles + genContext.outputDirectory = preferences.outputDirectory + genContext.testPackagePrefix = preferences.testPackagePrefix.toLowerCase + genContext.testPackageSuffix = preferences.testPackageSuffix.toLowerCase + genContext.testUnitPrefix = preferences.testUnitPrefix.toLowerCase + genContext.testUnitSuffix = preferences.testUnitSuffix.toLowerCase + genContext.numberOfTestsPerUnit = preferences.numberOfTestsPerUnit + genContext.generateComments = preferences.generateComments + genContext.disableTests = preferences.disableTests + genContext.suitePath = preferences.suitePath.toLowerCase + genContext.indentSpaces = preferences.indentSpaces + } + + private def getGenContext(Context context) { + val connectionName = context.URL.connectionName + val genContext = new GenContext + if (Connections.instance.isConnectionOpen(connectionName)) { + genContext.conn = Connections.instance.getConnection(connectionName) + val element = context.selection.get(0) + if (element instanceof PlSqlNode) { + genContext.objectType = element.objectType.replace(" BODY", "") + genContext.objectName = element.objectName + val preferences = PreferenceModel.getInstance(Preferences.preferences) + populateGenContext(genContext, preferences) + } + } + return genContext + } def runTest(Context context) { val view = context.view @@ -194,4 +258,48 @@ class UtplsqlController implements Controller { } } } + + def generateTest(Context context) { + val view = context.view + val node = context.node + logger.finer('''Generate utPLSQL test from view «view?.class?.name» and node «node?.class?.name».''') + if (view instanceof Editor) { + val component = view.defaultFocusComponent + if (component instanceof JEditorPane) { + var String connectionName = null; + if (node instanceof DatabaseSourceNode) { + connectionName = node.connectionName + } else if (view instanceof Worksheet) { + connectionName = view.connectionName + } + if (connectionName !== null) { + if (Connections.instance.isConnectionOpen(connectionName)) { + val genContext = new GenContext + genContext.conn = Connections.instance.getConnection(connectionName) + val parser = new UtplsqlParser(component.text) + val position = component.caretPosition + val obj = parser.getObjectAt(position) + if (obj !== null) { + genContext.objectType = obj.type.toUpperCase + genContext.objectName = obj.name.toUpperCase + val preferences = PreferenceModel.getInstance(Preferences.preferences) + populateGenContext(genContext, preferences) + val testTemplate = new TestTemplate(genContext) + val code = testTemplate.generate.toString + UtplsqlWorksheet.openWithCode(code, connectionName) + } + } + } + } + + } else if (view instanceof DBNavigatorWindow) { + val url=context.URL + if (url !== null) { + val connectionName = url.connectionName + val testTemplate = new TestTemplate(context.genContext) + val code = testTemplate.generate.toString + UtplsqlWorksheet.openWithCode(code, connectionName) + } + } + } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend index f9bc93d2..96b71db1 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend @@ -23,6 +23,7 @@ import org.utplsql.sqldev.model.ut.Annotation @Accessors class PlsqlObject extends AbstractModel { String name + String type Integer position List annotations } \ 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 3a06acd5..db7d71f4 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 @@ -15,12 +15,14 @@ */ package org.utplsql.sqldev.model.preference +import java.io.File import oracle.javatools.data.HashStructure import oracle.javatools.data.HashStructureAdapter import oracle.javatools.data.PropertyStorage import org.eclipse.xtext.xbase.lib.util.ToStringBuilder class PreferenceModel extends HashStructureAdapter { + public static final String DEFAULT_OUTPUT_DIRECTORY = '''«System.getProperty("user.home")»«File.separator»utplsql«File.separator»generated''' static final String DATA_KEY = "utplsql" private new(HashStructure hash) { @@ -36,7 +38,20 @@ class PreferenceModel extends HashStructureAdapter { 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_TEST_PACKAGE_PREFIX = "testPackagePrefix" + static final String KEY_TEST_PACKAGE_SUFFIX = "testPackageSuffix" + static final String KEY_TEST_UNIT_PREFIX = "testUnitPrefix" + static final String KEY_TEST_UNIT_SUFFIX = "testUnitSuffix" + static final String KEY_NUMBER_OF_TESTS_PER_UNIT = "numberOfTestsPerUnit" + static final String KEY_CHECK_GENERATE_UTPLSQL_TEST = "checkGenerateUtplsqlTest" + static final String KEY_GENERATE_COMMENTS = "generateComments" + static final String KEY_DISABLE_TESTS = "disableTests" + static final String KEY_SUITE_PATH="suitePath" + static final String KEY_INDENT_SPACES="indentSpaces" + static final String KEY_GENERATE_FILES="generateFiles" + static final String KEY_OUTPUT_DIRECTORY = "outputDirectory" + static final String KEY_ROOT_FOLDER_IN_ODDGEN_VIEW = "rootFolderInOddgenView" + def isUnsharedWorksheet() { return getHashStructure.getBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, true) } @@ -73,11 +88,115 @@ class PreferenceModel extends HashStructureAdapter { return getHashStructure.getBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, false) } - def setCheckRunUtplsqlTest(boolean autoExecute) { - getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, autoExecute) + def setCheckRunUtplsqlTest(boolean checkRunUtplsqlTest) { + getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, checkRunUtplsqlTest) + } + + def getTestPackagePrefix() { + return getHashStructure.getString(PreferenceModel.KEY_TEST_PACKAGE_PREFIX, "test_") + } + + def setTestPackagePrefix(String testPackagePrefix) { + getHashStructure.putString(PreferenceModel.KEY_TEST_PACKAGE_PREFIX, testPackagePrefix) + } + + def getTestPackageSuffix() { + return getHashStructure.getString(PreferenceModel.KEY_TEST_PACKAGE_SUFFIX, "") + } + + def setTestPackageSuffix(String testPackageSuffix) { + getHashStructure.putString(PreferenceModel.KEY_TEST_PACKAGE_SUFFIX, testPackageSuffix) + } + + def getTestUnitPrefix() { + return getHashStructure.getString(PreferenceModel.KEY_TEST_UNIT_PREFIX, "") + } + + def setTestUnitPrefix(String testUnitPrefix) { + getHashStructure.putString(PreferenceModel.KEY_TEST_UNIT_PREFIX, testUnitPrefix) + } + + def getTestUnitSuffix() { + return getHashStructure.getString(PreferenceModel.KEY_TEST_UNIT_SUFFIX, "") + } + + def setTestUnitSuffix(String testUnitSuffix) { + getHashStructure.putString(PreferenceModel.KEY_TEST_UNIT_SUFFIX, testUnitSuffix) + } + + def getNumberOfTestsPerUnit() { + return getHashStructure.getInt(PreferenceModel.KEY_NUMBER_OF_TESTS_PER_UNIT, 1) + } + + def setNumberOfTestsPerUnit(int numberOfTestsPerUnit) { + getHashStructure.putInt(PreferenceModel.KEY_NUMBER_OF_TESTS_PER_UNIT, numberOfTestsPerUnit) + } + + def isCheckGenerateUtplsqlTest() { + return getHashStructure.getBoolean(PreferenceModel.KEY_CHECK_GENERATE_UTPLSQL_TEST, false) + } + + def setCheckGenerateUtplsqlTest(boolean checkGenerateUtplsqlTest) { + getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_GENERATE_UTPLSQL_TEST, checkGenerateUtplsqlTest) } + def isGenerateComments() { + return getHashStructure.getBoolean(PreferenceModel.KEY_GENERATE_COMMENTS, true) + } + + def setGenerateComments(boolean generateComments) { + getHashStructure.putBoolean(PreferenceModel.KEY_GENERATE_COMMENTS, generateComments) + } + + def isDisableTests() { + return getHashStructure.getBoolean(PreferenceModel.KEY_DISABLE_TESTS, false) + } + + def setDisableTests(boolean disableTests) { + getHashStructure.putBoolean(PreferenceModel.KEY_DISABLE_TESTS, disableTests) + } + + def getSuitePath() { + return getHashStructure.getString(PreferenceModel.KEY_SUITE_PATH, "alltests") + } + + def setSuitePath(String suitePath) { + getHashStructure.putString(PreferenceModel.KEY_SUITE_PATH, suitePath) + } + + def getIndentSpaces() { + return getHashStructure.getInt(PreferenceModel.KEY_INDENT_SPACES, 3) + } + def setIndentSpaces(int indentSpaces) { + getHashStructure.putInt(PreferenceModel.KEY_INDENT_SPACES, indentSpaces) + } + + def isGenerateFiles() { + return getHashStructure.getBoolean(PreferenceModel.KEY_GENERATE_FILES, true) + } + + def setGenerateFiles(boolean generateFiles) { + getHashStructure.putBoolean(PreferenceModel.KEY_GENERATE_FILES, generateFiles) + } + + def getOutputDirectory() { + return getHashStructure.getString(PreferenceModel.KEY_OUTPUT_DIRECTORY, DEFAULT_OUTPUT_DIRECTORY) + } + + def setOutputDirectory(String outputDirectory) { + val dir = if (outputDirectory.empty) {DEFAULT_OUTPUT_DIRECTORY} else {outputDirectory} + getHashStructure.putString(PreferenceModel.KEY_OUTPUT_DIRECTORY, dir) + } + + def getRootFolderInOddgenView() { + return getHashStructure.getString(PreferenceModel.KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, "utPLSQL") + } + + def setRootFolderInOddgenView(String rootFolder) { + val folder = if (rootFolder.empty) {"utPLSQL"} else {rootFolder} + getHashStructure.putString(PreferenceModel.KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, folder) + } override toString() { new ToStringBuilder(this).addAllFields.toString diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend new file mode 100644 index 00000000..83d389f6 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend @@ -0,0 +1,262 @@ +/* + * 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.oddgen + +import java.io.File +import java.sql.Connection +import java.util.ArrayList +import java.util.HashMap +import java.util.LinkedHashMap +import java.util.List +import oracle.ide.config.Preferences +import org.oddgen.sqldev.generators.OddgenGenerator2 +import org.oddgen.sqldev.generators.model.Node +import org.oddgen.sqldev.generators.model.NodeTools +import org.oddgen.sqldev.plugin.templates.TemplateTools +import org.utplsql.sqldev.dal.UtplsqlDao +import org.utplsql.sqldev.model.preference.PreferenceModel +import org.utplsql.sqldev.resources.UtplsqlResources +import org.utplsql.sqldev.oddgen.model.GenContext + +class TestGenerator implements OddgenGenerator2 { + + public static val YES = "Yes" + public static val NO = "No" + + public static var GENERATE_FILES = "Generate files?" + public static var OUTPUT_DIRECTORY = "Output directory" + public static var TEST_PACKAGE_PREFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL") + public static var TEST_PACKAGE_SUFFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL") + public static var TEST_UNIT_PREFIX = UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL") + public static var TEST_UNIT_SUFFIX = UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL") + public static var NUMBER_OF_TESTS_PER_UNIT = UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL") + public static var GENERATE_COMMENTS = "Generate comments?" + public static var DISABLE_TESTS = "Disable tests?" + public static var SUITE_PATH = "Suite Path" + public static var INDENT_SPACES = "Indent Spaces" + + val extension NodeTools nodeTools = new NodeTools + val extension TemplateTools templateTools = new TemplateTools + val consoleOutput = new ArrayList(); + + private def toContext(Node node) { + val context = new GenContext() + context.objectType = node.toObjectType + context.objectName = node.toObjectName + context.generateFiles = node.params.get(GENERATE_FILES) == YES + context.outputDirectory = node.params.get(OUTPUT_DIRECTORY) + context.testPackagePrefix = node.params.get(TEST_PACKAGE_PREFIX).toLowerCase + context.testPackageSuffix = node.params.get(TEST_PACKAGE_SUFFIX).toLowerCase + context.testUnitPrefix = node.params.get(TEST_UNIT_PREFIX).toLowerCase + context.testUnitSuffix = node.params.get(TEST_UNIT_SUFFIX).toLowerCase + context.numberOfTestsPerUnit = Integer.valueOf(node.params.get(NUMBER_OF_TESTS_PER_UNIT)) + context.generateComments = node.params.get(GENERATE_COMMENTS) == YES + context.disableTests = node.params.get(DISABLE_TESTS) == YES + context.suitePath = node.params.get(SUITE_PATH).toLowerCase + context.indentSpaces = Integer.valueOf(node.params.get(INDENT_SPACES)) + return context + } + + private def void resetConsoleOutput() { + consoleOutput.clear + } + + private def void saveConsoleOutput(String s) { + consoleOutput.add(s) + } + + private def String deleteFile(File file) { + var String ret + try { + if (file.delete) { + ret = '''«file.absoluteFile» deleted.''' + } else { + ret = '''Cannot delete file «file.absoluteFile».''' + } + } catch (Exception e) { + ret = '''Cannot delete file «file.absoluteFile». Got the following error message: «e.message».''' + } + return ret + } + + private def deleteFiles(String directory) ''' + «val dir = new File(directory)» + «FOR file: dir.listFiles» + «IF !file.directory» + «IF file.name.endsWith(".pks") || file.name.endsWith(".pkb")» + «file.deleteFile» + «ENDIF» + «ENDIF» + «ENDFOR» + ''' + + override isSupported(Connection conn) { + var ret = false + if (conn !== null) { + if (conn.metaData.databaseProductName.startsWith("Oracle")) { + if (conn.metaData.databaseMajorVersion == 11) { + if (conn.metaData.databaseMinorVersion >= 2) { + ret = true + } + } else if (conn.metaData.databaseMajorVersion > 11) { + ret = true + } + } + } + return ret + } + + override getName(Connection conn) { + return "Generate test" + } + + override getDescription(Connection conn) { + return "Generates utPLSQL test packages for public units in packages, types, functions and procedures found in the current schema." + } + + override getFolders(Connection conn) { + val preferences = PreferenceModel.getInstance(Preferences.preferences) + val folders = new ArrayList + for (f : preferences.rootFolderInOddgenView.split(",").filter[!it.empty]) { + folders.add(f.trim) + } + return folders + } + + override getHelp(Connection conn) { + return "

not yet available

" + } + + override getNodes(Connection conn, String parentNodeId) { + val preferences = PreferenceModel.getInstance(Preferences.preferences) + val params = new LinkedHashMap() + params.put(GENERATE_FILES, if (preferences.generateFiles) {YES} else {NO}) + params.put(OUTPUT_DIRECTORY, preferences.outputDirectory) + params.put(TEST_PACKAGE_PREFIX, preferences.testPackagePrefix) + params.put(TEST_PACKAGE_SUFFIX, preferences.testPackageSuffix) + params.put(TEST_UNIT_PREFIX, preferences.testUnitPrefix) + params.put(TEST_UNIT_SUFFIX, preferences.testUnitSuffix) + params.put(NUMBER_OF_TESTS_PER_UNIT, String.valueOf(preferences.numberOfTestsPerUnit)) + params.put(GENERATE_COMMENTS, if(preferences.generateComments) {YES} else {NO}) + params.put(DISABLE_TESTS, if (preferences.disableTests) {YES} else {NO}) + params.put(SUITE_PATH, preferences.suitePath) + params.put(INDENT_SPACES, String.valueOf(preferences.indentSpaces)) + if (parentNodeId === null || parentNodeId.empty) { + val packageNode = new Node + packageNode.id = "PACKAGE" + packageNode.params = params + packageNode.leaf = false + packageNode.generatable = true + packageNode.multiselectable = true + val typeNode = new Node + typeNode.id = "TYPE" + typeNode.params = params + typeNode.leaf = false + typeNode.generatable = true + typeNode.multiselectable = true + val functionNode = new Node + functionNode.id = "FUNCTION" + functionNode.params = params + functionNode.leaf = false + functionNode.generatable = true + functionNode.multiselectable = true + val procedureNode = new Node + procedureNode.id = "PROCEDURE" + procedureNode.params = params + procedureNode.leaf = false + procedureNode.generatable = true + procedureNode.multiselectable = true + return #[packageNode, typeNode, functionNode, procedureNode] + } else { + val UtplsqlDao dao = new UtplsqlDao(conn) + val nodes = dao.testables(parentNodeId) + for (node : nodes) { + node.params = params + } + return nodes + } + } + + override getLov(Connection conn, LinkedHashMap params, List nodes) { + val lov = new HashMap>() + lov.put(NUMBER_OF_TESTS_PER_UNIT, #["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) + lov.put(INDENT_SPACES, #["1", "2", "3", "4", "5", "6", "7", "8"]) + lov.put(GENERATE_COMMENTS, #[YES, NO]) + lov.put(DISABLE_TESTS, #[YES, NO]) + lov.put(GENERATE_FILES, #[YES, NO]) + return lov + } + + override getParamStates(Connection conn, LinkedHashMap params, List nodes) { + val paramStates = new HashMap + paramStates.put(OUTPUT_DIRECTORY, params.get(GENERATE_FILES) == YES) + return paramStates + } + + override generateProlog(Connection conn, List nodes) ''' + «val generateFiles = nodes.get(0).params.get(GENERATE_FILES) == YES» + «val outputDirectory = nodes.get(0).params.get(OUTPUT_DIRECTORY)» + «IF generateFiles» + «resetConsoleOutput» + «outputDirectory.mkdirs.saveConsoleOutput» + «deleteFiles(outputDirectory).toString.saveConsoleOutput» + -- + -- install generated utPLSQL test packages + -- + «ENDIF» + «FOR node : nodes» + «val context = node.toContext» + «context.conn = conn» + «val testTemplate = new TestTemplate(context)» + «IF generateFiles» + «val packageName = '''«context.testPackagePrefix»«node.toObjectName»«context.testPackageSuffix»'''» + «writeToFile('''«outputDirectory»«File.separator»«packageName».pks'''.toString,testTemplate.generateSpec).saveConsoleOutput» + «writeToFile('''«outputDirectory»«File.separator»«packageName».pkb'''.toString,testTemplate.generateBody).saveConsoleOutput» + @«outputDirectory»«File.separator»«packageName».pks + @«outputDirectory»«File.separator»«packageName».pkb + «ELSE» + «testTemplate.generate» + + «ENDIF» + «ENDFOR» + «IF generateFiles && consoleOutput.size > 0» + + -- + -- console output produced during the generation of this script + -- + /* + + «FOR line : consoleOutput» + «line» + «ENDFOR» + + */ + «ENDIF» + ''' + + override generateSeparator(Connection conn) { + return "" + } + + override generateEpilog(Connection conn, List nodes) { + return "" + } + + override generate(Connection conn, Node node) { + return "" + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend new file mode 100644 index 00000000..11d43564 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend @@ -0,0 +1,130 @@ +/* + * 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.oddgen + +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.List +import org.utplsql.sqldev.dal.UtplsqlDao +import org.utplsql.sqldev.oddgen.model.GenContext + +class TestTemplate { + var GenContext context + var UtplsqlDao dao + var List units + var dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + var today = dateTimeFormatter.format(LocalDateTime.now()) + + new(GenContext context) { + this.context = context + dao = new UtplsqlDao(context.conn) + units = dao.units(context.objectType, context.objectName) + } + + def replaceTabsWithSpaces(CharSequence input) { + val spaces = String.format("%1$"+context.indentSpaces+"s", "") + return input.toString.replace("\t", spaces) + } + + def generateSpec() { + val ret = ''' + «val objectName = context.objectName.toLowerCase» + «val packageName = '''«context.testPackagePrefix»«objectName»«context.testPackageSuffix»'''» + CREATE OR REPLACE PACKAGE «packageName» IS + + «IF context.generateComments» + /* generated by utPLSQL for SQL Developer on «today» */ + + «ENDIF» + --%suite(«packageName») + «IF !context.suitePath.empty» + --%suitepath(«context.suitePath») + «ENDIF» + + «FOR u : units» + «val unit = u.toLowerCase» + «IF context.numberOfTestsPerUnit > 1 && (context.objectType == "PACKAGE" || context.objectType == "TYPE")» + --%context(«unit») + + «ENDIF» + «FOR i : 1 .. context.numberOfTestsPerUnit» + --%test + «IF context.disableTests» + --%disabled + «ENDIF» + PROCEDURE «context.testUnitPrefix»«unit»«context.testUnitSuffix»«IF context.numberOfTestsPerUnit > 1»«i»«ENDIF»; + + «ENDFOR» + «IF context.numberOfTestsPerUnit > 1 && (context.objectType == "PACKAGE" || context.objectType == "TYPE")» + --%endcontext + + «ENDIF» + «ENDFOR» + END «packageName»; + / + ''' + return ret.replaceTabsWithSpaces + } + + def generateBody() { + val ret = ''' + «val objectName = context.objectName.toLowerCase» + CREATE OR REPLACE PACKAGE BODY «context.testPackagePrefix»«objectName»«context.testPackageSuffix» IS + + «IF context.generateComments» + /* generated by utPLSQL for SQL Developer on «today» */ + + «ENDIF» + «FOR u : units» + «val unit = u.toLowerCase» + «FOR i : 1 .. context.numberOfTestsPerUnit» + «val procedureName = '''«context.testUnitPrefix»«unit»«context.testUnitSuffix»«IF context.numberOfTestsPerUnit > 1»«i»«ENDIF»'''» + «IF context.generateComments» + -- + -- test «unit»«IF context.numberOfTestsPerUnit > 0» case «i»: ...«ENDIF» + -- + «ENDIF» + PROCEDURE «procedureName» IS + l_actual INTEGER := 0; + l_expected INTEGER := 1; + BEGIN + «IF context.generateComments» + -- populate actual + -- «objectName».«unit»; + + -- populate expected + -- ... + + -- assert + «ENDIF» + ut.expect(l_actual).to_equal(l_expected); + END «procedureName»; + + «ENDFOR» + «ENDFOR» + + END «context.testPackagePrefix»«objectName»«context.testPackageSuffix»; + / + ''' + return ret.replaceTabsWithSpaces + } + + def generate() ''' + «generateSpec» + + «generateBody» + ''' +} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/model/GenContext.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/model/GenContext.xtend new file mode 100644 index 00000000..d3897f6a --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/model/GenContext.xtend @@ -0,0 +1,38 @@ +/* + * 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.oddgen.model + +import java.sql.Connection +import org.eclipse.xtend.lib.annotations.Accessors +import org.utplsql.sqldev.model.AbstractModel + +@Accessors +class GenContext extends AbstractModel { + Connection conn + String objectType + String objectName + boolean generateFiles + String outputDirectory + String testPackagePrefix + String testPackageSuffix + String testUnitPrefix + String testUnitSuffix + int numberOfTestsPerUnit + boolean generateComments + boolean disableTests + String suitePath + int indentSpaces +} 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 c6c523c8..6ac25a20 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend @@ -93,10 +93,11 @@ class UtplsqlParser { } private def populateObjects() { - val p = Pattern.compile("(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package)\\s+(body\\s+)?)([^\\s]+)(\\s+)") + val p = Pattern.compile("(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package|type|function|procedure)\\s+(body\\s+)?)([^\\s]+)(\\s+)") val m = p.matcher(plsqlReduced) while (m.find) { val o = new PlsqlObject + o.type = m.group(4).toUpperCase o.name = m.group(6) o.position = m.start objects.add(o) @@ -145,8 +146,14 @@ class UtplsqlParser { } } } - - private def getObjectAt(int position) { + + /** + * gets the PL/SQL object based on the current editor position + * + * @param position the absolute position as used in {@link JTextComponent#getCaretPosition()} + * @return the PL/SQL object + */ + def getObjectAt(int position) { var PlsqlObject obj for (o : objects) { if (o.position <= position) { @@ -154,13 +161,29 @@ class UtplsqlParser { } } return obj - } - - private def getObjectNameAt(int position) { - val o = getObjectAt(position) - return if (o !== null) {o.name} else {""} } + /** + * converts a line and column to a postion as used in as used in {@link JTextComponent#getCaretPosition()} + * used for testing purposes only + * + * @param line the line as used in SQL Developer, starting with 1 + * @param column the column as used in SQL Developer, starting with 1 + * @return the position + */ + def toPosition(int line, int column) { + var lines=0 + for (var i=0; i alt shift T + + alt shift G + alt shift T + + alt shift G + diff --git a/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/oddgen.png b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/oddgen.png new file mode 100644 index 00000000..5f7d1aac Binary files /dev/null and b/sqldev/src/main/resources/org/utplsql/sqldev/resources/images/oddgen.png differ diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend index 89c5bbee..eadcd989 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/tests/DalTest.xtend @@ -35,6 +35,31 @@ class DalTest extends AbstractJdbcTest { } catch (BadSqlGrammarException e) { // ignore } + try { + jdbcTemplate.execute("DROP PACKAGE junit_no_test_pkg") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP TYPE junit_tab1_ot") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP TYPE junit_tab2_ot") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP FUNCTION junit_f") + } catch (BadSqlGrammarException e) { + // ignore + } + try { + jdbcTemplate.execute("DROP PROCEDURE junit_p") + } catch (BadSqlGrammarException e) { + // ignore + } } @Test @@ -63,7 +88,6 @@ class DalTest extends AbstractJdbcTest { @Test def void containsUtplsqlTest() { val dao = new UtplsqlDao(dataSource.connection) - Assert.assertFalse(dao.containsUtplsqlTest("scott")) jdbcTemplate.execute(''' CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS -- %suite @@ -104,7 +128,6 @@ class DalTest extends AbstractJdbcTest { @Test def void annotations() { val dao = new UtplsqlDao(dataSource.connection) - Assert.assertEquals(new ArrayList, dao.annotations("scott", "junit_utplsql_test_pkg")) jdbcTemplate.execute(''' CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS -- %suite @@ -143,4 +166,82 @@ class DalTest extends AbstractJdbcTest { Assert.assertEquals(expected.toString, effective.toString) jdbcTemplate.execute("DROP PACKAGE junit_utplsql_test_pkg") } + + @Test + def void testablesPackages() { + val dao = new UtplsqlDao(dataSource.connection) + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_utplsql_test_pkg IS + -- %suite + + -- %test + PROCEDURE t1; + + -- %Test + PROCEDURE t2; + + PROCEDURE t3; + END junit_utplsql_test_pkg; + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE PACKAGE junit_no_test_pkg IS + PROCEDURE p1; + + PROCEDURE p2; + END junit_no_test_pkg; + ''') + val effective = dao.testables('PACKAGE') + Assert.assertEquals(1, effective.size) + Assert.assertEquals("PACKAGE.JUNIT_NO_TEST_PKG", effective.get(0).id) + } + + @Test + def void testablesTypes() { + val dao = new UtplsqlDao(dataSource.connection) + jdbcTemplate.execute(''' + CREATE OR REPLACE TYPE junit_tab1_ot IS object (a integer, b integer); + ''') + jdbcTemplate.execute(''' + CREATE OR REPLACE TYPE junit_tab2_ot IS object ( + a integer, + b integer, + member procedure c( + self in out nocopy junit_tab2_ot, + p integer + ) + ); + ''') + val effective = dao.testables('TYPE') + Assert.assertEquals(1, effective.size) + Assert.assertEquals("TYPE.JUNIT_TAB2_OT", effective.get(0).id) + } + + @Test + def void testablesFunctions() { + val dao = new UtplsqlDao(dataSource.connection) + jdbcTemplate.execute(''' + CREATE OR REPLACE FUNCTION junit_f RETURN INTEGER IS + BEGIN + RETURN 1; + END; + ''') + val effective = dao.testables('FUNCTION') + Assert.assertEquals(1, effective.size) + Assert.assertEquals("FUNCTION.JUNIT_F", effective.get(0).id) + } + + @Test + def void testablesProcedures() { + val dao = new UtplsqlDao(dataSource.connection) + jdbcTemplate.execute(''' + CREATE OR REPLACE PROCEDURE junit_p RETURN INTEGER IS + BEGIN + NULL; + END; + ''') + val effective = dao.testables('PROCEDURE') + Assert.assertEquals(1, effective.size) + Assert.assertEquals("PROCEDURE.JUNIT_P", effective.get(0).id) + } + } \ No newline at end of file diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend index 5dafb92c..206d690b 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend @@ -29,5 +29,17 @@ class PreferenceModelTest { Assert.assertFalse(model.clearScreen) Assert.assertTrue(model.autoExecute) Assert.assertFalse(model.checkRunUtplsqlTest) + Assert.assertEquals("test_", model.testPackagePrefix) + Assert.assertEquals("", model.testPackageSuffix) + Assert.assertEquals("", model.testUnitPrefix) + Assert.assertEquals("", model.testUnitSuffix) + Assert.assertFalse(model.checkGenerateUtplsqlTest) + Assert.assertTrue(model.generateComments) + Assert.assertFalse(model.disableTests) + Assert.assertEquals("alltests", model.suitePath) + Assert.assertEquals(3, model.indentSpaces) + Assert.assertTrue(model.generateFiles) + Assert.assertEquals(PreferenceModel.DEFAULT_OUTPUT_DIRECTORY, model.outputDirectory) + Assert.assertEquals("utPLSQL", model.rootFolderInOddgenView) } } diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend b/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend index e56a14fb..131a9d4e 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend +++ b/sqldev/src/test/java/org/utplsql/sqldev/tests/UtplsqlParserTest.xtend @@ -86,14 +86,14 @@ class UtplsqlParserTest extends AbstractJdbcTest { Assert.assertEquals('''"P"'''.toString, units.get(1).name) Assert.assertTrue(units.get(0).position < units.get(1).position) Assert.assertEquals("", parser.getPathAt(0)) - Assert.assertEquals("", parser.getPathAt(3,6)) - Assert.assertEquals("pkg", parser.getPathAt(4,1)) - Assert.assertEquals("pkg.p", parser.getPathAt(10,33)) - Assert.assertEquals("pkg.p", parser.getPathAt(13,1)) - Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(19,1)) - Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(22,9)) - Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(22,10)) - Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(29,1)) + Assert.assertEquals("", parser.getPathAt(parser.toPosition(3,6))) + Assert.assertEquals("pkg", parser.getPathAt(parser.toPosition(4,1))) + Assert.assertEquals("pkg.p", parser.getPathAt(parser.toPosition(10,33))) + Assert.assertEquals("pkg.p", parser.getPathAt(parser.toPosition(13,1))) + Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(parser.toPosition(19,1))) + Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(parser.toPosition(22,9))) + Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(parser.toPosition(22,10))) + Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(parser.toPosition(29,1))) } @Test @@ -124,8 +124,8 @@ class UtplsqlParserTest extends AbstractJdbcTest { parser = new UtplsqlParser(sqlScript, dataSource.connection, null) Assert.assertEquals(2, parser.getObjects.size) Assert.assertEquals(2, parser.getUnits.size) - Assert.assertEquals("pkg.p", parser.getPathAt(13,1)) - Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(19,1)) + Assert.assertEquals("pkg.p", parser.getPathAt(parser.toPosition(13,1))) + Assert.assertEquals("SCOTT.PKG.P", parser.getPathAt(parser.toPosition(19,1))) setupAndTeardown } @@ -178,12 +178,12 @@ class UtplsqlParserTest extends AbstractJdbcTest { end; ''' val parser = new UtplsqlParser(plsql) - Assert.assertEquals("test_expect_not_to_be_null.cleanup_expectations", parser.getPathAt(7,1)) - Assert.assertEquals("test_expect_not_to_be_null.create_types", parser.getPathAt(13,1)) + 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(23,1)) + 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(33,1)) + Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(33,1))) } @Test @@ -215,7 +215,89 @@ class UtplsqlParserTest extends AbstractJdbcTest { ''' 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(13,26)) + Assert.assertEquals("test_expect_not_to_be_null.blob_not_null", parser.getPathAt(parser.toPosition(13,26))) } + @Test + def testProcedure() { + val plsql = ''' + create or replace procedure z + is + null; + end; + / + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals("z", parser.getObjectAt(0).name) + Assert.assertEquals("PROCEDURE", parser.getObjectAt(0).type) + } + + @Test + def testFunction() { + val plsql = ''' + create or replace procedure z + is + null; + end; + / + + create or replace function f return number is + begin + null; + end; + / + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals("f", parser.getObjectAt(parser.toPosition(8,1)).name) + Assert.assertEquals("FUNCTION", parser.getObjectAt(parser.toPosition(8,1)).type) + } + + @Test + def testType() { + val plsql = ''' + create or replace type t force is + object ( + a number, + b number, + c varchar2(10), + member procedure p(self in t) + ) + end; + / + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals("t", parser.getObjectAt(0).name) + Assert.assertEquals("TYPE", parser.getObjectAt(0).type) + } + + @Test + def testTypeBody() { + val plsql = ''' + create or replace type body t force is + member procedure p(self in t) is + begin + null; + end; + end; + / + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals("t", parser.getObjectAt(0).name) + Assert.assertEquals("TYPE", parser.getObjectAt(0).type) + } + + @Test + def testUnknown() { + val plsql = ''' + create or replace unknown u is + begin + null; + end; + / + ''' + val parser = new UtplsqlParser(plsql) + Assert.assertEquals(null, parser.getObjectAt(0)) + } + + }