Kotlin Ultimate: Support Protractor run configurations for module directory

This commit is contained in:
Alexey Sedunov
2017-10-05 17:50:43 +03:00
parent 18d4b91b2d
commit e68f8d6d45
11 changed files with 663 additions and 14 deletions
+1 -1
View File
@@ -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"))
@@ -1,5 +1,8 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.KotlinMochaRunConfigurationProducer"/>
<configurationType implementation="org.jetbrains.kotlin.idea.nodejs.protractor.KotlinProtractorConfigurationType"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.mocha.KotlinMochaRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.protractor.KotlinProtractorRunConfigurationProducer"/>
</extensions>
</idea-plugin>
@@ -35,4 +35,4 @@ fun getJsClasspath(module: Module): List<String> {
true
}
return result
}
}
@@ -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)
}
@@ -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)
}
@@ -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<KotlinProtractorRunConfiguration>(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()
}
}
@@ -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)
}
}
@@ -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<KotlinProtractorRunConfiguration>() {
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
}
@@ -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<KotlinProtractorRunConfiguration>(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<PsiElement>
): 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
}
}
@@ -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
)
}
}
}
@@ -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<String>().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)
}
}