From 3eda40394a6b0b504f929be1eeeb22b204ec8afc Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Fri, 12 Apr 2019 11:42:50 +0300 Subject: [PATCH] Impossible to run applications with long command lines on windows ^KT-29352 Fixed --- .../idea/run/KotlinRunConfiguration.java | 1 + .../kotlin/console/KotlinConsoleKeeper.kt | 1 + .../idea/util/runConfigurationHelper.kt | 13 ++++ .../run/LongCommandLine/module/src/main.kt | 6 ++ .../kotlin/idea/repl/IdeReplExecutionTest.kt | 12 ++++ .../kotlin/idea/run/RunConfigurationTest.kt | 14 ++++ .../idea/run/RunConfigurationTest.kt.181 | 14 ++++ .../idea/run/runConfigurationTestUtil.kt | 33 +++++++++ .../scratch/AbstractScratchRunActionTest.kt | 45 ++++++------ .../scratch/CustomScratchRunActionTest.kt | 70 +++++++++++++++++++ .../idea/scratch/ScratchOptionsSaveTest.kt | 16 +---- 11 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 idea/testData/run/LongCommandLine/module/src/main.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/scratch/CustomScratchRunActionTest.kt diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfiguration.java b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfiguration.java index 469655fe060..028b81e47de 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfiguration.java +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfiguration.java @@ -344,6 +344,7 @@ public class KotlinRunConfiguration extends JetRunConfiguration { JavaParametersUtil.configureModule(module, params, classPathType, jreHome); setupJavaParameters(params); + params.setShortenCommandLine(null, module.getProject()); params.setMainClass(myConfiguration.getRunClass()); setupModulePath(params, module); diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt index ae66cdd0f19..0edc6e2cbcf 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt @@ -65,6 +65,7 @@ class KotlinConsoleKeeper(val project: Project) { if (module != null) { val classPath = JavaParametersBuilder.getModuleDependencies(module) if (classPath.isNotEmpty()) { + javaParameters.setUseDynamicParameters(javaParameters.isDynamicClasspath) javaParameters.programParametersList.add("-cp") javaParameters.programParametersList.add( classPath.joinToString(File.pathSeparator) diff --git a/idea/src/org/jetbrains/kotlin/idea/util/runConfigurationHelper.kt b/idea/src/org/jetbrains/kotlin/idea/util/runConfigurationHelper.kt index 07b7c999915..6f9fd4abacc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/runConfigurationHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/runConfigurationHelper.kt @@ -5,11 +5,13 @@ package org.jetbrains.kotlin.idea.util +import com.intellij.execution.ShortenCommandLine import com.intellij.execution.configurations.JavaParameters import com.intellij.openapi.compiler.ex.CompilerPathsEx import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkType +import com.intellij.openapi.projectRoots.JdkUtil import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.SimpleJavaSdkType import com.intellij.openapi.roots.ModuleRootManager @@ -29,9 +31,20 @@ class JavaParametersBuilder(private val project: Project) { if (jdk == null && setDefaultSdk) { this.jdk = SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome()) } + this.setShortenCommandLine(getDefaultShortenCommandLineMethod(jdk?.homePath), project) } } + /** + * This method is partially copied from IDEA sources but doesn't check presence of dynamic.classpath property + * because we want to shorten command line for scratches and repl anyway + * @see [com.intellij.execution.ShortenCommandLine.getDefaultMethod] + */ + private fun getDefaultShortenCommandLineMethod(rootPath: String?): ShortenCommandLine { + if (rootPath != null && JdkUtil.isModularRuntime(rootPath)) return ShortenCommandLine.ARGS_FILE + return if (JdkUtil.useClasspathJar()) ShortenCommandLine.MANIFEST else ShortenCommandLine.CLASSPATH_FILE + } + fun withMainClassName(name: String?): JavaParametersBuilder { mainClassName = name return this diff --git a/idea/testData/run/LongCommandLine/module/src/main.kt b/idea/testData/run/LongCommandLine/module/src/main.kt new file mode 100644 index 00000000000..a5c4ace84b7 --- /dev/null +++ b/idea/testData/run/LongCommandLine/module/src/main.kt @@ -0,0 +1,6 @@ +package some.test + +fun main(args: Array) { + +} + diff --git a/idea/tests/org/jetbrains/kotlin/idea/repl/IdeReplExecutionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/repl/IdeReplExecutionTest.kt index 8bd10d31ecb..8652e8d832a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/repl/IdeReplExecutionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/repl/IdeReplExecutionTest.kt @@ -6,10 +6,12 @@ package org.jetbrains.kotlin.idea.repl import com.intellij.execution.console.LanguageConsoleImpl +import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.testFramework.PlatformTestCase import com.intellij.util.ui.UIUtil import org.jetbrains.kotlin.console.KotlinConsoleKeeper import org.jetbrains.kotlin.console.KotlinConsoleRunner +import org.jetbrains.kotlin.idea.run.createLibraryWithLongPaths import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.junit.Test import kotlin.reflect.KMutableProperty0 @@ -82,6 +84,16 @@ class IdeReplExecutionTest : PlatformTestCase() { assertFalse(consoleRunner.processHandler.isProcessTerminated, "Process accidentally terminated") } + @Test + fun testLongCommandLine() { + ModuleRootModificationUtil.addDependency(module, createLibraryWithLongPaths(project)) + + consoleRunner.dispose() + consoleRunner = KotlinConsoleKeeper.getInstance(project).run(module)!! + + testRunPossibility() + } + @Test fun testOnePlusOne() = testSimpleCommand("1 + 1", "2") @Test fun testPrintlnText() = "Hello, console world!".let { testSimpleCommand("println(\"$it\")", it) } @Test fun testDivisionByZeroException() = testSimpleCommand("1 / 0", "java.lang.ArithmeticException: / by zero") diff --git a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt index 1df40df1431..b5f3b144bc7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt @@ -133,6 +133,20 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() { Assert.assertTrue(javaParameters.classPath.rootDirs.contains(moduleWithDependencySrcDir)) } + fun testLongCommandLine() { + val myModule = configureModule(moduleDirPath("module"), getTestProject().baseDir).module + ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, addJdk(testRootDisposable, ::mockJdk)) + + ModuleRootModificationUtil.addDependency(myModule, createLibraryWithLongPaths(project)) + + val kotlinRunConfiguration = createConfigurationFromMain("some.test.main") + kotlinRunConfiguration.setModule(myModule) + + val javaParameters = getJavaRunParameters(kotlinRunConfiguration) + val commandLine = javaParameters.toCommandLine().commandLineString + Assert.assertTrue(commandLine.length < javaParameters.classPath.pathList.joinToString().length) + } + fun testClassesAndObjects() { doTest(ConfigLibraryUtil::configureKotlinRuntimeAndSdk) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt.181 b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt.181 index 78d5652ebd1..ff6692db677 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt.181 +++ b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt.181 @@ -134,6 +134,20 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() { Assert.assertTrue(javaParameters.classPath.rootDirs.contains(moduleWithDependencySrcDir)) } + fun testLongCommandLine() { + val myModule = configureModule(moduleDirPath("module"), getTestProject().baseDir).module + ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, addJdk(testRootDisposable, ::mockJdk)) + + ModuleRootModificationUtil.addDependency(myModule, createLibraryWithLongPaths(project)) + + val kotlinRunConfiguration = createConfigurationFromMain("some.test.main") + kotlinRunConfiguration.setModule(myModule) + + val javaParameters = getJavaRunParameters(kotlinRunConfiguration) + val commandLine = javaParameters.toCommandLine().commandLineString + Assert.assertTrue(commandLine.length < javaParameters.classPath.pathList.joinToString().length) + } + fun testClassesAndObjects() { doTest(ConfigLibraryUtil::configureKotlinRuntimeAndSdk) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/run/runConfigurationTestUtil.kt b/idea/tests/org/jetbrains/kotlin/idea/run/runConfigurationTestUtil.kt index bfc7870bb0d..e4c6ca94447 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/run/runConfigurationTestUtil.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/run/runConfigurationTestUtil.kt @@ -17,8 +17,16 @@ import com.intellij.execution.configurations.RunProfile import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ExecutionEnvironmentBuilder +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiElement import com.intellij.testFramework.MapDataContext +import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.junit.Assert fun getJavaRunParameters(configuration: RunConfiguration): JavaParameters { @@ -42,6 +50,31 @@ fun createConfigurationFromElement(element: PsiElement, save: Boolean = false): return runnerAndConfigurationSettings!!.configuration } +fun createLibraryWithLongPaths(project: Project): Library { + val maxCommandlineLengthWindows = 24500 + val maxFilenameLengthWindows = 245 + + return runWriteAction { + val modifiableModel = ProjectLibraryTable.getInstance(project).modifiableModel + val library = try { + modifiableModel.createLibrary("LibraryWithLongPaths", null) + } finally { + modifiableModel.commit() + } + with(library.modifiableModel) { + for (i in 0..maxCommandlineLengthWindows / maxFilenameLengthWindows) { + val tmpFile = VirtualFileManager.constructUrl( + LocalFileSystem.getInstance().protocol, + FileUtil.createTempFile("file$i", "a".repeat(maxFilenameLengthWindows)).path + ) + addRoot(tmpFile, OrderRootType.CLASSES) + } + commit() + } + return@runWriteAction library + } +} + private object MockExecutor : DefaultRunExecutor() { override fun getId() = DefaultRunExecutor.EXECUTOR_ID diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt index 37ff0053449..61e5a80c4c2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt @@ -88,15 +88,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { val sourceFile = File(testDataPath, fileName) val fileText = sourceFile.readText() - val scratchFile = ScratchRootType.getInstance().createScratchFile( - project, - sourceFile.name, - KotlinLanguage.INSTANCE, - fileText, - ScratchFileService.Option.create_if_missing - ) ?: error("Couldn't create scratch file ${sourceFile.path}") - - myFixture.openFileInEditor(scratchFile) + val scratchFile = createScratchFile(sourceFile.name, fileText) ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchFile, project) @@ -115,16 +107,6 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { launchScratch(scratchFile) - UIUtil.dispatchAllInvocationEvents() - - val start = System.currentTimeMillis() - // wait until output is displayed in editor or for 1 minute - while (ScratchCompilationSupport.isAnyInProgress() && (System.currentTimeMillis() - start) < 60000) { - Thread.sleep(100) - } - - UIUtil.dispatchAllInvocationEvents() - val doc = PsiDocumentManager.getInstance(project).getDocument(psiFile) ?: error("Document for ${psiFile.name} is null") val actualOutput = StringBuilder(psiFile.text) @@ -152,13 +134,36 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { KotlinTestUtils.assertEqualsToFile(expectedFile, actualOutput.toString()) } - private fun launchScratch(scratchFile: VirtualFile) { + protected fun createScratchFile(name: String, text: String): VirtualFile { + val scratchFile = ScratchRootType.getInstance().createScratchFile( + project, + name, + KotlinLanguage.INSTANCE, + text, + ScratchFileService.Option.create_if_missing + ) ?: error("Couldn't create scratch file") + + myFixture.openFileInEditor(scratchFile) + return scratchFile + } + + protected fun launchScratch(scratchFile: VirtualFile) { val action = RunScratchAction() val e = getActionEvent(scratchFile, action) action.beforeActionPerformedUpdate(e) Assert.assertTrue(e.presentation.isEnabled && e.presentation.isVisible) action.actionPerformed(e) + + UIUtil.dispatchAllInvocationEvents() + + val start = System.currentTimeMillis() + // wait until output is displayed in editor or for 1 minute + while (ScratchCompilationSupport.isAnyInProgress() && (System.currentTimeMillis() - start) < 60000) { + Thread.sleep(100) + } + + UIUtil.dispatchAllInvocationEvents() } private fun getActionEvent(virtualFile: VirtualFile, action: AnAction): TestActionEvent { diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/CustomScratchRunActionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/CustomScratchRunActionTest.kt new file mode 100644 index 00000000000..eb6bb2450a3 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/CustomScratchRunActionTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scratch + +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable +import com.intellij.openapi.roots.libraries.Library +import org.jetbrains.kotlin.idea.run.createLibraryWithLongPaths +import org.jetbrains.kotlin.idea.scratch.output.InlayScratchFileRenderer +import org.jetbrains.kotlin.idea.util.application.runWriteAction + +class CustomScratchRunActionTest : AbstractScratchRunActionTest() { + + fun testLongCommandLineWithRepl() { + assertEquals("RESULT: res0: kotlin.Int = 1", getOutput(true)) + } + + fun testLongCommandLine() { + assertEquals("RESULT: 1", getOutput(false)) + } + + private fun getOutput(isRepl: Boolean): String { + val fileText = "1" + val scratchFile = createScratchFile("scratch_1.kts", fileText) + + val (editor, scratchPanel) = getEditorWithScratchPanel(myManager, scratchFile) ?: error("Couldn't find scratch panel") + scratchPanel.scratchFile.saveOptions { + copy(isRepl = isRepl, isInteractiveMode = false) + } + + scratchPanel.setModule(myFixture.module) + + launchScratch(scratchFile) + + return editor.editor.inlayModel.getInlineElementsInRange(0, fileText.length) + .map { it.renderer } + .filterIsInstance() + .joinToString().trim() + } + + private val library: Library by lazy { + createLibraryWithLongPaths(project) + } + + override fun setUp() { + super.setUp() + + ModuleRootModificationUtil.addDependency(myFixture.module, library) + } + + override fun tearDown() { + removeLibraryWithLongPaths() + + super.tearDown() + } + + private fun removeLibraryWithLongPaths() { + runWriteAction { + val modifiableModel = ProjectLibraryTable.getInstance(project).modifiableModel + try { + modifiableModel.removeLibrary(library) + } finally { + modifiableModel.commit() + } + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchOptionsSaveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchOptionsSaveTest.kt index 47f4e7b88b0..1bd02784b48 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchOptionsSaveTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchOptionsSaveTest.kt @@ -5,9 +5,6 @@ package org.jetbrains.kotlin.idea.scratch -import com.intellij.ide.scratch.ScratchFileService -import com.intellij.ide.scratch.ScratchRootType -import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.scratch.ui.ScratchTopPanel import org.junit.Assert import javax.swing.JCheckBox @@ -17,18 +14,7 @@ import kotlin.reflect.full.declaredMemberProperties class ScratchOptionsSaveTest : AbstractScratchRunActionTest() { fun testOptionsSaveOnClosingFile() { - val fileText = "val a = 1" - - val scratchFile = ScratchRootType.getInstance().createScratchFile( - project, - "scratch_1.kts", - KotlinLanguage.INSTANCE, - fileText, - ScratchFileService.Option.create_if_missing - ) ?: error("Couldn't create scratch file") - - myManager.openFile(scratchFile, true) - + val scratchFile = createScratchFile("scratch_1.kts", "val a = 1") val (_, scratchPanelBeforeClosingFile) = getEditorWithScratchPanel(myManager, scratchFile) ?: error("Couldn't find scratch panel") Assert.assertEquals(