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
+
+
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))
+ }
+
+
}