From e68f8d6d456e5ba1402685ada7273534b6465a90 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 5 Oct 2017 17:50:43 +0300 Subject: [PATCH] Kotlin Ultimate: Support Protractor run configurations for module directory --- ultimate/build.gradle.kts | 2 +- ultimate/resources/META-INF/kotlin-nodejs.xml | 5 +- .../jetbrains/kotlin/idea/js/jsModuleUtils.kt | 2 +- .../KotlinMochaRunConfigurationProducer.kt | 31 +-- .../kotlin/idea/nodejs/nodeJsUtil.kt | 33 ++++ .../KotlinProtractorConfigurationType.kt | 52 +++++ .../KotlinProtractorRunConfiguration.kt | 103 ++++++++++ .../KotlinProtractorRunConfigurationEditor.kt | 144 ++++++++++++++ ...otlinProtractorRunConfigurationProducer.kt | 64 +++++++ .../protractor/KotlinProtractorRunSettings.kt | 63 +++++++ .../protractor/KotlinProtractorRunState.kt | 178 ++++++++++++++++++ 11 files changed, 663 insertions(+), 14 deletions(-) rename ultimate/src/org/jetbrains/kotlin/idea/nodejs/{ => mocha}/KotlinMochaRunConfigurationProducer.kt (90%) create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/nodeJsUtil.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorConfigurationType.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfiguration.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationEditor.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationProducer.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunSettings.kt create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunState.kt diff --git a/ultimate/build.gradle.kts b/ultimate/build.gradle.kts index bcb5fc03e8c..0b16e411ce1 100644 --- a/ultimate/build.gradle.kts +++ b/ultimate/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { compile(ideaUltimatePreloadedDeps("*.jar", subdir = "nodejs_plugin/NodeJS/lib")) compile(ideaUltimateSdkCoreDeps("annotations", "trove4j", "intellij-core")) - compile(ideaUltimateSdkDeps("openapi", "idea", "util")) + compile(ideaUltimateSdkDeps("openapi", "idea", "util", "jdom")) compile(ideaUltimatePluginDeps("*.jar", plugin = "CSS")) compile(ideaUltimatePluginDeps("*.jar", plugin = "DatabaseTools")) compile(ideaUltimatePluginDeps("*.jar", plugin = "JavaEE")) diff --git a/ultimate/resources/META-INF/kotlin-nodejs.xml b/ultimate/resources/META-INF/kotlin-nodejs.xml index 8771420d1db..4865475e0fe 100644 --- a/ultimate/resources/META-INF/kotlin-nodejs.xml +++ b/ultimate/resources/META-INF/kotlin-nodejs.xml @@ -1,5 +1,8 @@ - + + + + diff --git a/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt b/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt index 36235d8e754..df617d7747e 100644 --- a/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt +++ b/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt @@ -35,4 +35,4 @@ fun getJsClasspath(module: Module): List { true } return result -} \ No newline at end of file +} diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/mocha/KotlinMochaRunConfigurationProducer.kt similarity index 90% rename from ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt rename to ultimate/src/org/jetbrains/kotlin/idea/nodejs/mocha/KotlinMochaRunConfigurationProducer.kt index 919c99926c6..c221c00a862 100644 --- a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/mocha/KotlinMochaRunConfigurationProducer.kt @@ -1,8 +1,23 @@ -package org.jetbrains.kotlin.idea.nodejs +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.mocha import com.intellij.execution.RunManager import com.intellij.execution.actions.ConfigurationContext -import com.intellij.execution.configuration.EnvironmentVariablesData import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project @@ -13,14 +28,13 @@ import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement -import com.intellij.psi.impl.file.PsiJavaDirectoryImpl import com.intellij.psi.util.PsiUtilCore import com.intellij.util.SmartList import com.intellij.util.containers.SmartHashSet import com.jetbrains.nodejs.mocha.MochaUtil import com.jetbrains.nodejs.mocha.execution.* -import org.jetbrains.kotlin.idea.js.getJsClasspath import org.jetbrains.kotlin.idea.js.getJsOutputFilePath +import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir import org.jetbrains.kotlin.js.resolve.JsPlatform @@ -30,7 +44,6 @@ import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf -import java.io.File class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() { private data class TestElementInfo(val runSettings: MochaRunSettings, val enclosingTestElement: PsiElement) @@ -80,7 +93,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() { } private fun createSuiteOrTestData(element: PsiElement, module: Module): TestElementPath? { - if (element is PsiDirectory && module.getModuleDir() == (element as PsiJavaDirectoryImpl).virtualFile.path) { + if (element is PsiDirectory && module.getModuleDir() == element.virtualFile.path) { return TestElementPath.BySingleFile } @@ -133,11 +146,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() { } } - val nodeJsClasspath = getJsClasspath(module).joinToString(File.pathSeparator) { - val basePath = project.basePath ?: return@joinToString it - FileUtil.getRelativePath(basePath, it, '/') ?: it - } - builder.setEnvData(EnvironmentVariablesData.create(mapOf("NODE_PATH" to nodeJsClasspath), true)) + builder.setEnvData(module.getNodeJsEnvironmentVars()) return TestElementInfo(builder.build(), element) } diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/nodeJsUtil.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/nodeJsUtil.kt new file mode 100644 index 00000000000..c352b188078 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/nodeJsUtil.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs + +import com.intellij.execution.configuration.EnvironmentVariablesData +import com.intellij.openapi.module.Module +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.idea.js.getJsClasspath +import java.io.File + +const val NODE_PATH_VAR = "NODE_PATH" + +fun Module.getNodeJsEnvironmentVars(): EnvironmentVariablesData { + val nodeJsClasspath = getJsClasspath(this).joinToString(File.pathSeparator) { + val basePath = project.basePath ?: return@joinToString it + FileUtil.getRelativePath(basePath, it, '/') ?: it + } + return EnvironmentVariablesData.create(mapOf(NODE_PATH_VAR to nodeJsClasspath), true) +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorConfigurationType.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorConfigurationType.kt new file mode 100644 index 00000000000..5653970a61c --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorConfigurationType.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.configuration.ConfigurationFactoryEx +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.ConfigurationTypeBase +import com.intellij.execution.configurations.ConfigurationTypeUtil +import com.intellij.openapi.project.Project +import icons.JavaScriptLanguageIcons + +class KotlinProtractorConfigurationType : ConfigurationTypeBase( + "KotlinJavaScriptTestRunnerProtractor", + "Protractor", + NAME, + JavaScriptLanguageIcons.Protractor.Protractor +) { + init { + addFactory(object : ConfigurationFactoryEx(this) { + override fun createTemplateConfiguration(project: Project) = KotlinProtractorRunConfiguration(project, this, NAME) + + override fun isConfigurationSingletonByDefault() = true + + override fun canConfigurationBeSingleton() = false + + override fun onNewConfigurationCreated(configuration: KotlinProtractorRunConfiguration) = configuration.initialize() + }) + } + + companion object { + private val NAME = "Protractor" + + fun getInstance() = ConfigurationTypeUtil.findConfigurationType(KotlinProtractorConfigurationType::class.java) + + val factory: ConfigurationFactory + get() = getInstance().configurationFactories.first() + } +} diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfiguration.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfiguration.kt new file mode 100644 index 00000000000..6fd3bb21603 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfiguration.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.LocatableConfigurationBase +import com.intellij.execution.configurations.RuntimeConfigurationError +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter +import com.intellij.javascript.nodejs.util.NodePackage +import com.intellij.javascript.protractor.ProtractorUtil +import com.intellij.openapi.project.Project +import com.intellij.util.PathUtil +import org.jdom.Element +import java.io.File + +// Based on com.intellij.javascript.protractor.ProtractorRunConfiguration +class KotlinProtractorRunConfiguration( + project: Project, + factory: ConfigurationFactory, + name: String +) : LocatableConfigurationBase(project, factory, name) { + var runSettings = KotlinProtractorRunSettings() + + val protractorPackage: NodePackage + get() { + val project = project + val interpreter = runSettings.interpreterRef.resolve(project) + return ProtractorUtil.getProtractorPackage(project, null, interpreter, true) + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment) = + KotlinProtractorRunState(this, environment, protractorPackage) + + override fun getConfigurationEditor() = KotlinProtractorRunConfigurationEditor(project) + + override fun readExternal(element: Element) { + super.readExternal(element) + runSettings = KotlinProtractorRunSettings.readFromXML(element) + } + + override fun writeExternal(element: Element) { + super.writeExternal(element) + runSettings.writeToXML(element) + } + + fun initialize() { + setNameChangedByUser(false) + } + + override fun checkConfiguration() { + val isOptionalConfig = runSettings.testFileSystemDependentPath.isNotBlank() || runSettings.seleniumAddress.isNotBlank() + if (isOptionalConfig) { + validatePath("test file", runSettings.testFileSystemDependentPath, true, false) + if (runSettings.seleniumAddress.isBlank()) { + throw RuntimeConfigurationError("Unspecified Selenium address") + } + } + if (!isOptionalConfig || runSettings.configFileSystemDependentPath.isNotBlank()) { + validatePath("configuration file", runSettings.configFileSystemDependentPath, true, false) + } + + val interpreter = runSettings.interpreterRef.resolve(project) + NodeJsLocalInterpreter.checkForRunConfiguration(interpreter) + + validatePath("protractor package", protractorPackage.systemDependentPath, true, true) + } + + override fun suggestedName(): String? { + val name = PathUtil.getFileName(runSettings.testFileSystemDependentPath) + return if (name.isNotBlank()) name else null + } + + private fun validatePath(name: String, + path: String?, + shouldBeAbsolute: Boolean, + shouldBeDirectory: Boolean + ) { + if (path.isNullOrBlank()) throw RuntimeConfigurationError("Unspecified " + name) + + val file = File(path!!) + + if (shouldBeAbsolute && !file.isAbsolute) throw RuntimeConfigurationError("No such " + name) + + val exists = if (shouldBeDirectory) file.isDirectory else file.isFile + if (!exists) throw RuntimeConfigurationError("No such " + name) + } +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationEditor.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationEditor.kt new file mode 100644 index 00000000000..3082aae25c7 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationEditor.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.configuration.EnvironmentVariablesTextFieldWithBrowseButton +import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterField +import com.intellij.javascript.nodejs.util.NodePackageField +import com.intellij.javascript.protractor.ProtractorUtil +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.options.ex.SingleConfigurableEditor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.util.io.FileUtil +import com.intellij.ui.RawCommandLineEditor +import com.intellij.ui.TextFieldWithHistoryWithBrowseButton +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.ui.FormBuilder +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.SwingHelper + +import javax.swing.* +import java.util.Collections + +// Based com.intellij.javascript.protractor.ProtractorRunConfigurationEditor +class KotlinProtractorRunConfigurationEditor(private val myProject: Project) : SettingsEditor() { + private val testFilePathTextFieldWithBrowseButton: TextFieldWithHistoryWithBrowseButton + private val seleniumAddressTextField: JTextField + private val extraOptionsEditor: RawCommandLineEditor + private val configPathTextFieldWithBrowseButton: TextFieldWithHistoryWithBrowseButton + private val interpreterField: NodeJsInterpreterField + private val protractorPackageField: NodePackageField + private val envVarsComponent: EnvironmentVariablesTextFieldWithBrowseButton + private val panel: JPanel + + init { + testFilePathTextFieldWithBrowseButton = createTestFileTextField(myProject) + seleniumAddressTextField = JTextField() + extraOptionsEditor = RawCommandLineEditor().apply { dialogCaption = "Extra Protractor Options" } + configPathTextFieldWithBrowseButton = createConfigurationFileTextField(myProject) + interpreterField = NodeJsInterpreterField(myProject, false) + protractorPackageField = NodePackageField(myProject, ProtractorUtil.PACKAGE_NAME) { interpreterField.interpreter } + envVarsComponent = EnvironmentVariablesTextFieldWithBrowseButton() + panel = FormBuilder() + .setAlignLabelOnRight(false) + .addLabeledComponent("&Test file:", testFilePathTextFieldWithBrowseButton) + .addLabeledComponent("&Selenium address:", seleniumAddressTextField) + .addLabeledComponent("E&xtra Protractor options:", extraOptionsEditor) + .addComponent(JSeparator(), JBUI.scale(8)) + .addLabeledComponent("&Configuration file:", configPathTextFieldWithBrowseButton) + .addComponent(JSeparator(), JBUI.scale(8)) + .addLabeledComponent("Node &interpreter:", interpreterField, JBUI.scale(8)) + .addLabeledComponent("&Protractor package:", protractorPackageField) + .addLabeledComponent("&Environment variables:", envVarsComponent) + .panel + } + + private fun createConfigurationFileTextField(project: Project) = TextFieldWithHistoryWithBrowseButton().apply { + val textFieldWithHistory = childComponent + textFieldWithHistory.setHistorySize(-1) + textFieldWithHistory.setMinimumAndPreferredWidth(0) + SwingHelper.addHistoryOnExpansion(textFieldWithHistory) { + val newFiles = ProtractorUtil.listPossibleConfigFilesInProject(project) + val newFilePaths = ContainerUtil.map(newFiles) { file -> FileUtil.toSystemDependentName(file.path) } + Collections.sort(newFilePaths) + newFilePaths + } + + SwingHelper.installFileCompletionAndBrowseDialog( + project, + this, + "Select Protractor Configuration File", + FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor() + ) + } + + private fun createTestFileTextField(project: Project) = TextFieldWithHistoryWithBrowseButton().apply { + val textFieldWithHistory = childComponent + textFieldWithHistory.setHistorySize(-1) + textFieldWithHistory.setMinimumAndPreferredWidth(0) + + SwingHelper.installFileCompletionAndBrowseDialog( + project, + this, + "Select Protractor Configuration File", + FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor() + ) + } + + override fun resetEditorFrom(runConfiguration: KotlinProtractorRunConfiguration) { + with(runConfiguration.runSettings) { + testFilePathTextFieldWithBrowseButton.setTextAndAddToHistory(testFileSystemDependentPath) + seleniumAddressTextField.text = seleniumAddress + extraOptionsEditor.text = extraOptions + configPathTextFieldWithBrowseButton.setTextAndAddToHistory(configFileSystemDependentPath) + interpreterField.interpreterRef = interpreterRef + protractorPackageField.selected = runConfiguration.protractorPackage + envVarsComponent.data = envData + } + updatePreferredWidth() + } + + private fun updatePreferredWidth() { + val dialogWrapper = DialogWrapper.findInstance(panel) + if (dialogWrapper is SingleConfigurableEditor) { + // dialog for single run configuration + interpreterField.setPreferredWidthToFitText() + protractorPackageField.setPreferredWidthToFitText() + SwingHelper.setPreferredWidthToFitText(testFilePathTextFieldWithBrowseButton) + SwingHelper.setPreferredWidthToFitText(seleniumAddressTextField) + SwingHelper.setPreferredWidthToFitText(configPathTextFieldWithBrowseButton) + ApplicationManager.getApplication().invokeLater { SwingHelper.adjustDialogSizeToFitPreferredSize(dialogWrapper) } + } + } + + override fun applyEditorTo(runConfiguration: KotlinProtractorRunConfiguration) { + ProtractorUtil.setProtractorPackage(myProject, protractorPackageField.selected) + runConfiguration.runSettings = runConfiguration.runSettings.copy( + interpreterRef = interpreterField.interpreterRef, + configFilePath = configPathTextFieldWithBrowseButton.text, + testFilePath = testFilePathTextFieldWithBrowseButton.text, + seleniumAddress = seleniumAddressTextField.text, + extraOptions = extraOptionsEditor.text, + envData = envVarsComponent.data + ) + } + + override fun createEditor() = panel +} diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationProducer.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationProducer.kt new file mode 100644 index 00000000000..c34c26e06cf --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunConfigurationProducer.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.actions.CompatibleRunConfigurationProducer +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.util.Ref +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.js.getJsOutputFilePath +import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir +import org.jetbrains.kotlin.idea.util.projectStructure.module +import org.jetbrains.kotlin.js.resolve.JsPlatform + +class KotlinProtractorRunConfigurationProducer : + CompatibleRunConfigurationProducer(KotlinProtractorConfigurationType.getInstance()) { + override fun isConfigurationFromCompatibleContext( + configuration: KotlinProtractorRunConfiguration, + context: ConfigurationContext + ): Boolean { + val contextPsi = context.psiLocation ?: return false + val module = contextPsi.module ?: return false + val testFilePath = getJsOutputFilePath(module, isTests = true, isMeta = false) ?: return false + return configuration.runSettings.testFileSystemDependentPath == FileUtil.toSystemDependentName(testFilePath) + } + + override fun setupConfigurationFromCompatibleContext( + configuration: KotlinProtractorRunConfiguration, + context: ConfigurationContext, + sourceElement: Ref + ): Boolean { + val element = context.psiLocation ?: return false + val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return false + if (element !is PsiDirectory || module.getModuleDir() != element.virtualFile.path) return false + if (TargetPlatformDetector.getPlatform(module) !is JsPlatform) return false + val testFilePath = getJsOutputFilePath(module, true, false) ?: return false + + sourceElement.set(element) + configuration.runSettings = configuration.runSettings.copy( + testFilePath = testFilePath, + envData = module.getNodeJsEnvironmentVars() + ) + configuration.name = configuration.suggestedName() + return true + } +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunSettings.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunSettings.kt new file mode 100644 index 00000000000..f58041b0a91 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunSettings.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.configuration.EnvironmentVariablesData +import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterRef +import com.intellij.openapi.util.JDOMExternalizerUtil.* +import com.intellij.openapi.util.io.FileUtil +import org.jdom.Element + +data class KotlinProtractorRunSettings( + val interpreterRef: NodeJsInterpreterRef = NodeJsInterpreterRef.createProjectRef(), + private val configFilePath: String = "", + private val testFilePath: String = "", + val seleniumAddress: String = "", + val extraOptions: String = "", + val envData: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT +) { + val configFileSystemDependentPath get() = FileUtil.toSystemDependentName(configFilePath) + val testFileSystemDependentPath get() = FileUtil.toSystemDependentName(testFilePath) + + fun writeToXML(element: Element) { + addElementWithValueAttribute(element, "config-file", FileUtil.toSystemIndependentName(configFileSystemDependentPath)) + addElementWithValueAttribute(element, "test-file", FileUtil.toSystemIndependentName(testFileSystemDependentPath)) + addElementWithValueAttribute(element, "selenium-address", seleniumAddress) + addElementWithValueAttribute(element, "extra-protractor-options", extraOptions) + addElementWithValueAttribute(element, "node-interpreter", interpreterRef.referenceName) + envData.writeExternal(element) + } + + companion object { + fun readFromXML(element: Element): KotlinProtractorRunSettings { + val configFilePath = getFirstChildValueAttribute(element, "config-file") + val testFilePath = getFirstChildValueAttribute(element, "test-file") + val seleniumAddress = getFirstChildValueAttribute(element, "selenium-address") + val extraOptions = getFirstChildValueAttribute(element, "extra-protractor-options") + val interpreterRefName = getFirstChildValueAttribute(element, "node-interpreter") + val envData = EnvironmentVariablesData.readExternal(element) + return KotlinProtractorRunSettings( + NodeJsInterpreterRef.create(interpreterRefName ?: "project"), + configFilePath ?: "", + testFilePath ?: "", + seleniumAddress ?: "", + extraOptions ?: "", + envData + ) + } + } +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunState.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunState.kt new file mode 100644 index 00000000000..863e3ae1581 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/protractor/KotlinProtractorRunState.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.idea.nodejs.protractor + +import com.intellij.execution.DefaultExecutionResult +import com.intellij.execution.ExecutionException +import com.intellij.execution.ExecutionResult +import com.intellij.execution.Executor +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.process.ProcessTerminatedListener +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.testframework.TestConsoleProperties +import com.intellij.execution.testframework.autotest.ToggleAutoTestAction +import com.intellij.execution.testframework.sm.SMCustomMessagesParsing +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties +import com.intellij.execution.testframework.sm.runner.SMTestLocator +import com.intellij.execution.ui.ConsoleView +import com.intellij.javascript.HelperFilesLocator +import com.intellij.javascript.nodejs.NodeCommandLineUtil +import com.intellij.javascript.nodejs.debug.NodeLocalDebugRunProfileState +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter +import com.intellij.javascript.nodejs.util.NodePackage +import com.intellij.javascript.protractor.ProtractorConsoleFilter +import com.intellij.javascript.protractor.ProtractorOutputToGeneralTestEventsConverter +import com.intellij.javascript.protractor.ProtractorUtil +import com.intellij.javascript.testFramework.navigation.JSTestLocationProvider +import com.intellij.lang.javascript.buildTools.base.JsbtUtil +import com.intellij.openapi.util.Disposer +import com.intellij.util.PathUtil +import com.intellij.util.execution.ParametersListUtil +import java.io.File +import java.io.IOException +import java.nio.charset.StandardCharsets + +class KotlinProtractorRunState( + private val runConfiguration: KotlinProtractorRunConfiguration, + private val environment: ExecutionEnvironment, + private val protractorPackage: NodePackage +) : RunProfileState, NodeLocalDebugRunProfileState { + companion object { + val FRAMEWORK_NAME = "KotlinProtractorJavaScriptTestRunner" + private val INTELLIJ_CONFIG_FILE_PATH = "protractor-intellij/lib/protractor-intellij-config.js" + } + + private val project = runConfiguration.project + private val runSettings = runConfiguration.runSettings + + override fun execute(debugPort: Int): ExecutionResult { + val interpreter = runSettings.interpreterRef.resolveAsLocal(project) + val commandLine = createCommandLine(interpreter, debugPort) + val processHandler = NodeCommandLineUtil.createProcessHandler(commandLine, false) + val consoleView = createSmtRunnerConsoleView(commandLine.workDirectory) + ProcessTerminatedListener.attach(processHandler) + consoleView.attachToProcess(processHandler) + foldCommandLine(consoleView, processHandler) + val executionResult = DefaultExecutionResult(consoleView, processHandler) + executionResult.setRestartActions(ToggleAutoTestAction()) + return executionResult + } + + private fun createSmtRunnerConsoleView(workingDirectory: File?): ConsoleView { + val testConsoleProperties = ProtractorConsoleProperties(runConfiguration, environment.executor, JSTestLocationProvider()) + val consoleView = SMTestRunnerConnectionUtil.createConsole(FRAMEWORK_NAME, testConsoleProperties) + consoleView.addMessageFilter(ProtractorConsoleFilter(project, workingDirectory)) + Disposer.register(environment, consoleView) + return consoleView + } + + override fun foldCommandLine(consoleView: ConsoleView, processHandler: ProcessHandler) { + val configFileName = PathUtil.getFileName(runSettings.configFileSystemDependentPath) + val testFileName = PathUtil.getFileName(runSettings.testFileSystemDependentPath) + val seleniumAddress = runSettings.seleniumAddress + val parameters = ArrayList().apply { + add("protractor") + if (configFileName.isNotBlank()) { + add(configFileName) + } + if (testFileName.isNotBlank()) { + add("--specs=$testFileName") + } + if (seleniumAddress.isNotBlank()) { + add("--seleniumAddress=$seleniumAddress") + } + addAll(ParametersListUtil.parse(runSettings.extraOptions.trim())) + } + val foldedCommandLine = ParametersListUtil.join(parameters) + JsbtUtil.foldCommandLine(consoleView, processHandler, foldedCommandLine) + } + + private fun createCommandLine(interpreter: NodeJsLocalInterpreter, debugPort: Int): GeneralCommandLine { + val commandLine = GeneralCommandLine().apply { charset = StandardCharsets.UTF_8 } + + runSettings.envData.configureCommandLine(commandLine, true) + + val originalConfigFilePath = runSettings.configFileSystemDependentPath + val testFilePath = runSettings.testFileSystemDependentPath + val seleniumAddress = runSettings.seleniumAddress + val withConfigFile = originalConfigFilePath.isNotBlank() + commandLine.setWorkDirectory(project.basePath) + + commandLine.exePath = interpreter.interpreterSystemDependentPath + NodeCommandLineUtil.addNodeOptionsForDebugging(commandLine, emptyList(), debugPort, true, interpreter, true) + NodeCommandLineUtil.configureUsefulEnvironment(commandLine) + commandLine.addParameter(ProtractorUtil.getProtractorMainJsFile(protractorPackage).absolutePath) + + val intellijConfigFile = try { + HelperFilesLocator.getFileRelativeToHelpersDir(INTELLIJ_CONFIG_FILE_PATH) + } + catch (e: IOException) { + throw ExecutionException("Cannot locate wrapper config file", e) + } + + commandLine.addParameter(intellijConfigFile.absolutePath) + + val actualConfigFilePath = if (withConfigFile) { + originalConfigFilePath + } + else { + val tempConfigFile = File.createTempFile("protractor", "conf.js") + tempConfigFile.writeText("exports.config = {}") + tempConfigFile.absolutePath + } + + commandLine.addParameter("--intellijOriginalConfigFile=$actualConfigFilePath") + + if (testFilePath.isNotBlank()) { + commandLine.addParameter("--specs=$testFilePath") + } + + if (seleniumAddress.isNotBlank()) { + commandLine.addParameter("--seleniumAddress=$seleniumAddress") + } + + commandLine.addParameters(ParametersListUtil.parse(runSettings.extraOptions.trim())) + + commandLine.addParameter("--disableChecks") + + return commandLine + } + + private class ProtractorConsoleProperties( + configuration: KotlinProtractorRunConfiguration, + executor: Executor, + private val myLocator: SMTestLocator + ) : SMTRunnerConsoleProperties(configuration, FRAMEWORK_NAME, executor), SMCustomMessagesParsing { + init { + isUsePredefinedMessageFilter = false + setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false) + setIfUndefined(TestConsoleProperties.HIDE_IGNORED_TEST, true) + setIfUndefined(TestConsoleProperties.SCROLL_TO_SOURCE, true) + setIfUndefined(TestConsoleProperties.SELECT_FIRST_DEFECT, true) + isIdBasedTestTree = true + isPrintTestingStartedTime = false + } + + override fun getTestLocator() = myLocator + + override fun createTestEventsConverter(testFrameworkName: String, consoleProperties: TestConsoleProperties) = + ProtractorOutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties.isEditable) + } +} \ No newline at end of file