mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
(gtw) [CWM-5109] fixing tabs coloring when vcs changing rev changes
GitOrigin-RevId: fc6053e5c0a51eb4b886191ee06c15fd7f503af4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
939d5881da
commit
7b010bbabb
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,4 +18,4 @@ edu/dependencies/build
|
|||||||
native/**/build/
|
native/**/build/
|
||||||
stale_outputs_checked
|
stale_outputs_checked
|
||||||
/android
|
/android
|
||||||
/tools/intellij.ide.starter
|
/tools/ideTestingFramework/intellij.tools.ide.starter
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||||
package com.intellij.openapi.fileEditor.impl;
|
package com.intellij.openapi.fileEditor.impl;
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.colors.ColorKey;
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
@@ -34,7 +35,7 @@ public interface EditorTabColorProvider {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
default Color getEditorTabForegroundColor(@NotNull Project project, @NotNull VirtualFile file) { return null; }
|
default ColorKey getEditorTabForegroundColor(@NotNull Project project, @NotNull VirtualFile file) { return null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
package com.intellij.ui.tabs;
|
package com.intellij.ui.tabs;
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.colors.ColorKey;
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||||
import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
|
import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
|
||||||
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
|
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
|
||||||
import com.intellij.openapi.project.DumbAware;
|
import com.intellij.openapi.project.DumbAware;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.vcs.FileStatusManager;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.ui.FileColorManager;
|
import com.intellij.ui.FileColorManager;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -27,11 +29,11 @@ public class EditorTabColorProviderImpl implements EditorTabColorProvider, DumbA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Color getEditorTabForegroundColor(@NotNull Project project, @NotNull VirtualFile file) {
|
public @Nullable ColorKey getEditorTabForegroundColor(@NotNull Project project, @NotNull VirtualFile file) {
|
||||||
FileEditorManager manger = FileEditorManager.getInstance(project);
|
FileEditorManager manger = FileEditorManager.getInstance(project);
|
||||||
if (!(manger instanceof FileEditorManagerImpl)) return null;
|
if (!(manger instanceof FileEditorManagerImpl)) return null;
|
||||||
FileEditorManagerImpl fileEditorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project);
|
FileStatusManager fileStatusManager = FileStatusManager.getInstance(project);
|
||||||
return fileEditorManager.getFileColor(file);
|
return fileStatusManager.getStatus(file).getColorKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -434,7 +434,8 @@ open class EditorsSplitters internal constructor(val manager: FileEditorManagerI
|
|||||||
val manager = manager
|
val manager = manager
|
||||||
window.setForegroundAt(index, manager.getFileColor(file))
|
window.setForegroundAt(index, manager.getFileColor(file))
|
||||||
var resultAttributes = TextAttributes()
|
var resultAttributes = TextAttributes()
|
||||||
resultAttributes.foregroundColor = getForegroundColorForFile(manager.project, file)
|
|
||||||
|
resultAttributes.foregroundColor = colorScheme.getColor(getForegroundColorForFile(manager.project, file))
|
||||||
var attributes = if (manager.isProblem(file)) colorScheme.getAttributes(CodeInsightColors.ERRORS_ATTRIBUTES) else null
|
var attributes = if (manager.isProblem(file)) colorScheme.getAttributes(CodeInsightColors.ERRORS_ATTRIBUTES) else null
|
||||||
if (composite.isPreview) {
|
if (composite.isPreview) {
|
||||||
val italic = TextAttributes(null, null, null, null, Font.ITALIC)
|
val italic = TextAttributes(null, null, null, null, Font.ITALIC)
|
||||||
@@ -1014,4 +1015,4 @@ private fun getSplittersForProject(activeWindow: Window?, project: Project?): Ed
|
|||||||
?: return null
|
?: return null
|
||||||
val splitters = if (activeWindow == null) null else fileEditorManager.getSplittersFor(activeWindow)
|
val splitters = if (activeWindow == null) null else fileEditorManager.getSplittersFor(activeWindow)
|
||||||
return splitters ?: fileEditorManager.splitters
|
return splitters ?: fileEditorManager.splitters
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
package com.intellij.openapi.fileEditor.impl
|
package com.intellij.openapi.fileEditor.impl
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.colors.ColorKey
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import java.awt.Color
|
|
||||||
|
|
||||||
fun getForegroundColorForFile(project: Project, file: VirtualFile): Color? {
|
fun getForegroundColorForFile(project: Project, file: VirtualFile): ColorKey? {
|
||||||
return EditorTabColorProvider.EP_NAME.extensionList.firstOrNull()?.getEditorTabForegroundColor(project, file)
|
val first: EditorTabColorProvider = EditorTabColorProvider.EP_NAME.extensionList.first {
|
||||||
|
val editorTabColor = it.getEditorTabForegroundColor(project, file)
|
||||||
|
return@first editorTabColor != null
|
||||||
|
}
|
||||||
|
return first.getEditorTabForegroundColor(project, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
tools/intellij.ide.starter/.gitignore
vendored
4
tools/intellij.ide.starter/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
.idea
|
|
||||||
out
|
|
||||||
.gradle
|
|
||||||
build
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
### Starter for IntelliJ IDEA based IDE's
|
|
||||||
|
|
||||||
|
|
||||||
#### Overview
|
|
||||||
|
|
||||||
Starter helps you write tests/code, that will start IntelliJ-based IDE from installer in external process.
|
|
||||||
Aside from that, you may find useful functionality as below:
|
|
||||||
|
|
||||||
* execution commands in plugins (list of available commands described below)
|
|
||||||
* implementing your custom command to be invoked later in tests
|
|
||||||
* execution custom code (though, you cannot use external libraries here)
|
|
||||||
* integration with CI (optional)
|
|
||||||
* collecting test artifacts
|
|
||||||
* reporting of artifacts to CI (optional)
|
|
||||||
* run a test with a profiler (not yet included)
|
|
||||||
|
|
||||||
|
|
||||||
#### Supported products
|
|
||||||
|
|
||||||
* IDEA
|
|
||||||
* GoLand
|
|
||||||
* WebStorm
|
|
||||||
* PhpStorm
|
|
||||||
* DataGrip
|
|
||||||
* PyCharm
|
|
||||||
* RubyMine
|
|
||||||
* Android Studio
|
|
||||||
|
|
||||||
|
|
||||||
##### How to setup
|
|
||||||
|
|
||||||
Configure maven repositories in your `build.gradle` file
|
|
||||||
|
|
||||||
```
|
|
||||||
repositories {
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/maven-central" }
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/intellij-dependencies" }
|
|
||||||
|
|
||||||
maven { url = "https://www.jetbrains.com/intellij-repository/releases" }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Instead of `maven { url = "https://cache-redirector.jetbrains.com/maven-central" }` you may use `mavenCentral()`
|
|
||||||
|
|
||||||
If you're sure, that you need more recent version of packages, you might use
|
|
||||||
`maven { url = "https://www.jetbrains.com/intellij-repository/snapshots" }`
|
|
||||||
OR
|
|
||||||
`maven { url = "https://www.jetbrains.com/intellij-repository/nightly" }`
|
|
||||||
|
|
||||||
But don't forget to change accordingly version of the packages as such:
|
|
||||||
* nightly -> LATEST-TRUNK-SNAPSHOT
|
|
||||||
* snapshots -> LATEST-EAP-SNAPSHOT
|
|
||||||
* releases -> semver package version
|
|
||||||
|
|
||||||
Minimal setup example:
|
|
||||||
|
|
||||||
```
|
|
||||||
dependencies {
|
|
||||||
testImplementation("com.jetbrains.intellij.ide:ide-starter:LATEST-EAP-SNAPSHOT")
|
|
||||||
testImplementation("com.jetbrains.intellij.performanceTesting:performance-testing-commands:LATEST-EAP-SNAPSHOT")
|
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To make sure, that you will not get problem with Kotlin Duration, add the following
|
|
||||||
|
|
||||||
```
|
|
||||||
compileTestKotlin {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
|
||||||
freeCompilerArgs += [
|
|
||||||
"-Xopt-in=kotlin.time.ExperimentalTime"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### Run with JUnit4
|
|
||||||
|
|
||||||
[Example of simple test, that will download IntelliJ IDEA and start import of gradle project](https://github.com/JetBrains/intellij-community/tree/master/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/examples)
|
|
||||||
|
|
||||||
Don't forget to create appropriate classes for JUnit4StarterRule, IdeaCases.
|
|
||||||
|
|
||||||
##### Run with JUnit5
|
|
||||||
|
|
||||||
The key difference between running tests on JUnit4 and JUnit5 here will be migration from ExternalResource (in JUnit4) to TempDir (in JUnit5).
|
|
||||||
Though, for now we don't have an example for JUnit5StarterRule.
|
|
||||||
|
|
||||||
[JUnit4StarterRule implementation example is here](https://github.com/JetBrains/intellij-community/blob/master/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/examples/junit4/JUnit4StarterRule.kt)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### Available commands from plugins
|
|
||||||
|
|
||||||
Dependency `performance-testing-commands`
|
|
||||||
- waitForSmartMode()
|
|
||||||
- flushIndexes()
|
|
||||||
- setupProjectSdk(sdkName: String, sdkType: String, sdkPath: String)
|
|
||||||
- setupProjectSdk(sdkObject: SdkObject)
|
|
||||||
- setupProjectJdk(sdkName: String, sdkPath: String) = setupProjectSdk(sdkName, "JavaSDK", sdkPath)
|
|
||||||
- openFile(relativePath: String)
|
|
||||||
- openProject(projectPath: Path)
|
|
||||||
- reopenProject()
|
|
||||||
- goto(line: String, column: String)
|
|
||||||
- findUsages()
|
|
||||||
- inspectCode()
|
|
||||||
- checkOnRedCode()
|
|
||||||
- exitApp(forceExit: Boolean = true)
|
|
||||||
- exitAppWithTimeout(timeoutInSeconds: Long)
|
|
||||||
- memoryDump()
|
|
||||||
- dumpProjectFiles()
|
|
||||||
- compareProjectFiles(firstDir: String, secondDir: String)
|
|
||||||
- cleanCaches()
|
|
||||||
- doComplete()
|
|
||||||
- doComplete(times: Int)
|
|
||||||
- openProjectView()
|
|
||||||
- pressKey(key: String)
|
|
||||||
- delayType(command: String)
|
|
||||||
- doLocalInspection()
|
|
||||||
- altEnter(intention: String)
|
|
||||||
- callAltEnter(times: Int, intention: String = "")
|
|
||||||
- createAllServicesAndExtensions()
|
|
||||||
- runConfiguration(command: String)
|
|
||||||
- openFileWithTerminate(relativePath: String, terminateIdeInSeconds: Long)
|
|
||||||
- searchEverywhere(text: String)
|
|
||||||
- storeIndices()
|
|
||||||
- compareIndices()
|
|
||||||
- recoveryAction(action: RecoveryActionType)
|
|
||||||
- ... **TBD**
|
|
||||||
|
|
||||||
Dependency `performance-testing-maven-commands`
|
|
||||||
- importMavenProject()
|
|
||||||
|
|
||||||
Dependency `performance-testing-gradle-commands`
|
|
||||||
- importGradleProject()
|
|
||||||
|
|
||||||
#### What behaviour might be extended / modified
|
|
||||||
|
|
||||||
Everything, that initializes via DI framework (Kodein DI) might be modified in your code for your need.
|
|
||||||
[DI container initialization](https://github.com/JetBrains/intellij-community/blob/master/tools/intellij.ide.starter/src/com/intellij/ide/starter/di/diContainer.kt)
|
|
||||||
For example, you might write your own implementation of CIServer and provide it via DI.
|
|
||||||
|
|
||||||
NOTE: Be sure to use the same version of Kodein, that is used in `build.gradle` for starter project.
|
|
||||||
|
|
||||||
E.g:
|
|
||||||
```
|
|
||||||
di = DI {
|
|
||||||
extend(di)
|
|
||||||
bindSingleton<CIServer>(overrides = true) { YourImplementationOfCI() }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
ext.kotlin_version = '1.7.0'
|
|
||||||
ext.jackson_version = '2.13.3'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/maven-central" }
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/intellij-dependencies" }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main.kotlin.srcDirs += 'src'
|
|
||||||
test.kotlin.srcDirs += 'testSrc'
|
|
||||||
test.resources {
|
|
||||||
srcDirs "testResources"
|
|
||||||
includes = ["**/*.*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/maven-central" }
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/intellij-dependencies" }
|
|
||||||
|
|
||||||
maven { url = "https://www.jetbrains.com/intellij-repository/releases" }
|
|
||||||
maven { url = "https://www.jetbrains.com/intellij-repository/snapshots" }
|
|
||||||
maven { url = "https://www.jetbrains.com/intellij-repository/nightly" }
|
|
||||||
|
|
||||||
maven { url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/grazi/grazie-platform-public" }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.2"
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
|
||||||
implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version"
|
|
||||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
|
||||||
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
|
|
||||||
implementation "org.apache.httpcomponents:httpmime:4.5.13"
|
|
||||||
implementation "org.jetbrains.intellij.deps:gradle-api:7.4"
|
|
||||||
implementation "org.kodein.di:kodein-di-jvm:7.13.0"
|
|
||||||
implementation "org.rauschig:jarchivelib:1.2.0"
|
|
||||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3"
|
|
||||||
implementation "commons-logging:commons-logging:1.2"
|
|
||||||
implementation "com.jetbrains.qodana:qodana-sarif:0.1.99"
|
|
||||||
|
|
||||||
implementation('com.jetbrains.intellij.platform:lang-impl:LATEST-TRUNK-SNAPSHOT') {
|
|
||||||
exclude group: 'org.jetbrains.kotlinx'
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation('com.jetbrains.intellij.platform:ide-impl:LATEST-TRUNK-SNAPSHOT') {
|
|
||||||
exclude group: 'org.jetbrains.kotlinx'
|
|
||||||
exclude group: 'com.jetbrains.intellij.remoteDev'
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation 'com.jetbrains.intellij.performanceTesting:performance-testing-commands:LATEST-TRUNK-SNAPSHOT'
|
|
||||||
|
|
||||||
testImplementation 'com.jetbrains.intellij.performanceTesting:performance-testing-maven-commands:LATEST-TRUNK-SNAPSHOT'
|
|
||||||
testImplementation 'com.jetbrains.intellij.performanceTesting:performance-testing-gradle-commands:LATEST-TRUNK-SNAPSHOT'
|
|
||||||
testImplementation 'com.jetbrains.intellij.dynamicPlugins:dynamic-plugins-performance-testing-commands:LATEST-TRUNK-SNAPSHOT'
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
|
|
||||||
testImplementation 'org.junit.vintage:junit-vintage-engine:5.8.1'
|
|
||||||
testImplementation 'org.junit.platform:junit-platform-launcher:1.8.2'
|
|
||||||
|
|
||||||
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.3.2'
|
|
||||||
testImplementation 'org.mockito:mockito-core:3.12.4'
|
|
||||||
testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4'
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
testImplementation.exclude(group: 'com.jetbrains.intellij.remoteDev')
|
|
||||||
}
|
|
||||||
|
|
||||||
compileKotlin {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
|
||||||
freeCompilerArgs += [
|
|
||||||
"-version",
|
|
||||||
"-Xjvm-default=enable",
|
|
||||||
"-Xopt-in=kotlin.time.ExperimentalTime"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileTestKotlin {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
|
||||||
freeCompilerArgs += [
|
|
||||||
"-version",
|
|
||||||
"-Xjvm-default=enable",
|
|
||||||
"-Xopt-in=kotlin.time.ExperimentalTime"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
|
|
||||||
testLogging {
|
|
||||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task runOnlyUnitTests(type: Test) {
|
|
||||||
filter {
|
|
||||||
includeTestsMatching "com.intellij.ide.starter.tests.unit.*"
|
|
||||||
}
|
|
||||||
|
|
||||||
useJUnitPlatform()
|
|
||||||
|
|
||||||
testLogging {
|
|
||||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
234
tools/intellij.ide.starter/gradlew
vendored
234
tools/intellij.ide.starter/gradlew
vendored
@@ -1,234 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original authors.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
#
|
|
||||||
# https://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.
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
89
tools/intellij.ide.starter/gradlew.bat
vendored
89
tools/intellij.ide.starter/gradlew.bat
vendored
@@ -1,89 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="kotlin-language" name="Kotlin">
|
|
||||||
<configuration version="3" platform="JVM 11" allPlatforms="JVM [11]" useProjectSettings="false">
|
|
||||||
<compilerSettings>
|
|
||||||
<option name="additionalArguments" value="-version -Xjvm-default=enable -Xopt-in=kotlin.time.ExperimentalTime" />
|
|
||||||
</compilerSettings>
|
|
||||||
<compilerArguments>
|
|
||||||
<option name="jvmTarget" value="11" />
|
|
||||||
<option name="languageVersion" value="1.7" />
|
|
||||||
<option name="apiVersion" value="1.7" />
|
|
||||||
</compilerArguments>
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
|
|
||||||
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
|
|
||||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
|
||||||
<orderEntry type="library" name="jackson" level="project" />
|
|
||||||
<orderEntry type="library" name="jackson-databind" level="project" />
|
|
||||||
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
|
|
||||||
<orderEntry type="library" name="http-client" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle" level="project" />
|
|
||||||
<orderEntry type="library" name="kodein-di-jvm" level="project" />
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library name="rauschig.jarchivelib" type="repository">
|
|
||||||
<properties maven-id="org.rauschig:jarchivelib:1.2.0" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/rauschig/jarchivelib/1.2.0/jarchivelib-1.2.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-compress/1.21/commons-compress-1.21.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
<excluded>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-compress/1.21/commons-compress-1.21.jar!/" />
|
|
||||||
</excluded>
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="module" module-name="intellij.performanceTesting.commands" scope="TEST" />
|
|
||||||
<orderEntry type="module" module-name="intellij.performancePlugin.extended.maven.commands" scope="TEST" />
|
|
||||||
<orderEntry type="module" module-name="intellij.performancePlugin.extended.gradle.commands" scope="TEST" />
|
|
||||||
<orderEntry type="library" name="commons-logging" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="JUnit5Jupiter" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="JUnit5Params" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
|
|
||||||
<orderEntry type="module-library" scope="TEST">
|
|
||||||
<library name="mockito-junit-jupiter" type="repository">
|
|
||||||
<properties maven-id="org.mockito:mockito-junit-jupiter:3.12.4" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-junit-jupiter/3.12.4/mockito-junit-jupiter-3.12.4.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-core/3.12.4/mockito-core-3.12.4.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.11.13/byte-buddy-1.11.13.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.11.13/byte-buddy-agent-1.11.13.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.2/objenesis-3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-junit-jupiter/3.12.4/mockito-junit-jupiter-3.12.4-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-core/3.12.4/mockito-core-3.12.4-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.11.13/byte-buddy-1.11.13-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.11.13/byte-buddy-agent-1.11.13-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.2/objenesis-3.2-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2-javadoc.jar!/" />
|
|
||||||
</JAVADOC>
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library name="jackson-datatype-jsr310" type="repository">
|
|
||||||
<properties include-transitive-deps="false" maven-id="com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.1/jackson-datatype-jsr310-2.13.1.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="library" scope="TEST" name="kotlin-test-assertions-core-jvm" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="java-diff-utils" level="project" />
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library name="qodana-sarif" type="repository">
|
|
||||||
<properties include-transitive-deps="false" maven-id="com.jetbrains.qodana:qodana-sarif:0.1.95" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/qodana/qodana-sarif/0.1.95/qodana-sarif-0.1.95.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="library" name="gson" level="project" />
|
|
||||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
|
||||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<application>
|
|
||||||
<component name="GeneralSettings">
|
|
||||||
<option name="useSafeWrite" value="false" />
|
|
||||||
<option name="confirmExit" value="false"/>
|
|
||||||
<option name="showTipsOnStartup" value="false"/>
|
|
||||||
</component>
|
|
||||||
<component name="Registry">
|
|
||||||
<entry key="compiler.automake.allow.parallel" value="true"/>
|
|
||||||
<entry key="vcs.log.index.git" value="false"/>
|
|
||||||
<entry key="vcs.log.keep.up.to.date" value="false"/>
|
|
||||||
<entry key="actionSystem.playback.typecommand.delay" value="0"/>
|
|
||||||
</component>
|
|
||||||
</application>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.1.0",
|
|
||||||
"runs": [
|
|
||||||
{
|
|
||||||
"tool": {
|
|
||||||
"driver": {
|
|
||||||
"name": "intellij.ide.starter",
|
|
||||||
"fullName": "Intellij ide starter framework",
|
|
||||||
"version": "",
|
|
||||||
"taxa": [],
|
|
||||||
"language": "en-US",
|
|
||||||
"contents": [
|
|
||||||
"localizedData",
|
|
||||||
"nonLocalizedData"
|
|
||||||
],
|
|
||||||
"isComprehensive": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"invocations": [],
|
|
||||||
"language": "en-US",
|
|
||||||
"versionControlProvenance": [],
|
|
||||||
"results": [],
|
|
||||||
"automationDetails": {
|
|
||||||
"id": "",
|
|
||||||
"guid": ""
|
|
||||||
},
|
|
||||||
"newlineSequences": [
|
|
||||||
"\r\n",
|
|
||||||
"\n"
|
|
||||||
],
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
package com.intellij.ide.starter
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect
|
|
||||||
import com.intellij.ide.starter.exec.exec
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import com.intellij.ide.starter.utils.resolveInstalledJdk11
|
|
||||||
import org.gradle.internal.hash.Hashing
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.isDirectory
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve platform specific android studio installer and return paths
|
|
||||||
* @return Pair<InstallDir / InstallerFile>
|
|
||||||
*/
|
|
||||||
fun downloadAndroidStudio(): Pair<Path, File> {
|
|
||||||
val ext = when {
|
|
||||||
SystemInfo.isWindows -> "-windows.zip"
|
|
||||||
SystemInfo.isMac -> "-mac.zip"
|
|
||||||
SystemInfo.isLinux -> "-linux.tar.gz"
|
|
||||||
else -> error("Not supported OS")
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadUrl = "https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2021.1.1.11/android-studio-2021.1.1.11$ext"
|
|
||||||
val asFileName = downloadUrl.split("/").last()
|
|
||||||
val globalPaths by di.instance<GlobalPaths>()
|
|
||||||
val zipFile = globalPaths.getCacheDirectoryFor("android-studio").resolve(asFileName)
|
|
||||||
HttpClient.downloadIfMissing(downloadUrl, zipFile)
|
|
||||||
|
|
||||||
val installDir = globalPaths.getCacheDirectoryFor("builds") / "AI-211"
|
|
||||||
|
|
||||||
installDir.toFile().deleteRecursively()
|
|
||||||
|
|
||||||
val installerFile = zipFile.toFile()
|
|
||||||
|
|
||||||
return Pair(installDir, installerFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun downloadLatestAndroidSdk(javaHome: Path): Path {
|
|
||||||
val packages = listOf(
|
|
||||||
"build-tools;28.0.3",
|
|
||||||
//"cmake;3.10.2.4988404",
|
|
||||||
//"docs",
|
|
||||||
//"ndk;20.0.5594570",
|
|
||||||
"platforms;android-28",
|
|
||||||
"sources;android-28",
|
|
||||||
"platform-tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
val sdkManager = downloadSdkManager()
|
|
||||||
|
|
||||||
// we use unique home folder per installation to ensure only expected
|
|
||||||
// packages are included into the SDK home path
|
|
||||||
val packagesHash = Hashing.sha1().hashString(packages.joinToString("$"))
|
|
||||||
val home = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("android-sdk") / "sdk-roots" / "sdk-root-$packagesHash"
|
|
||||||
if (home.isDirectory() && home.toFile().walk().count() > 10) return home
|
|
||||||
|
|
||||||
val envVariablesWithJavaHome = System.getenv() + ("JAVA_HOME" to javaHome.toAbsolutePath().toString())
|
|
||||||
|
|
||||||
try {
|
|
||||||
home.toFile().mkdirs()
|
|
||||||
/// https://stackoverflow.com/questions/38096225/automatically-accept-all-sdk-licences
|
|
||||||
/// sending "yes" to the process in the STDIN :(
|
|
||||||
exec(presentablePurpose = "android-sdk-licenses",
|
|
||||||
workDir = home,
|
|
||||||
environmentVariables = envVariablesWithJavaHome,
|
|
||||||
args = listOf(sdkManager.toString(), "--sdk_root=$home", "--licenses"),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("[sdkmanager-err]"),
|
|
||||||
stdInBytes = "yes\n".repeat(10).toByteArray(), // it asks the confirmation at least two times
|
|
||||||
timeout = 15.minutes
|
|
||||||
)
|
|
||||||
|
|
||||||
//loading SDK
|
|
||||||
exec(presentablePurpose = "android-sdk-loading",
|
|
||||||
workDir = home,
|
|
||||||
environmentVariables = envVariablesWithJavaHome,
|
|
||||||
args = listOf(sdkManager.toString(), "--sdk_root=$home", "--list"),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("[sdkmanager-err]"),
|
|
||||||
timeout = 15.minutes
|
|
||||||
)
|
|
||||||
|
|
||||||
//loading SDK
|
|
||||||
exec(presentablePurpose = "android-sdk-installing",
|
|
||||||
workDir = home,
|
|
||||||
environmentVariables = envVariablesWithJavaHome,
|
|
||||||
args = listOf(sdkManager.toString(), "--sdk_root=$home", "--install", "--verbose") + packages,
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("[sdkmanager-err]"),
|
|
||||||
timeout = 15.minutes
|
|
||||||
)
|
|
||||||
return home
|
|
||||||
}
|
|
||||||
catch (t: Throwable) {
|
|
||||||
home.toFile().deleteRecursively()
|
|
||||||
throw Exception("Failed to prepare Android SDK to $home. ${t.message}", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun downloadSdkManager(): Path {
|
|
||||||
val url = when {
|
|
||||||
SystemInfo.isMac -> "https://dl.google.com/android/repository/commandlinetools-mac-6200805_latest.zip"
|
|
||||||
SystemInfo.isWindows -> "https://dl.google.com/android/repository/commandlinetools-win-6200805_latest.zip"
|
|
||||||
SystemInfo.isLinux -> "https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip"
|
|
||||||
else -> error("Unsupported OS: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val name = url.split("/").last()
|
|
||||||
val androidSdkCache = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("android-sdk")
|
|
||||||
val targetArchive = androidSdkCache / "archives" / name
|
|
||||||
val targetUnpack = androidSdkCache / "builds" / name
|
|
||||||
HttpClient.downloadIfMissing(url, targetArchive)
|
|
||||||
|
|
||||||
FileSystem.unpackIfMissing(targetArchive, targetUnpack)
|
|
||||||
|
|
||||||
val ext = if (SystemInfo.isWindows) ".bat" else ""
|
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
|
||||||
val sdkManager = targetUnpack.toFile().walk().first { it.endsWith("tools/bin/sdkmanager$ext") }
|
|
||||||
|
|
||||||
if (SystemInfo.isMac || SystemInfo.isLinux) {
|
|
||||||
sdkManager.setExecutable(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdkManager.toPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
downloadLatestAndroidSdk(resolveInstalledJdk11())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun IDETestContext.downloadAndroidPluginProject(): IDETestContext {
|
|
||||||
val projectHome = resolvedProjectHome
|
|
||||||
if (projectHome.toFile().name == "intellij-community-master" && !(projectHome / "android").toFile().exists()) {
|
|
||||||
val scriptName = "getPlugins.sh"
|
|
||||||
|
|
||||||
val script = (projectHome / scriptName).toFile()
|
|
||||||
assert(script.exists()) { "File $script does not exist" }
|
|
||||||
val scriptContent = script.readText()
|
|
||||||
|
|
||||||
val stdout = ExecOutputRedirect.ToString()
|
|
||||||
val stderr = ExecOutputRedirect.ToString()
|
|
||||||
exec(
|
|
||||||
"git-clone-android-plugin",
|
|
||||||
workDir = projectHome, timeout = 10.minutes,
|
|
||||||
args = scriptContent.split(" "),
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr
|
|
||||||
)
|
|
||||||
logOutput(stdout.read().trim())
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles different stuff, that is related to that particular build tool
|
|
||||||
*/
|
|
||||||
open class BuildTool(val type: BuildToolType, val testContext: IDETestContext)
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
|
|
||||||
open class BuildToolDefaultProvider(testContext: IDETestContext) : BuildToolProvider(testContext) {
|
|
||||||
override val maven: MavenBuildTool = MavenBuildTool(testContext)
|
|
||||||
|
|
||||||
override val gradle: GradleBuildTool = GradleBuildTool(testContext)
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
|
|
||||||
abstract class BuildToolProvider(val testContext: IDETestContext) {
|
|
||||||
abstract val maven: MavenBuildTool
|
|
||||||
|
|
||||||
abstract val gradle: GradleBuildTool
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
enum class BuildToolType {
|
|
||||||
MAVEN,
|
|
||||||
GRADLE
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.utils.logError
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.stream.IntStream
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import javax.xml.transform.TransformerFactory
|
|
||||||
import javax.xml.transform.dom.DOMSource
|
|
||||||
import javax.xml.transform.stream.StreamResult
|
|
||||||
import kotlin.io.path.*
|
|
||||||
|
|
||||||
open class GradleBuildTool(testContext: IDETestContext) : BuildTool(BuildToolType.GRADLE, testContext) {
|
|
||||||
val localGradleRepo: Path
|
|
||||||
get() = testContext.paths.tempDir.resolve("gradle")
|
|
||||||
|
|
||||||
fun useNewGradleLocalCache(): GradleBuildTool {
|
|
||||||
localGradleRepo.toFile().mkdirs()
|
|
||||||
testContext.addVMOptionsPatch { addSystemProperty("gradle.user.home", localGradleRepo.toString()) }
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeGradleConfigFiles(): GradleBuildTool {
|
|
||||||
logOutput("Removing Gradle config files in ${testContext.resolvedProjectHome} ...")
|
|
||||||
|
|
||||||
testContext.resolvedProjectHome.toFile().walkTopDown()
|
|
||||||
.forEach {
|
|
||||||
if (it.isFile && (it.extension == "gradle" || (it.name in listOf("gradlew", "gradlew.bat", "gradle.properties")))) {
|
|
||||||
it.delete()
|
|
||||||
logOutput("File ${it.path} is deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addPropertyToGradleProperties(property: String, value: String): GradleBuildTool {
|
|
||||||
val projectDir = testContext.resolvedProjectHome
|
|
||||||
val gradleProperties = projectDir.resolve("gradle.properties")
|
|
||||||
val lineWithTheSameProperty = gradleProperties.readLines().singleOrNull { it.contains(property) }
|
|
||||||
|
|
||||||
if (lineWithTheSameProperty != null) {
|
|
||||||
if (lineWithTheSameProperty.contains(value)) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
val newValue = lineWithTheSameProperty.substringAfter("$property=") + " $value"
|
|
||||||
val tempFile = File.createTempFile("newContent", ".txt").toPath()
|
|
||||||
gradleProperties.forEachLine { line ->
|
|
||||||
tempFile.appendText(when {
|
|
||||||
line.contains(property) -> "$property=$newValue" + System.getProperty("line.separator")
|
|
||||||
else -> line + System.getProperty("line.separator")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
gradleProperties.writeText(tempFile.readText())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gradleProperties.appendLines(listOf("$property=$value"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setGradleJvmInProject(useJavaHomeAsGradleJvm: Boolean = true): GradleBuildTool {
|
|
||||||
try {
|
|
||||||
val ideaDir = testContext.resolvedProjectHome.resolve(".idea")
|
|
||||||
val gradleXml = ideaDir.resolve("gradle.xml")
|
|
||||||
|
|
||||||
if (gradleXml.toFile().exists()) {
|
|
||||||
val xmlDoc = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().parse(gradleXml.toFile())
|
|
||||||
xmlDoc.documentElement.normalize()
|
|
||||||
|
|
||||||
val gradleSettings = xmlDoc.getElementsByTagName("GradleProjectSettings")
|
|
||||||
if (gradleSettings.length == 1) {
|
|
||||||
val options = (gradleSettings.item(0) as Element).getElementsByTagName("option")
|
|
||||||
IntStream
|
|
||||||
.range(0, options.length)
|
|
||||||
.mapToObj { i -> options.item(i) as Element }
|
|
||||||
.filter { it.getAttribute("name") == "gradleJvm" }
|
|
||||||
.findAny()
|
|
||||||
.ifPresent { node -> gradleSettings.item(0).removeChild(node) }
|
|
||||||
|
|
||||||
if (useJavaHomeAsGradleJvm) {
|
|
||||||
val option = xmlDoc.createElement("option")
|
|
||||||
option.setAttribute("name", "gradleJvm")
|
|
||||||
option.setAttribute("value", "#JAVA_HOME")
|
|
||||||
gradleSettings.item(0).appendChild(option)
|
|
||||||
}
|
|
||||||
|
|
||||||
val source = DOMSource(xmlDoc)
|
|
||||||
val outputStream = FileOutputStream(gradleXml.toFile())
|
|
||||||
val result = StreamResult(outputStream)
|
|
||||||
val transformerFactory = TransformerFactory.newInstance()
|
|
||||||
val transformer = transformerFactory.newTransformer()
|
|
||||||
transformer.transform(source, result)
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.intellij.ide.starter.build.tool
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
open class MavenBuildTool(testContext: IDETestContext) : BuildTool(BuildToolType.MAVEN, testContext) {
|
|
||||||
val localMavenRepo: Path
|
|
||||||
get() = testContext.paths.tempDir.resolve(".m3").resolve("repository")
|
|
||||||
|
|
||||||
fun useNewMavenLocalRepository(): MavenBuildTool {
|
|
||||||
localMavenRepo.toFile().mkdirs()
|
|
||||||
testContext.addVMOptionsPatch { addSystemProperty("idea.force.m2.home", localMavenRepo.toString()) }
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeMavenConfigFiles(): MavenBuildTool {
|
|
||||||
logOutput("Removing Maven config files in ${testContext.resolvedProjectHome} ...")
|
|
||||||
|
|
||||||
testContext.resolvedProjectHome.toFile().walkTopDown()
|
|
||||||
.forEach {
|
|
||||||
if (it.isFile && it.name == "pom.xml") {
|
|
||||||
it.delete()
|
|
||||||
logOutput("File ${it.path} is deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
open class Event<T>(
|
|
||||||
state: EventState = EventState.UNDEFINED,
|
|
||||||
val data: T
|
|
||||||
) : Signal(state)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author https://github.com/Kosert/FlowBus
|
|
||||||
* @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE
|
|
||||||
* */
|
|
||||||
interface EventCallback<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function will be called for received event
|
|
||||||
*/
|
|
||||||
fun onEvent(event: T)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
enum class EventState {
|
|
||||||
UNDEFINED,
|
|
||||||
|
|
||||||
/** Right before the action */
|
|
||||||
BEFORE,
|
|
||||||
|
|
||||||
/** After the action was completed */
|
|
||||||
AFTER
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.utils.catchAll
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author https://github.com/Kosert/FlowBus
|
|
||||||
* @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE
|
|
||||||
* Class for receiving events posted to [FlowBus]
|
|
||||||
*
|
|
||||||
* @param bus [FlowBus] instance to subscribe to. If not set, [StarterBus] will be used
|
|
||||||
*/
|
|
||||||
open class EventsReceiver @JvmOverloads constructor(
|
|
||||||
private val bus: FlowBus = StarterBus
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val jobs = mutableMapOf<Class<*>, Job>()
|
|
||||||
|
|
||||||
private var returnDispatcher: CoroutineDispatcher = Dispatchers.Default
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the `CoroutineDispatcher` which will be used to launch your callbacks.
|
|
||||||
*
|
|
||||||
* If this [EventsReceiver] was created on the main thread the default dispatcher will be [Dispatchers.Main].
|
|
||||||
* In any other case [Dispatchers.Default] will be used.
|
|
||||||
*/
|
|
||||||
fun returnOn(dispatcher: CoroutineDispatcher): EventsReceiver {
|
|
||||||
returnDispatcher = dispatcher
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe to events that are type of [clazz] with the given [callback] function.
|
|
||||||
* The [callback] can be called immediately if event of type [clazz] is present in the flow.
|
|
||||||
*
|
|
||||||
* @param clazz Type of event to subscribe to
|
|
||||||
* @param skipRetained Skips event already present in the flow. This is `false` by default
|
|
||||||
* @param callback The callback function
|
|
||||||
* @return This instance of [EventsReceiver] for chaining
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
fun <T : Any> subscribeTo(
|
|
||||||
clazz: Class<T>,
|
|
||||||
skipRetained: Boolean = false,
|
|
||||||
callback: suspend (event: T) -> Unit
|
|
||||||
): EventsReceiver {
|
|
||||||
|
|
||||||
if (jobs.containsKey(clazz))
|
|
||||||
throw IllegalArgumentException("Already subscribed for event type: $clazz")
|
|
||||||
|
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
|
||||||
throw throwable
|
|
||||||
}
|
|
||||||
|
|
||||||
val job = CoroutineScope(Job() + Dispatchers.Default + exceptionHandler).launch {
|
|
||||||
bus.forEvent(clazz)
|
|
||||||
.drop(if (skipRetained) 1 else 0)
|
|
||||||
.filterNotNull()
|
|
||||||
.collect {
|
|
||||||
catchAll {
|
|
||||||
withContext(returnDispatcher) { callback(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs[clazz] = job
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of [subscribeTo] that uses an instance of [EventCallback] as callback.
|
|
||||||
*
|
|
||||||
* @param clazz Type of event to subscribe to
|
|
||||||
* @param skipRetained Skips event already present in the flow. This is `false` by default
|
|
||||||
* @param callback Interface with implemented callback function
|
|
||||||
* @return This instance of [EventsReceiver] for chaining
|
|
||||||
* @see [subscribeTo]
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
fun <T : Any> subscribeTo(
|
|
||||||
clazz: Class<T>,
|
|
||||||
callback: EventCallback<T>,
|
|
||||||
skipRetained: Boolean = false
|
|
||||||
): EventsReceiver = subscribeTo(clazz, skipRetained) { callback.onEvent(it) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from events type of [clazz]
|
|
||||||
*/
|
|
||||||
fun <T : Any> unsubscribe(clazz: Class<T>) {
|
|
||||||
jobs.remove(clazz)?.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from all events
|
|
||||||
*/
|
|
||||||
fun unsubscribe() {
|
|
||||||
jobs.values.forEach { it.cancel() }
|
|
||||||
jobs.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author https://github.com/Kosert/FlowBus
|
|
||||||
* @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE
|
|
||||||
**/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see FlowBus.dropEvent
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> FlowBus.dropEvent() = dropEvent(T::class.java)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see FlowBus.getFlow
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> FlowBus.getFlow() = getFlow(T::class.java)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplified [EventsReceiver.subscribeTo] for Kotlin.
|
|
||||||
* Type of event is automatically inferred from [callback] parameter type.
|
|
||||||
*
|
|
||||||
* @param skipRetained Skips event already present in the flow. This is `false` by default
|
|
||||||
* @param callback The callback function
|
|
||||||
* @return This instance of [EventsReceiver] for chaining
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> EventsReceiver.subscribe(skipRetained: Boolean = false, noinline callback: suspend (event: T) -> Unit): EventsReceiver {
|
|
||||||
return subscribeTo(T::class.java, skipRetained, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of [subscribe] that uses an instance of [EventCallback] as callback.
|
|
||||||
*
|
|
||||||
* @param skipRetained Skips event already present in the flow. This is `false` by default
|
|
||||||
* @param callback Interface with implemented callback function
|
|
||||||
* @return This instance of [EventsReceiver] for chaining
|
|
||||||
* @see [subscribe]
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> EventsReceiver.subscribe(callback: EventCallback<T>, skipRetained: Boolean = false): EventsReceiver {
|
|
||||||
return subscribeTo(T::class.java, callback, skipRetained)
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author https://github.com/Kosert/FlowBus
|
|
||||||
* @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE
|
|
||||||
*
|
|
||||||
* This class holds all shared flows and handles event posting.
|
|
||||||
* You can use [StarterBus] that is just plain instance of this class or create your own implementation.
|
|
||||||
*/
|
|
||||||
open class FlowBus {
|
|
||||||
|
|
||||||
private val flows = mutableMapOf<Class<*>, MutableSharedFlow<*>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a MutableSharedFlow for events of the given type. Creates new if one doesn't exist.
|
|
||||||
* @return MutableSharedFlow for events that are instances of clazz
|
|
||||||
*/
|
|
||||||
internal fun <T : Any> forEvent(clazz: Class<T>): MutableSharedFlow<T?> {
|
|
||||||
return flows.getOrPut(clazz) {
|
|
||||||
MutableSharedFlow<T?>(extraBufferCapacity = 5000)
|
|
||||||
} as MutableSharedFlow<T?>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Flow for events of the given type.
|
|
||||||
*
|
|
||||||
* **This flow never completes.**
|
|
||||||
*
|
|
||||||
* The returned Flow is _hot_ as it is based on a [SharedFlow]. This means a call to [collect] never completes normally, calling [toList] will suspend forever, etc.
|
|
||||||
*
|
|
||||||
* You are entirely responsible to cancel this flow. To cancel this flow, the scope in which the coroutine is running needs to be cancelled.
|
|
||||||
* @see [SharedFlow]
|
|
||||||
*/
|
|
||||||
fun <T : Any> getFlow(clazz: Class<T>): Flow<T> {
|
|
||||||
return forEvent(clazz).filterNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Posts new event to SharedFlow of the [event] type.
|
|
||||||
* @param retain If the [event] should be retained in the flow for future subscribers. This is true by default.
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
fun <T : Any> post(event: T, retain: Boolean = true) {
|
|
||||||
val flow = forEvent(event.javaClass)
|
|
||||||
|
|
||||||
flow.tryEmit(event).also {
|
|
||||||
if (!it)
|
|
||||||
throw IllegalStateException("SharedFlow cannot take element, this should never happen")
|
|
||||||
}
|
|
||||||
if (!retain) {
|
|
||||||
// without starting a coroutine here, the event is dropped immediately
|
|
||||||
// and not delivered to subscribers
|
|
||||||
CoroutineScope(Job() + Dispatchers.Default).launch {
|
|
||||||
dropEvent(event.javaClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes retained event of type [clazz]
|
|
||||||
*/
|
|
||||||
fun <T> dropEvent(clazz: Class<T>) {
|
|
||||||
if (!flows.contains(clazz)) return
|
|
||||||
val channel = flows[clazz] as MutableSharedFlow<T?>
|
|
||||||
channel.tryEmit(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all retained events
|
|
||||||
*/
|
|
||||||
fun dropAll() {
|
|
||||||
flows.values.forEach {
|
|
||||||
(it as MutableSharedFlow<Any?>).tryEmit(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
open class Signal(val state: EventState = EventState.UNDEFINED)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
object StarterBus : FlowBus()
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.intellij.ide.starter.bus
|
|
||||||
|
|
||||||
object StarterListener : EventsReceiver()
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ci
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
//TODO(Describe different approaches for different CIs about publishing artifacts)
|
|
||||||
interface CIServer {
|
|
||||||
val isBuildRunningOnCI: Boolean
|
|
||||||
val buildNumber: String
|
|
||||||
val branchName: String
|
|
||||||
val buildParams: Map<String, String>
|
|
||||||
|
|
||||||
fun publishArtifact(source: Path,
|
|
||||||
artifactPath: String,
|
|
||||||
artifactName: String = source.fileName.toString())
|
|
||||||
|
|
||||||
fun reportTestFailure(testName: String, message: String, details: String)
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ci
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.utils.logError
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
/** Dummy for CI server */
|
|
||||||
object NoCIServer : CIServer {
|
|
||||||
override val isBuildRunningOnCI: Boolean = false
|
|
||||||
override val buildNumber: String = ""
|
|
||||||
override val branchName: String = ""
|
|
||||||
override val buildParams: Map<String, String> = mapOf()
|
|
||||||
|
|
||||||
override fun publishArtifact(source: Path, artifactPath: String, artifactName: String) {
|
|
||||||
logError("No logic for publishing artifacts has been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reportTestFailure(testName: String, message: String, details: String) {
|
|
||||||
logError("No logic for reporting test failure has been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import com.intellij.ide.starter.community.model.ReleaseInfo
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import org.apache.http.client.methods.HttpGet
|
|
||||||
|
|
||||||
|
|
||||||
class JetBrainsDataServiceClient {
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val DATA_SERVICE_URL = "https://data.services.jetbrains.com"
|
|
||||||
|
|
||||||
fun getReleases(request: ProductInfoRequestParameters): Map<String, List<ReleaseInfo>> {
|
|
||||||
return HttpClient.sendRequest(
|
|
||||||
HttpGet("$DATA_SERVICE_URL/products/releases$request").apply {
|
|
||||||
addHeader("Content-Type", "application/json")
|
|
||||||
addHeader("Accept", "application/json")
|
|
||||||
}) {
|
|
||||||
jacksonObjectMapper()
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
||||||
.registerModule(JavaTimeModule())
|
|
||||||
.readValue(it.entity.content, object : TypeReference<Map<String, List<ReleaseInfo>>>() {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community
|
|
||||||
|
|
||||||
import org.apache.http.client.utils.URIBuilder
|
|
||||||
|
|
||||||
data class ProductInfoRequestParameters(val code: String,
|
|
||||||
val type: String = "release",
|
|
||||||
val majorVersion: String = "",
|
|
||||||
val build: String = "") {
|
|
||||||
fun toUriQuery(): URIBuilder {
|
|
||||||
val builder = URIBuilder()
|
|
||||||
|
|
||||||
if (code.isNotBlank()) builder.addParameter("code", code)
|
|
||||||
if (type.isNotBlank()) builder.addParameter("type", type)
|
|
||||||
if (majorVersion.isNotBlank()) builder.addParameter("majorVersion", majorVersion)
|
|
||||||
if (build.isNotBlank()) builder.addParameter("build", build)
|
|
||||||
|
|
||||||
return builder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return toUriQuery().toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IdeDownloader
|
|
||||||
import com.intellij.ide.starter.ide.IdeInstaller
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.system.OsType
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
|
|
||||||
object PublicIdeDownloader : IdeDownloader {
|
|
||||||
|
|
||||||
override fun downloadIdeInstaller(ideInfo: IdeInfo, installerDirectory: Path): IdeInstaller {
|
|
||||||
val releaseInfoMap = JetBrainsDataServiceClient.getReleases(
|
|
||||||
ProductInfoRequestParameters(code = ideInfo.productCode,
|
|
||||||
type = ideInfo.buildType,
|
|
||||||
build = ideInfo.buildNumber)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (releaseInfoMap.size == 1) {
|
|
||||||
//Find the latest build
|
|
||||||
val possibleBuild = releaseInfoMap.values.first().sortedByDescending { it.date }.first()
|
|
||||||
|
|
||||||
val downloadLink: String = when (SystemInfo.getOsType()) {
|
|
||||||
OsType.Linux -> possibleBuild.downloads.linux!!.link
|
|
||||||
OsType.MacOS -> {
|
|
||||||
if (SystemInfo.OS_ARCH == "aarch64") possibleBuild.downloads.macM1!!.link // macM1
|
|
||||||
else possibleBuild.downloads.mac!!.link
|
|
||||||
}
|
|
||||||
OsType.Windows -> possibleBuild.downloads.windows!!.link
|
|
||||||
else -> throw RuntimeException("Unsupported OS ${SystemInfo.getOsType()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val installerFile = installerDirectory.resolve(
|
|
||||||
"${ideInfo.installerFilePrefix}-" + possibleBuild.build.replace(".", "") + ideInfo.installerFileExt
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!installerFile.exists()) {
|
|
||||||
logOutput("Downloading $ideInfo ...")
|
|
||||||
HttpClient.download(downloadLink, installerFile)
|
|
||||||
}
|
|
||||||
else logOutput("Installer file $installerFile already exists. Skipping download.")
|
|
||||||
|
|
||||||
return IdeInstaller(installerFile, possibleBuild.build)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw RuntimeException("Only one product can be handled. Found ${releaseInfoMap.keys}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community.model
|
|
||||||
|
|
||||||
enum class BuildType(val type: String) {
|
|
||||||
RELEASE("release"),
|
|
||||||
EAP("eap")
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community.model
|
|
||||||
|
|
||||||
data class IdeModel(val releaseInfo: Map<Int, ReleaseInfo>)
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.intellij.ide.starter.community.model
|
|
||||||
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
|
|
||||||
data class ReleaseInfo(val date: LocalDate,
|
|
||||||
val type: String,
|
|
||||||
val version: String,
|
|
||||||
val majorVersion: String,
|
|
||||||
val build: String,
|
|
||||||
val downloads: Download)
|
|
||||||
|
|
||||||
data class Download(val linux: OperatingSystem?,
|
|
||||||
val mac: OperatingSystem?,
|
|
||||||
val macM1: OperatingSystem?,
|
|
||||||
val windows: OperatingSystem?)
|
|
||||||
|
|
||||||
data class OperatingSystem(val link: String)
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.intellij.ide.starter.data
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.models.TestCase
|
|
||||||
|
|
||||||
abstract class TestCaseTemplate(val ideInfo: IdeInfo) {
|
|
||||||
protected fun getTemplate() = TestCase(ideInfo = ideInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.intellij.ide.starter.di
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.build.tool.BuildToolDefaultProvider
|
|
||||||
import com.intellij.ide.starter.build.tool.BuildToolProvider
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.ci.NoCIServer
|
|
||||||
import com.intellij.ide.starter.community.PublicIdeDownloader
|
|
||||||
import com.intellij.ide.starter.ide.*
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.models.IdeProduct
|
|
||||||
import com.intellij.ide.starter.models.IdeProductImp
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.path.InstallerGlobalPaths
|
|
||||||
import com.intellij.ide.starter.plugins.PluginConfigurator
|
|
||||||
import com.intellij.ide.starter.report.publisher.ReportPublisher
|
|
||||||
import com.intellij.ide.starter.report.publisher.impl.ConsoleTestResultPublisher
|
|
||||||
import com.intellij.ide.starter.report.publisher.impl.QodanaTestResultPublisher
|
|
||||||
import com.intellij.ide.starter.runner.CodeBuilderHost
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.bindFactory
|
|
||||||
import org.kodein.di.bindSingleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reinitialize / override bindings for this DI container in your module before executing tests
|
|
||||||
* https://docs.kodein.org/kodein-di/7.9/core/bindings.html
|
|
||||||
*
|
|
||||||
* E.g:
|
|
||||||
* ```
|
|
||||||
* di = DI {
|
|
||||||
* extend(di)
|
|
||||||
* bindSingleton<GlobalPaths>(overrides = true) { YourImplementationOfPaths() }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
var di = DI {
|
|
||||||
bindSingleton<GlobalPaths> { InstallerGlobalPaths() }
|
|
||||||
bindSingleton<CIServer> { NoCIServer }
|
|
||||||
bindSingleton<CodeInjector> { CodeBuilderHost() }
|
|
||||||
bindFactory { testContext: IDETestContext -> PluginConfigurator(testContext) }
|
|
||||||
bindSingleton<IdeDownloader> { PublicIdeDownloader }
|
|
||||||
bindFactory<IdeInfo, IdeInstallator> { ideInfo ->
|
|
||||||
if (ideInfo.productCode == "AI") {
|
|
||||||
AndroidInstaller()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SimpleInstaller()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bindFactory<IDETestContext, BuildToolProvider> { testContext: IDETestContext -> BuildToolDefaultProvider(testContext) }
|
|
||||||
bindSingleton<List<ReportPublisher>> { listOf(ConsoleTestResultPublisher, QodanaTestResultPublisher) }
|
|
||||||
bindSingleton<IdeProduct> { IdeProductImp }
|
|
||||||
}.apply {
|
|
||||||
logOutput("DI was initialized")
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package com.intellij.ide.starter.exec
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect.*
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.io.File
|
|
||||||
import java.io.PrintWriter
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies how a child process' stdout or stderr must be redirected in the current process:
|
|
||||||
* - [NoRedirect]
|
|
||||||
* - [ToFile]
|
|
||||||
* - [ToStdOut]
|
|
||||||
*/
|
|
||||||
sealed class ExecOutputRedirect {
|
|
||||||
|
|
||||||
open fun open() = Unit
|
|
||||||
|
|
||||||
open fun close() = Unit
|
|
||||||
|
|
||||||
open fun redirectLine(line: String) = Unit
|
|
||||||
|
|
||||||
abstract fun read(): String
|
|
||||||
|
|
||||||
abstract override fun toString(): String
|
|
||||||
|
|
||||||
protected fun reportOnStdoutIfNecessary(line: String) {
|
|
||||||
// Propagate the IDE debugger attach service message.
|
|
||||||
if (line.contains("Listening for transport dt_socket")) {
|
|
||||||
println(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object NoRedirect : ExecOutputRedirect() {
|
|
||||||
override fun redirectLine(line: String) {
|
|
||||||
reportOnStdoutIfNecessary(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read() = ""
|
|
||||||
|
|
||||||
override fun toString() = "ignored"
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ToFile(val outputFile: File) : ExecOutputRedirect() {
|
|
||||||
|
|
||||||
private lateinit var writer: PrintWriter
|
|
||||||
|
|
||||||
override fun open() {
|
|
||||||
outputFile.apply {
|
|
||||||
toPath().parent.createDirectories()
|
|
||||||
createNewFile()
|
|
||||||
}
|
|
||||||
writer = outputFile.printWriter()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
writer.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun redirectLine(line: String) {
|
|
||||||
reportOnStdoutIfNecessary(line)
|
|
||||||
writer.println(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(): String {
|
|
||||||
if (!outputFile.exists()) {
|
|
||||||
logOutput("File $outputFile doesn't exist")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputFile.readText()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = "file $outputFile"
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ToStdOut(val prefix: String) : ExecOutputRedirect() {
|
|
||||||
override fun redirectLine(line: String) {
|
|
||||||
reportOnStdoutIfNecessary(line)
|
|
||||||
logOutput(" $prefix $line")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read() = ""
|
|
||||||
|
|
||||||
override fun toString() = "stdout"
|
|
||||||
}
|
|
||||||
|
|
||||||
class ToString : ExecOutputRedirect() {
|
|
||||||
|
|
||||||
private val stringBuilder = StringBuilder()
|
|
||||||
|
|
||||||
override fun redirectLine(line: String) {
|
|
||||||
reportOnStdoutIfNecessary(line)
|
|
||||||
stringBuilder.appendLine(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read() = stringBuilder.toString()
|
|
||||||
|
|
||||||
override fun toString() = "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
package com.intellij.ide.starter.exec
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.catchAll
|
|
||||||
import com.intellij.ide.starter.utils.logError
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.io.IOException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
import kotlin.io.path.readText
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
private fun redirectProcessOutput(
|
|
||||||
process: Process,
|
|
||||||
outOrErrStream: Boolean,
|
|
||||||
redirectOutput: ExecOutputRedirect
|
|
||||||
): Thread {
|
|
||||||
val inputStream = if (outOrErrStream) process.inputStream else process.errorStream
|
|
||||||
return thread(start = true, isDaemon = true, name = "Redirect " + (if (outOrErrStream) "stdout" else "stderr")) {
|
|
||||||
val reader = inputStream.bufferedReader()
|
|
||||||
redirectOutput.open()
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
val line = try {
|
|
||||||
reader.readLine() ?: break
|
|
||||||
}
|
|
||||||
catch (e: IOException) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
redirectOutput.redirectLine(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
redirectOutput.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun redirectProcessInput(process: Process, inputBytes: ByteArray): Thread? {
|
|
||||||
if (inputBytes.isEmpty()) {
|
|
||||||
catchAll { process.outputStream.close() }
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return thread(start = true, isDaemon = true, name = "Redirect input") {
|
|
||||||
catchAll {
|
|
||||||
process.outputStream.use {
|
|
||||||
it.write(inputBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ProcessBuilder.actualizeEnvVariables(environmentVariables: Map<String, String> = System.getenv(),
|
|
||||||
onlyEnrichExistedEnvVariables: Boolean = false): ProcessBuilder {
|
|
||||||
val processEnvironment = environment()
|
|
||||||
if (processEnvironment == environmentVariables) return this
|
|
||||||
|
|
||||||
environmentVariables.filter { it.value == null }.forEach { logError("Env variable: ${it.key} has null value ${it.value}") }
|
|
||||||
val notNullValues = environmentVariables.filter { it.value != null }
|
|
||||||
|
|
||||||
// env variables enrichment
|
|
||||||
processEnvironment.putAll(notNullValues)
|
|
||||||
|
|
||||||
if (!onlyEnrichExistedEnvVariables) {
|
|
||||||
val missingKeys = processEnvironment.keys - notNullValues.keys
|
|
||||||
missingKeys.forEach { key -> processEnvironment.remove(key) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new process and wait for it's completion
|
|
||||||
*/
|
|
||||||
@Throws(ExecTimeoutException::class)
|
|
||||||
fun exec(
|
|
||||||
presentablePurpose: String,
|
|
||||||
workDir: Path?,
|
|
||||||
timeout: Duration = 10.minutes,
|
|
||||||
environmentVariables: Map<String, String> = System.getenv(),
|
|
||||||
args: List<String>,
|
|
||||||
errorDiagnosticFiles: List<Path> = emptyList(),
|
|
||||||
stdoutRedirect: ExecOutputRedirect = ExecOutputRedirect.NoRedirect,
|
|
||||||
stderrRedirect: ExecOutputRedirect = ExecOutputRedirect.NoRedirect,
|
|
||||||
onProcessCreated: (Process, Long) -> Unit = { _, _ -> },
|
|
||||||
onBeforeKilled: (Process, Long) -> Unit = { _, _ -> },
|
|
||||||
stdInBytes: ByteArray = byteArrayOf(),
|
|
||||||
onlyEnrichExistedEnvVariables: Boolean = false
|
|
||||||
) {
|
|
||||||
logOutput(buildString {
|
|
||||||
appendLine("Running external process for `$presentablePurpose`")
|
|
||||||
appendLine(" Working directory: $workDir")
|
|
||||||
appendLine(" Arguments: [${args.joinToString()}]")
|
|
||||||
appendLine(" STDOUT will be redirected to: $stdoutRedirect")
|
|
||||||
appendLine(" STDERR will be redirected to: $stderrRedirect")
|
|
||||||
append(" STDIN is empty: " + stdInBytes.isEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
require(args.isNotEmpty()) { "Arguments must be not empty to start external process" }
|
|
||||||
|
|
||||||
val processBuilder = ProcessBuilder()
|
|
||||||
.directory(workDir?.toFile())
|
|
||||||
.command(*args.toTypedArray())
|
|
||||||
.redirectInput(ProcessBuilder.Redirect.PIPE)
|
|
||||||
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
|
||||||
.redirectError(ProcessBuilder.Redirect.PIPE)
|
|
||||||
.actualizeEnvVariables(environmentVariables, onlyEnrichExistedEnvVariables)
|
|
||||||
|
|
||||||
logOutput(
|
|
||||||
"""
|
|
||||||
Process: `$presentablePurpose`
|
|
||||||
Arguments: ${args.joinToString(separator = " ")}
|
|
||||||
Environment variables: [${processBuilder.environment().entries.joinToString { "${it.key}=${it.value}" }}]
|
|
||||||
""".trimIndent())
|
|
||||||
val process = processBuilder.start()
|
|
||||||
|
|
||||||
val processId = process.pid()
|
|
||||||
catchAll {
|
|
||||||
logOutput(" ... started external process `$presentablePurpose` with process ID = $processId")
|
|
||||||
onProcessCreated(process, processId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val inputThread = redirectProcessInput(process, stdInBytes)
|
|
||||||
val stdoutThread = redirectProcessOutput(process, true, stdoutRedirect)
|
|
||||||
val stderrThread = redirectProcessOutput(process, false, stderrRedirect)
|
|
||||||
val threads = listOfNotNull(inputThread, stdoutThread, stderrThread)
|
|
||||||
|
|
||||||
fun killProcess() {
|
|
||||||
catchAll { onBeforeKilled(process, processId) }
|
|
||||||
catchAll { process.descendants().forEach { catchAll { it.destroyForcibly() } } }
|
|
||||||
catchAll { process.destroy() }
|
|
||||||
catchAll { process.destroyForcibly() }
|
|
||||||
catchAll { threads.forEach { it.interrupt() } }
|
|
||||||
}
|
|
||||||
|
|
||||||
val stopper = Runnable {
|
|
||||||
logOutput(
|
|
||||||
" ... terminating process `$presentablePurpose` by request from external process (either SIGTERM or SIGKILL is caught) ...")
|
|
||||||
killProcess()
|
|
||||||
}
|
|
||||||
|
|
||||||
val stopperThread = Thread(stopper, "process-shutdown-hook")
|
|
||||||
try {
|
|
||||||
Runtime.getRuntime().addShutdownHook(stopperThread)
|
|
||||||
}
|
|
||||||
catch (e: IllegalStateException) {
|
|
||||||
logError("Process: $presentablePurpose. Shutdown hook cannot be added because: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!runCatching { process.waitFor(timeout.inWholeSeconds, TimeUnit.SECONDS) }.getOrDefault(false)) {
|
|
||||||
stopperThread.apply {
|
|
||||||
start()
|
|
||||||
join(20.seconds.inWholeMilliseconds)
|
|
||||||
}
|
|
||||||
throw ExecTimeoutException(args.joinToString(" "), timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
catchAll { Runtime.getRuntime().removeShutdownHook(stopperThread) }
|
|
||||||
}
|
|
||||||
|
|
||||||
threads.forEach { catchAll { it.join() } }
|
|
||||||
|
|
||||||
val code = process.exitValue()
|
|
||||||
if (code != 0) {
|
|
||||||
val linesLimit = 100
|
|
||||||
|
|
||||||
logOutput(" ... failed external process `$presentablePurpose` with exit code $code")
|
|
||||||
val message = buildString {
|
|
||||||
appendLine("External process `$presentablePurpose` failed with code $code")
|
|
||||||
for (diagnosticFile in errorDiagnosticFiles.filter { it.exists() && Files.size(it) > 0 }) {
|
|
||||||
appendLine(diagnosticFile.fileName.toString())
|
|
||||||
appendLine(diagnosticFile.readText().lines().joinToString(System.lineSeparator()) { " $it" })
|
|
||||||
}
|
|
||||||
|
|
||||||
stderrRedirect.read().lines().apply {
|
|
||||||
take(linesLimit).dropWhile { it.trim().isBlank() }.let { lines ->
|
|
||||||
if (lines.isNotEmpty()) {
|
|
||||||
appendLine(" FIRST $linesLimit lines of the standard error stream")
|
|
||||||
lines.forEach { appendLine(" $it") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > linesLimit) {
|
|
||||||
appendLine("...")
|
|
||||||
|
|
||||||
takeLast(linesLimit).dropWhile { it.trim().isBlank() }.let { lines ->
|
|
||||||
if (lines.isNotEmpty()) {
|
|
||||||
appendLine(" LAST $linesLimit lines of the standard error stream")
|
|
||||||
lines.forEach { appendLine(" $it") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stdoutRedirect.read().lines().takeLast(linesLimit).dropWhile { it.trim().isEmpty() }.let { lines ->
|
|
||||||
if (lines.isNotEmpty()) {
|
|
||||||
appendLine(" LAST $linesLimit lines of the standard output stream")
|
|
||||||
lines.forEach { appendLine(" $it") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error(message)
|
|
||||||
}
|
|
||||||
logOutput(" ... successfully finished external process for `$presentablePurpose` with exit code 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExecTimeoutException(private val processName: String,
|
|
||||||
private val timeout: Duration) : RuntimeException() {
|
|
||||||
override val message
|
|
||||||
get() = "Failed to wait for the process `$processName` to complete in $timeout"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun executeScript(fileNameToExecute: String, projectDirPath: Path) {
|
|
||||||
val stdout = ExecOutputRedirect.ToString()
|
|
||||||
val stderr = ExecOutputRedirect.ToString()
|
|
||||||
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "Executing of $fileNameToExecute",
|
|
||||||
workDir = projectDirPath,
|
|
||||||
timeout = 20.minutes,
|
|
||||||
args = listOf(fileNameToExecute),
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr
|
|
||||||
)
|
|
||||||
|
|
||||||
val commit = stdout.read().trim()
|
|
||||||
val error = stderr.read().trim()
|
|
||||||
|
|
||||||
logOutput("Stdout of command execution $commit")
|
|
||||||
logOutput("Stderr of command execution $error")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun execGradlew(pathToProject: Path, args: List<String>) {
|
|
||||||
val stdout = ExecOutputRedirect.ToString()
|
|
||||||
val stderr = ExecOutputRedirect.ToString()
|
|
||||||
|
|
||||||
val command = when (SystemInfo.isWindows) {
|
|
||||||
true -> (pathToProject / "gradlew.bat").toString()
|
|
||||||
false -> "./gradlew"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SystemInfo.isWindows) {
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "chmod gradlew",
|
|
||||||
workDir = pathToProject,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf("chmod", "+x", "gradlew"),
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr
|
|
||||||
)
|
|
||||||
}
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "Gradle Format",
|
|
||||||
workDir = pathToProject,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf(command) + args,
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.intellij.ide.starter
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
fun downloadGoSdk(version: String): Path {
|
|
||||||
val os = when {
|
|
||||||
SystemInfo.isWindows -> "windows"
|
|
||||||
SystemInfo.isLinux -> "linux"
|
|
||||||
SystemInfo.isMac -> "darwin"
|
|
||||||
else -> error("Unknown OS")
|
|
||||||
}
|
|
||||||
val extension = when {
|
|
||||||
SystemInfo.isWindows -> ".zip"
|
|
||||||
SystemInfo.isLinux || SystemInfo.isMac -> ".tar.gz"
|
|
||||||
else -> error("Unknown OS")
|
|
||||||
}
|
|
||||||
val url = "https://cache-redirector.jetbrains.com/dl.google.com/go/go$version.$os-amd64$extension"
|
|
||||||
val dirToDownload = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("go-sdk/$version")
|
|
||||||
val downloadedFile = dirToDownload.resolve("go$version.$os-amd64$extension")
|
|
||||||
val goRoot = dirToDownload.resolve("go-roots")
|
|
||||||
if (goRoot.toFile().exists()) {
|
|
||||||
return goRoot.resolve("go")
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpClient.download(url, downloadedFile)
|
|
||||||
FileSystem.unpack(downloadedFile, goRoot)
|
|
||||||
return goRoot.resolve("go")
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.downloadAndroidStudio
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class AndroidInstaller : IdeInstallator {
|
|
||||||
|
|
||||||
override fun install(ideInfo: IdeInfo): Pair<String, InstalledIde> {
|
|
||||||
|
|
||||||
val installDir: Path
|
|
||||||
val installerFile: File
|
|
||||||
|
|
||||||
downloadAndroidStudio().also {
|
|
||||||
installDir = it.first
|
|
||||||
installerFile = it.second
|
|
||||||
}
|
|
||||||
IdeArchiveExtractor.unpackIdeIfNeeded(installerFile, installDir.toFile())
|
|
||||||
val installationPath = when (!SystemInfo.isMac) {
|
|
||||||
true -> installDir.resolve("android-studio")
|
|
||||||
false -> installDir
|
|
||||||
}
|
|
||||||
val ide = IdeDistributionFactory.installIDE(installationPath.toFile(), ideInfo.executableFileName)
|
|
||||||
return Pair(ide.build, ide)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
// code has been borrowed from Intellij Community sources
|
|
||||||
// Some parts, that requires dependency on Community, were removed
|
|
||||||
public class BuildNumber implements Comparable<BuildNumber> {
|
|
||||||
private static final String STAR = "*";
|
|
||||||
private static final String SNAPSHOT = "SNAPSHOT";
|
|
||||||
private static final String FALLBACK_VERSION = "999.SNAPSHOT";
|
|
||||||
|
|
||||||
public static final int SNAPSHOT_VALUE = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
private final String myProductCode;
|
|
||||||
private final int[] myComponents;
|
|
||||||
|
|
||||||
public BuildNumber(String productCode, int baselineVersion, int buildNumber) {
|
|
||||||
this(productCode, new int[]{baselineVersion, buildNumber});
|
|
||||||
}
|
|
||||||
|
|
||||||
public BuildNumber(String productCode, int... components) {
|
|
||||||
myProductCode = productCode;
|
|
||||||
myComponents = components;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPlaceholder(String value) {
|
|
||||||
return "__BUILD_NUMBER__".equals(value) || "__BUILD__".equals(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProductCode() {
|
|
||||||
return myProductCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBaselineVersion() {
|
|
||||||
return myComponents[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getComponents() {
|
|
||||||
return myComponents.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSnapshot() {
|
|
||||||
for (int value : myComponents) {
|
|
||||||
if (value == SNAPSHOT_VALUE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BuildNumber withoutProductCode() {
|
|
||||||
return myProductCode.isEmpty() ? this : new BuildNumber("", myComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String asString() {
|
|
||||||
return asString(true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String asStringWithoutProductCode() {
|
|
||||||
return asString(false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String asStringWithoutProductCodeAndSnapshot() {
|
|
||||||
return asString(false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String asString(boolean includeProductCode, boolean withSnapshotMarker) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
if (includeProductCode && !myProductCode.isEmpty()) {
|
|
||||||
builder.append(myProductCode).append('-');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int each : myComponents) {
|
|
||||||
if (each != SNAPSHOT_VALUE) {
|
|
||||||
builder.append(each);
|
|
||||||
}
|
|
||||||
else if (withSnapshotMarker) {
|
|
||||||
builder.append(SNAPSHOT);
|
|
||||||
}
|
|
||||||
builder.append('.');
|
|
||||||
}
|
|
||||||
if (builder.charAt(builder.length() - 1) == '.') {
|
|
||||||
builder.setLength(builder.length() - 1);
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BuildNumber fromPluginsCompatibleBuild() {
|
|
||||||
return fromString(getPluginsCompatibleBuild());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to parse build number from the specified string.
|
|
||||||
* Returns {@code null} if the string is not a valid build number.
|
|
||||||
*/
|
|
||||||
public static BuildNumber fromStringOrNull(String version) {
|
|
||||||
try {
|
|
||||||
return fromString(version);
|
|
||||||
}
|
|
||||||
catch (RuntimeException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BuildNumber fromString(String version) {
|
|
||||||
if (version == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
version = version.trim();
|
|
||||||
return fromString(version, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BuildNumber fromStringWithProductCode(String version, String productCode) {
|
|
||||||
return fromString(version, null, productCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BuildNumber fromString(String version, String pluginName, String productCodeIfAbsentInVersion) {
|
|
||||||
if (version.isEmpty()) return null;
|
|
||||||
String code = version;
|
|
||||||
int productSeparator = code.indexOf('-');
|
|
||||||
String productCode;
|
|
||||||
if (productSeparator > 0) {
|
|
||||||
productCode = code.substring(0, productSeparator);
|
|
||||||
code = code.substring(productSeparator + 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
productCode = productCodeIfAbsentInVersion != null ? productCodeIfAbsentInVersion : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
int baselineVersionSeparator = code.indexOf('.');
|
|
||||||
|
|
||||||
if (baselineVersionSeparator > 0) {
|
|
||||||
String baselineVersionString = code.substring(0, baselineVersionSeparator);
|
|
||||||
if (baselineVersionString.trim().isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] stringComponents = code.split("\\.");
|
|
||||||
int[] intComponentsList = new int[stringComponents.length];
|
|
||||||
for (int i = 0, n = stringComponents.length; i < n; i++) {
|
|
||||||
String stringComponent = stringComponents[i];
|
|
||||||
int comp = parseBuildNumber(version, stringComponent, pluginName);
|
|
||||||
intComponentsList[i] = comp;
|
|
||||||
if (comp == SNAPSHOT_VALUE && (i + 1) != n) {
|
|
||||||
intComponentsList = Arrays.copyOf(intComponentsList, i + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new BuildNumber(productCode, intComponentsList);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int buildNumber = parseBuildNumber(version, code, pluginName);
|
|
||||||
if (buildNumber <= 2000) {
|
|
||||||
// it's probably a baseline, not a build number
|
|
||||||
return new BuildNumber(productCode, buildNumber, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int baselineVersion = getBaseLineForHistoricBuilds(buildNumber);
|
|
||||||
return new BuildNumber(productCode, baselineVersion, buildNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int parseBuildNumber(String version, String code, String pluginName) {
|
|
||||||
if (SNAPSHOT.equals(code) || isPlaceholder(code) || STAR.equals(code)) {
|
|
||||||
return SNAPSHOT_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(code);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + pluginName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(BuildNumber o) {
|
|
||||||
int[] c1 = myComponents;
|
|
||||||
int[] c2 = o.myComponents;
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(c1.length, c2.length); i++) {
|
|
||||||
if (c1[i] == c2[i] && c1[i] == SNAPSHOT_VALUE) return 0;
|
|
||||||
if (c1[i] == SNAPSHOT_VALUE) return 1;
|
|
||||||
if (c2[i] == SNAPSHOT_VALUE) return -1;
|
|
||||||
int result = c1[i] - c2[i];
|
|
||||||
if (result != 0) return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c1.length - c2.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
BuildNumber that = (BuildNumber)o;
|
|
||||||
|
|
||||||
if (!myProductCode.equals(that.myProductCode)) return false;
|
|
||||||
return Arrays.equals(myComponents, that.myComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = myProductCode.hashCode();
|
|
||||||
result = 31 * result + Arrays.hashCode(myComponents);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html
|
|
||||||
private static int getBaseLineForHistoricBuilds(int bn) {
|
|
||||||
if (bn >= 10000) return 88; // Maia, 9x builds
|
|
||||||
if (bn >= 9500) return 85; // 8.1 builds
|
|
||||||
if (bn >= 9100) return 81; // 8.0.x builds
|
|
||||||
if (bn >= 8000) return 80; // 8.0, including pre-release builds
|
|
||||||
if (bn >= 7500) return 75; // 7.0.2+
|
|
||||||
if (bn >= 7200) return 72; // 7.0 final
|
|
||||||
if (bn >= 6900) return 69; // 7.0 pre-M2
|
|
||||||
if (bn >= 6500) return 65; // 7.0 pre-M1
|
|
||||||
if (bn >= 6000) return 60; // 6.0.2+
|
|
||||||
if (bn >= 5000) return 55; // 6.0 branch, including all 6.0 EAP builds
|
|
||||||
if (bn >= 4000) return 50; // 5.1 branch
|
|
||||||
return 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getPluginsCompatibleBuild() {
|
|
||||||
return System.getProperty("idea.plugins.compatible.build");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.VMOptionsDiff
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
interface IDEStartConfig {
|
|
||||||
val workDir: Path
|
|
||||||
|
|
||||||
val environmentVariables: Map<String, String>
|
|
||||||
get() = System.getenv().filterKeys {
|
|
||||||
// don't inherit these environment variables from parent process
|
|
||||||
it != "IDEA_PROPERTIES" && !it.endsWith("VM_OPTIONS") && it != "JAVA_HOME" && !it.endsWith("_JDK")
|
|
||||||
}
|
|
||||||
|
|
||||||
val commandLine: List<String>
|
|
||||||
|
|
||||||
fun vmOptionsDiff(): VMOptionsDiff? = null
|
|
||||||
|
|
||||||
val errorDiagnosticFiles: List<Path>
|
|
||||||
get() = emptyList()
|
|
||||||
}
|
|
||||||
@@ -1,548 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.build.tool.BuildToolProvider
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.ide.command.CommandChain
|
|
||||||
import com.intellij.ide.starter.ide.command.MarshallableCommand
|
|
||||||
import com.intellij.ide.starter.models.*
|
|
||||||
import com.intellij.ide.starter.path.IDEDataPaths
|
|
||||||
import com.intellij.ide.starter.plugins.PluginConfigurator
|
|
||||||
import com.intellij.ide.starter.profiler.ProfilerType
|
|
||||||
import com.intellij.ide.starter.report.publisher.ReportPublisher
|
|
||||||
import com.intellij.ide.starter.runner.IDECommandLine
|
|
||||||
import com.intellij.ide.starter.runner.IDERunContext
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.factory
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import org.kodein.di.newInstance
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import org.w3c.dom.NodeList
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.nio.file.Path
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import javax.xml.transform.TransformerFactory
|
|
||||||
import javax.xml.transform.dom.DOMSource
|
|
||||||
import javax.xml.transform.stream.StreamResult
|
|
||||||
import kotlin.io.path.*
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
data class IDETestContext(
|
|
||||||
val paths: IDEDataPaths,
|
|
||||||
val ide: InstalledIde,
|
|
||||||
val testCase: TestCase,
|
|
||||||
val testName: String,
|
|
||||||
private val _resolvedProjectHome: Path?,
|
|
||||||
var patchVMOptions: VMOptions.() -> VMOptions,
|
|
||||||
val ciServer: CIServer,
|
|
||||||
var profilerType: ProfilerType = ProfilerType.NONE,
|
|
||||||
val publishers: List<ReportPublisher> = di.direct.instance(),
|
|
||||||
var isReportPublishingEnabled: Boolean = false
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
const val TEST_RESULT_FILE_PATH_PROPERTY = "test.result.file.path"
|
|
||||||
const val OPENTELEMETRY_FILE = "opentelemetry.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
val resolvedProjectHome: Path
|
|
||||||
get() = checkNotNull(_resolvedProjectHome) { "Project is not found for the test $testName" }
|
|
||||||
|
|
||||||
val pluginConfigurator: PluginConfigurator by di.newInstance { factory<IDETestContext, PluginConfigurator>().invoke(this@IDETestContext) }
|
|
||||||
|
|
||||||
val buildTools: BuildToolProvider by di.newInstance { factory<IDETestContext, BuildToolProvider>().invoke(this@IDETestContext) }
|
|
||||||
|
|
||||||
fun addVMOptionsPatch(patchVMOptions: VMOptions.() -> VMOptions): IDETestContext {
|
|
||||||
this.patchVMOptions = this.patchVMOptions.andThen(patchVMOptions)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addLockFileForUITest(fileName: String): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("uiLockTempFile", paths.tempDir / fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableJcef(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
// Disable JCEF (IDEA-243147). Otherwise, tests will fail with LOG.error
|
|
||||||
addSystemProperty("ide.browser.jcef.enabled", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableLinuxNativeMenuForce(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("linux.native.menu.force.disable", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMemorySize(sizeMb: Int): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
this
|
|
||||||
.withXmx(sizeMb)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableGitLogIndexing(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("vcs.log.index.git", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun executeAfterProjectOpening(executeAfterProjectOpening: Boolean = true) = addVMOptionsPatch {
|
|
||||||
addSystemProperty("performance.execute.script.after.project.opened", executeAfterProjectOpening)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun executeDuringIndexing(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("performance.execute.script.after.scanning", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withGtk2OnLinux(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
this
|
|
||||||
.addSystemProperty("jdk.gtk.verbose", true)
|
|
||||||
.let {
|
|
||||||
// Desperate attempt to fix JBR-2783
|
|
||||||
if (SystemInfo.isLinux) {
|
|
||||||
it.addSystemProperty("jdk.gtk.version", 2)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableInstantIdeShutdown(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("ide.instant.shutdown", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableSlowOperationsInEdtInTests(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("ide.slow.operations.assertion", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does not allow IDE to fork a process that sends FUS statistics on IDE shutdown.
|
|
||||||
* On Windows that forked process may prevent some files from removing.
|
|
||||||
* See [com.intellij.internal.statistic.EventLogApplicationLifecycleListener]
|
|
||||||
*/
|
|
||||||
fun disableFusSendingOnIdeClose(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("feature.usage.event.log.send.on.ide.close", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withVerboseIndexingDiagnostics(dumpPaths: Boolean = false): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
this
|
|
||||||
.addSystemProperty("intellij.indexes.diagnostics.should.dump.for.interrupted.index.updaters", true)
|
|
||||||
.addSystemProperty("intellij.indexes.diagnostics.limit.of.files", 10000)
|
|
||||||
.addSystemProperty("intellij.indexes.diagnostics.should.dump.paths.of.indexed.files", dumpPaths)
|
|
||||||
// Dumping of lists of indexed file paths may require a lot of memory.
|
|
||||||
.withXmx(4 * 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPathForMemorySnapshot(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("memory.snapshots.path", paths.logsDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPathForSnapshots(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("snapshots.path", paths.snapshotsDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectMemorySnapshotOnFailedPluginUnload(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("ide.plugins.snapshot.on.unload.fail", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPerProjectDynamicPluginsFlag(): IDETestContext =
|
|
||||||
addVMOptionsPatch {
|
|
||||||
addSystemProperty("ide.plugins.per.project", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableAutoImport(disabled: Boolean = true) = addVMOptionsPatch {
|
|
||||||
addSystemProperty("external.system.auto.import.disabled", disabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableOrdinaryIndexes() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.use.only.index.infrastructure.extension", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSharedIndexesDownload(enable: Boolean = true) = addVMOptionsPatch {
|
|
||||||
addSystemProperty("shared.indexes.bundled", enable)
|
|
||||||
.addSystemProperty("shared.indexes.download", enable)
|
|
||||||
.addSystemProperty("shared.indexes.download.auto.consent", enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun skipIndicesInitialization() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.skip.indices.initialization", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addTestResultFilePath() = addVMOptionsPatch {
|
|
||||||
addSystemProperty(TEST_RESULT_FILE_PATH_PROPERTY, paths.tempDir.resolve("testResult.txt"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectImportProjectPerfMetrics() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.collect.project.import.performance", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectOpenTelemetry() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.diagnostic.opentelemetry.file", paths.logsDir.resolve(OPENTELEMETRY_FILE))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableWorkspaceModelVerboseLogs() = addVMOptionsPatch {
|
|
||||||
configureLoggers(traceLoggers = listOf("com.intellij.workspaceModel"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wipeSystemDir() = apply {
|
|
||||||
//TODO: it would be better to allocate a new context instead of wiping the folder
|
|
||||||
logOutput("Cleaning system dir for $this at $paths")
|
|
||||||
paths.systemDir.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wipeLogsDir() = apply {
|
|
||||||
//TODO: it would be better to allocate a new context instead of wiping the folder
|
|
||||||
logOutput("Cleaning logs dir for $this at $paths")
|
|
||||||
paths.logsDir.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wipeProjectsDir() = apply {
|
|
||||||
val path = paths.systemDir / "projects"
|
|
||||||
logOutput("Cleaning project cache dir for $this at $path")
|
|
||||||
path.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wipeEventLogDataDir() = apply {
|
|
||||||
val path = paths.systemDir / "event-log-data"
|
|
||||||
logOutput("Cleaning event-log-data dir for $this at $path")
|
|
||||||
path.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wipeSnapshotDir() = apply {
|
|
||||||
val path = paths.snapshotsDir
|
|
||||||
logOutput("Cleaning snapshot dir for $this at $path")
|
|
||||||
path.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runContext(
|
|
||||||
patchVMOptions: VMOptions.() -> VMOptions = { this },
|
|
||||||
commandLine: IDECommandLine? = null,
|
|
||||||
commands: Iterable<MarshallableCommand> = CommandChain(),
|
|
||||||
codeBuilder: (CodeInjector.() -> Unit)? = null,
|
|
||||||
runTimeout: Duration = 10.minutes,
|
|
||||||
useStartupScript: Boolean = true,
|
|
||||||
launchName: String = "",
|
|
||||||
expectedKill: Boolean = false,
|
|
||||||
collectNativeThreads: Boolean = false
|
|
||||||
): IDERunContext {
|
|
||||||
return IDERunContext(testContext = this)
|
|
||||||
.copy(
|
|
||||||
commandLine = commandLine,
|
|
||||||
commands = commands,
|
|
||||||
codeBuilder = codeBuilder,
|
|
||||||
runTimeout = runTimeout,
|
|
||||||
useStartupScript = useStartupScript,
|
|
||||||
launchName = launchName,
|
|
||||||
expectedKill = expectedKill,
|
|
||||||
collectNativeThreads = collectNativeThreads
|
|
||||||
)
|
|
||||||
.addVMOptionsPatch(patchVMOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup profiler injection
|
|
||||||
*/
|
|
||||||
fun setProfiler(profilerType: ProfilerType): IDETestContext {
|
|
||||||
this.profilerType = profilerType
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun internalMode() = addVMOptionsPatch { addSystemProperty("idea.is.internal", true) }
|
|
||||||
|
|
||||||
fun prepareProjectCleanImport(): IDETestContext {
|
|
||||||
return removeIdeaProjectDirectory().removeAllImlFilesInProject()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableAutoSetupJavaProject() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.java.project.setup.disabled", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disablePackageSearchBuildFiles() = addVMOptionsPatch {
|
|
||||||
addSystemProperty("idea.pkgs.disableLoading", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeIdeaProjectDirectory(): IDETestContext {
|
|
||||||
val ideaDirPath = resolvedProjectHome.resolve(".idea")
|
|
||||||
|
|
||||||
logOutput("Removing $ideaDirPath ...")
|
|
||||||
|
|
||||||
if (ideaDirPath.notExists()) {
|
|
||||||
logOutput("Idea project directory $ideaDirPath doesn't exist. So, it will not be deleted")
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
ideaDirPath.toFile().deleteRecursively()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAllImlFilesInProject(): IDETestContext {
|
|
||||||
val projectDir = resolvedProjectHome
|
|
||||||
|
|
||||||
logOutput("Removing all .iml files in $projectDir ...")
|
|
||||||
|
|
||||||
projectDir.toFile().walkTopDown()
|
|
||||||
.forEach {
|
|
||||||
if (it.isFile && it.extension == "iml") {
|
|
||||||
it.delete()
|
|
||||||
logOutput("File ${it.path} is deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runIDE(
|
|
||||||
patchVMOptions: VMOptions.() -> VMOptions = { this },
|
|
||||||
commandLine: IDECommandLine? = null,
|
|
||||||
commands: Iterable<MarshallableCommand>,
|
|
||||||
codeBuilder: (CodeInjector.() -> Unit)? = null,
|
|
||||||
runTimeout: Duration = 10.minutes,
|
|
||||||
useStartupScript: Boolean = true,
|
|
||||||
launchName: String = "",
|
|
||||||
expectedKill: Boolean = false,
|
|
||||||
collectNativeThreads: Boolean = false
|
|
||||||
): IDEStartResult {
|
|
||||||
|
|
||||||
val ideRunResult = runContext(
|
|
||||||
commandLine = commandLine,
|
|
||||||
commands = commands,
|
|
||||||
codeBuilder = codeBuilder,
|
|
||||||
runTimeout = runTimeout,
|
|
||||||
useStartupScript = useStartupScript,
|
|
||||||
launchName = launchName,
|
|
||||||
expectedKill = expectedKill,
|
|
||||||
collectNativeThreads = collectNativeThreads,
|
|
||||||
patchVMOptions = patchVMOptions
|
|
||||||
).runIDE()
|
|
||||||
|
|
||||||
if (isReportPublishingEnabled) publishers.forEach {
|
|
||||||
it.publish(ideRunResult)
|
|
||||||
}
|
|
||||||
if (ideRunResult.failureError != null) throw ideRunResult.failureError
|
|
||||||
return ideRunResult
|
|
||||||
}
|
|
||||||
|
|
||||||
fun warmUp(
|
|
||||||
patchVMOptions: VMOptions.() -> VMOptions = { this },
|
|
||||||
commands: Iterable<MarshallableCommand>,
|
|
||||||
runTimeout: Duration = 10.minutes,
|
|
||||||
storeClassReport: Boolean = false
|
|
||||||
): IDEStartResult {
|
|
||||||
|
|
||||||
return runIDE(
|
|
||||||
patchVMOptions = {
|
|
||||||
val warmupReports = IDEStartupReports(paths.reportsDir / "warmUp")
|
|
||||||
if (storeClassReport) {
|
|
||||||
this.enableStartupPerformanceLog(warmupReports).enableClassLoadingReport(
|
|
||||||
paths.reportsDir / "warmUp" / "class-report.txt").patchVMOptions()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
commands = testCase.commands.plus(commands),
|
|
||||||
runTimeout = runTimeout,
|
|
||||||
launchName = "warmUp"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAndUnpackProject(): IDETestContext {
|
|
||||||
testCase.markNotReusable().projectInfo?.downloadAndUnpackProject()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setProviderMemoryOnlyOnLinux(): IDETestContext {
|
|
||||||
if (SystemInfo.isLinux) {
|
|
||||||
val optionsConfig = paths.configDir.resolve("options")
|
|
||||||
optionsConfig.toFile().mkdirs()
|
|
||||||
val securityXml = optionsConfig.resolve("security.xml")
|
|
||||||
securityXml.toFile().createNewFile()
|
|
||||||
securityXml.toFile().writeText("""<application>
|
|
||||||
<component name="PasswordSafe">
|
|
||||||
<option name="PROVIDER" value="MEMORY_ONLY" />
|
|
||||||
</component>
|
|
||||||
</application>""")
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addBuildProcessProfiling(): IDETestContext {
|
|
||||||
if (_resolvedProjectHome != null) {
|
|
||||||
val ideaDir = resolvedProjectHome.resolve(".idea")
|
|
||||||
val workspace = ideaDir.resolve("workspace.xml")
|
|
||||||
|
|
||||||
if (workspace.toFile().exists()) {
|
|
||||||
val newContent = StringBuilder()
|
|
||||||
val readText = workspace.toFile().readText()
|
|
||||||
val userLocalBuildProcessVmOptions = when {
|
|
||||||
(testName.contains(
|
|
||||||
"intellij_sources")) -> "-Dprofiling.mode=true -Dgroovyc.in.process=true -Dgroovyc.asm.resolving.only=false"
|
|
||||||
else -> "-Dprofiling.mode=true"
|
|
||||||
}
|
|
||||||
if (readText.contains("CompilerWorkspaceConfiguration")) {
|
|
||||||
workspace.toFile().readLines().forEach {
|
|
||||||
if (it.contains("<component name=\"CompilerWorkspaceConfiguration\">")) {
|
|
||||||
val newLine = "<component name=\"CompilerWorkspaceConfiguration\">\n<option name=\"COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS\" value=\"$userLocalBuildProcessVmOptions\" />"
|
|
||||||
newContent.appendLine(newLine)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newContent.appendLine(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workspace.writeText(newContent.toString())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val xmlDoc = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().parse(workspace.toFile())
|
|
||||||
|
|
||||||
xmlDoc.documentElement.normalize()
|
|
||||||
|
|
||||||
val firstElement = xmlDoc.firstChild
|
|
||||||
val componentElement = xmlDoc.createElement("component")
|
|
||||||
componentElement.setAttribute("name", "CompilerWorkspaceConfiguration")
|
|
||||||
val optionElement = xmlDoc.createElement("option")
|
|
||||||
optionElement.setAttribute("name", "COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS")
|
|
||||||
optionElement.setAttribute("value", userLocalBuildProcessVmOptions)
|
|
||||||
firstElement.appendChild(componentElement).appendChild(optionElement)
|
|
||||||
val source = DOMSource(xmlDoc)
|
|
||||||
val outputStream = FileOutputStream(workspace.toFile())
|
|
||||||
val result = StreamResult(outputStream)
|
|
||||||
val transformerFactory = TransformerFactory.newInstance()
|
|
||||||
val transformer = transformerFactory.newTransformer()
|
|
||||||
transformer.transform(source, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkThatBuildRunByIdea(): IDETestContext {
|
|
||||||
if (_resolvedProjectHome != null) {
|
|
||||||
val ideaDir = resolvedProjectHome.resolve(".idea")
|
|
||||||
val gradle = ideaDir.resolve("gradle.xml")
|
|
||||||
if (gradle.toFile().exists()) {
|
|
||||||
val readText = gradle.toFile().readText()
|
|
||||||
if (!readText.contains("<option name=\"delegatedBuild\" value=\"false\"/>")) {
|
|
||||||
val xmlDoc = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().parse(gradle.toFile())
|
|
||||||
|
|
||||||
xmlDoc.documentElement.normalize()
|
|
||||||
|
|
||||||
val gradleProjectSettingsElements: NodeList = xmlDoc.getElementsByTagName("GradleProjectSettings")
|
|
||||||
if (gradleProjectSettingsElements.length == 1) {
|
|
||||||
|
|
||||||
for (i in 0 until gradleProjectSettingsElements.length) {
|
|
||||||
val component: Node = gradleProjectSettingsElements.item(i)
|
|
||||||
|
|
||||||
if (component.nodeType == Node.ELEMENT_NODE) {
|
|
||||||
val optionElement = xmlDoc.createElement("option")
|
|
||||||
optionElement.setAttribute("name", "delegatedBuild")
|
|
||||||
optionElement.setAttribute("value", "false")
|
|
||||||
component.appendChild(optionElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val source = DOMSource(xmlDoc)
|
|
||||||
val outputStream = FileOutputStream(gradle.toFile())
|
|
||||||
val result = StreamResult(outputStream)
|
|
||||||
val transformerFactory = TransformerFactory.newInstance()
|
|
||||||
val transformer = transformerFactory.newTransformer()
|
|
||||||
transformer.transform(source, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setBuildProcessHeapSize(heapSizeValue: String): IDETestContext {
|
|
||||||
if (_resolvedProjectHome != null) {
|
|
||||||
val heapSize = when (heapSizeValue.isEmpty()) {
|
|
||||||
true -> "2000"
|
|
||||||
else -> heapSizeValue
|
|
||||||
}
|
|
||||||
val ideaDir = resolvedProjectHome.resolve(".idea")
|
|
||||||
val compilerXml = ideaDir.resolve("compiler.xml")
|
|
||||||
if (compilerXml.toFile().exists()) {
|
|
||||||
val newContent = StringBuilder()
|
|
||||||
val readText = compilerXml.toFile().readText()
|
|
||||||
if (!readText.contains("BUILD_PROCESS_HEAP_SIZE")) {
|
|
||||||
compilerXml.toFile().readLines().forEach {
|
|
||||||
if (it.contains("<component name=\"CompilerConfiguration\">")) {
|
|
||||||
val newLine = "<component name=\"CompilerConfiguration\">\n<option name=\"BUILD_PROCESS_HEAP_SIZE\" value=\"$heapSize\" />"
|
|
||||||
newContent.appendLine(newLine)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newContent.appendLine(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compilerXml.writeText(newContent.toString())
|
|
||||||
}
|
|
||||||
else if (heapSizeValue.isNotEmpty()) {
|
|
||||||
compilerXml.toFile().readLines().forEach {
|
|
||||||
if (it.contains("BUILD_PROCESS_HEAP_SIZE")) {
|
|
||||||
val newLine = it.replace("value=\"\\d*\"".toRegex(), "value=\"$heapSize\"")
|
|
||||||
newContent.appendLine(newLine)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newContent.appendLine(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compilerXml.writeText(newContent.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateGeneralSettings(): IDETestContext {
|
|
||||||
val patchedIdeGeneralXml = this::class.java.classLoader.getResourceAsStream("ide.general.xml")
|
|
||||||
val pathToGeneralXml = paths.configDir.toAbsolutePath().resolve("options/ide.general.xml")
|
|
||||||
|
|
||||||
if (!pathToGeneralXml.exists()) {
|
|
||||||
pathToGeneralXml.parent.createDirectories()
|
|
||||||
patchedIdeGeneralXml.use {
|
|
||||||
if (it != null) {
|
|
||||||
pathToGeneralXml.writeBytes(it.readAllBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun setLicense(pathToFileWithLicense: Path): IDETestContext {
|
|
||||||
val licenseKeyFileName: String = when(this.ide.productCode) {
|
|
||||||
"IU" -> "idea.key"
|
|
||||||
"RM" -> "rubymine.key"
|
|
||||||
"WS" -> "webstorm.key"
|
|
||||||
"PS" -> "phpstorm.key"
|
|
||||||
"GO" -> "goland.key"
|
|
||||||
"PY" -> "pycharm.key"
|
|
||||||
"DG" -> "datagrip.key"
|
|
||||||
else -> error("Setting license to the product ${this.ide.productCode} is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
val keyFile = paths.configDir.resolve(licenseKeyFileName)
|
|
||||||
keyFile.toFile().createNewFile()
|
|
||||||
keyFile.toFile().writeText(pathToFileWithLicense.toFile().readText())
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun publishArtifact(source: Path,
|
|
||||||
artifactPath: String = testName,
|
|
||||||
artifactName: String = source.fileName.toString()) = ciServer.publishArtifact(source, artifactPath, artifactName)
|
|
||||||
|
|
||||||
fun withReportPublishing(isEnabled: Boolean): IDETestContext {
|
|
||||||
isReportPublishingEnabled = isEnabled
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect
|
|
||||||
import com.intellij.ide.starter.exec.exec
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.catchAll
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.nameWithoutExtension
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
object IdeArchiveExtractor {
|
|
||||||
|
|
||||||
fun unpackIdeIfNeeded(ideInstallerFile: File, unpackDir: File) {
|
|
||||||
if (unpackDir.isDirectory && unpackDir.listFiles()?.isNotEmpty() == true) {
|
|
||||||
logOutput("Build directory $unpackDir already exists for the binary $ideInstallerFile")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logOutput("Extracting application into $unpackDir")
|
|
||||||
when {
|
|
||||||
ideInstallerFile.extension == "dmg" -> unpackDmg(ideInstallerFile, unpackDir.toPath())
|
|
||||||
ideInstallerFile.extension == "exe" -> unpackWin(ideInstallerFile, unpackDir)
|
|
||||||
ideInstallerFile.extension == "zip" -> FileSystem.unpack(ideInstallerFile.toPath(), unpackDir.toPath())
|
|
||||||
ideInstallerFile.name.endsWith(".tar.gz") -> FileSystem.unpackTarGz(ideInstallerFile, unpackDir)
|
|
||||||
else -> error("Unsupported build file: $ideInstallerFile")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unpackDmg(dmgFile: File, target: Path): Path {
|
|
||||||
target.toFile().deleteRecursively()
|
|
||||||
target.createDirectories()
|
|
||||||
|
|
||||||
val mountDir = File(dmgFile.path + "-mount${System.currentTimeMillis()}")
|
|
||||||
try {
|
|
||||||
exec(presentablePurpose = "hdiutil",
|
|
||||||
workDir = target,
|
|
||||||
timeout = 10.minutes,
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("hdiutil"),
|
|
||||||
stdoutRedirect = ExecOutputRedirect.ToStdOut("hdiutil"),
|
|
||||||
args = listOf("hdiutil", "attach", "-readonly", "-noautoopen", "-noautofsck", "-nobrowse", "-mountpoint", "$mountDir",
|
|
||||||
"$dmgFile"))
|
|
||||||
}
|
|
||||||
catch (t: Throwable) {
|
|
||||||
dmgFile.delete()
|
|
||||||
throw Error("Failed to mount $dmgFile. ${t.message}.", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val appDir = mountDir.listFiles()?.singleOrNull { it.name.endsWith(".app") }
|
|
||||||
?: error("Failed to find the only one .app folder in $dmgFile")
|
|
||||||
|
|
||||||
val targetAppDir = target / appDir.name
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "copy-dmg",
|
|
||||||
workDir = target,
|
|
||||||
timeout = 10.minutes,
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("cp"),
|
|
||||||
args = listOf("cp", "-R", "$appDir", "$targetAppDir"))
|
|
||||||
|
|
||||||
return targetAppDir
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
catchAll {
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "hdiutil",
|
|
||||||
workDir = target,
|
|
||||||
timeout = 10.minutes,
|
|
||||||
stdoutRedirect = ExecOutputRedirect.ToStdOut("hdiutil"),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("hdiutil"),
|
|
||||||
args = listOf("hdiutil", "detach", "-force", "$mountDir"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unpackWin(exeFile: File, targetDir: File) {
|
|
||||||
targetDir.deleteRecursively()
|
|
||||||
|
|
||||||
//we use 7-Zip to unpack NSIS binaries, same way as in Toolbox App
|
|
||||||
val sevenZipUrl = "https://repo.labs.intellij.net/thirdparty/7z-cmdline-15.06.zip"
|
|
||||||
val sevenZipCacheDir = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("7zip")
|
|
||||||
|
|
||||||
val sevenZipFile = sevenZipCacheDir / sevenZipUrl.split("/").last()
|
|
||||||
val sevenZipTool = sevenZipCacheDir / sevenZipFile.fileName.nameWithoutExtension
|
|
||||||
|
|
||||||
HttpClient.downloadIfMissing(sevenZipUrl, sevenZipFile)
|
|
||||||
FileSystem.unpackIfMissing(sevenZipFile, sevenZipTool)
|
|
||||||
|
|
||||||
val severZipToolExe = sevenZipTool.resolve("7z.exe")
|
|
||||||
|
|
||||||
targetDir.mkdirs()
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "unpack-zip",
|
|
||||||
workDir = targetDir.toPath(),
|
|
||||||
timeout = 10.minutes,
|
|
||||||
args = listOf(severZipToolExe.toAbsolutePath().toString(), "x", "-y", "-o$targetDir", exeFile.path)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.logError
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
abstract class IdeDistribution {
|
|
||||||
abstract fun installIde(unpackDir: Path, executableFileName: String): InstalledIde
|
|
||||||
|
|
||||||
private fun downloadJbr(jbrFileName: String): Pair<Boolean, Path> {
|
|
||||||
val downloadUrl = "https://cache-redirector.jetbrains.com/intellij-jbr/$jbrFileName"
|
|
||||||
|
|
||||||
val jbrCacheDirectory = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("jbr")
|
|
||||||
val localFile = jbrCacheDirectory / jbrFileName
|
|
||||||
val localDir = jbrCacheDirectory / jbrFileName.removeSuffix(".tar.gz")
|
|
||||||
|
|
||||||
val isSuccess = HttpClient.downloadIfMissing(downloadUrl, localFile, retries = 1)
|
|
||||||
if (!isSuccess) return Pair(isSuccess, localDir)
|
|
||||||
|
|
||||||
FileSystem.unpackIfMissing(localFile, localDir)
|
|
||||||
|
|
||||||
return Pair(isSuccess, localDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun downloadAndUnpackJbrIfNeeded(jbrFullVersion: String): Path {
|
|
||||||
val jbrVersion = jbrFullVersion.split(".")[0].toInt()
|
|
||||||
var majorVersion = jbrFullVersion.split("+").firstOrNull()
|
|
||||||
if (jbrVersion < 17) {
|
|
||||||
majorVersion = majorVersion?.replace(".", "_")
|
|
||||||
}
|
|
||||||
requireNotNull(majorVersion) {
|
|
||||||
{ "majorVersion is: $majorVersion" }
|
|
||||||
}
|
|
||||||
val buildNumber = jbrFullVersion.split("-b").drop(1).singleOrNull()
|
|
||||||
requireNotNull(buildNumber) {
|
|
||||||
{ "buildNumber is: $buildNumber" }
|
|
||||||
}
|
|
||||||
logOutput("Detected JBR version $jbrFullVersion with parts: $majorVersion and build $buildNumber")
|
|
||||||
|
|
||||||
val os = when {
|
|
||||||
SystemInfo.isWindows -> "windows"
|
|
||||||
SystemInfo.isLinux -> "linux"
|
|
||||||
SystemInfo.isMac -> "osx"
|
|
||||||
else -> error("Unknown OS")
|
|
||||||
}
|
|
||||||
|
|
||||||
val arch = when (SystemInfo.isMac) {
|
|
||||||
true -> when (System.getProperty("os.arch")) {
|
|
||||||
"x86_64" -> "x64"
|
|
||||||
"aarch64" -> "aarch64"
|
|
||||||
else -> error("Unsupported architecture of Mac OS")
|
|
||||||
}
|
|
||||||
false -> "x64"
|
|
||||||
}
|
|
||||||
|
|
||||||
var jbrFileName = "jbrsdk-$majorVersion-$os-$arch-b$buildNumber.tar.gz"
|
|
||||||
var downloadResult = downloadJbr(jbrFileName)
|
|
||||||
|
|
||||||
if (!downloadResult.first) {
|
|
||||||
logError("Couldn't download '$jbrFileName'. Trying download jbrsdk_nomod")
|
|
||||||
downloadResult = downloadJbr("jbrsdk_nomod-$majorVersion-$os-$arch-b$buildNumber.tar.gz")
|
|
||||||
}
|
|
||||||
|
|
||||||
val appHome = (downloadResult.second.toFile().listFiles() ?: arrayOf()).singleOrNull { it.isDirectory }?.toPath()
|
|
||||||
requireNotNull(appHome) {
|
|
||||||
"appHome is null: $appHome"
|
|
||||||
}
|
|
||||||
when {
|
|
||||||
SystemInfo.isMac -> return appHome / "Contents" / "Home"
|
|
||||||
}
|
|
||||||
return appHome
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object IdeDistributionFactory {
|
|
||||||
fun installIDE(unpackDir: File, executableFileName: String): InstalledIde {
|
|
||||||
val distribution = when {
|
|
||||||
SystemInfo.isMac -> MacOsIdeDistribution()
|
|
||||||
SystemInfo.isWindows -> WindowsIdeDistribution()
|
|
||||||
SystemInfo.isLinux -> LinuxIdeDistribution()
|
|
||||||
else -> error("Not supported app: $unpackDir")
|
|
||||||
}
|
|
||||||
|
|
||||||
return distribution.installIde(unpackDir.toPath(), executableFileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
interface IdeDownloader {
|
|
||||||
fun downloadIdeInstaller(ideInfo: IdeInfo, installerPath: Path): IdeInstaller
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
|
|
||||||
interface IdeInstallator {
|
|
||||||
fun install(ideInfo: IdeInfo): Pair<String, InstalledIde>
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
open class IdeInstaller(val installerFile: Path, val buildNumber: String) {
|
|
||||||
companion object
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.models.IdeProduct
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import kotlin.reflect.full.declaredMemberProperties
|
|
||||||
|
|
||||||
object IdeProductProvider {
|
|
||||||
/** GoLand */
|
|
||||||
val GO: IdeInfo = di.direct.instance<IdeProduct>().GO
|
|
||||||
|
|
||||||
/** IntelliJ Ultimate */
|
|
||||||
val IU: IdeInfo = di.direct.instance<IdeProduct>().IU
|
|
||||||
|
|
||||||
/** IntelliJ Community */
|
|
||||||
val IC: IdeInfo = di.direct.instance<IdeProduct>().IC
|
|
||||||
|
|
||||||
/** Android Studio */
|
|
||||||
val AI: IdeInfo = di.direct.instance<IdeProduct>().AI
|
|
||||||
|
|
||||||
/** WebStorm */
|
|
||||||
val WS: IdeInfo = di.direct.instance<IdeProduct>().WS
|
|
||||||
|
|
||||||
/** PhpStorm */
|
|
||||||
val PS: IdeInfo = di.direct.instance<IdeProduct>().PS
|
|
||||||
|
|
||||||
/** DataGrip */
|
|
||||||
val DB: IdeInfo = di.direct.instance<IdeProduct>().DB
|
|
||||||
|
|
||||||
/** RubyMine */
|
|
||||||
val RM: IdeInfo = di.direct.instance<IdeProduct>().RM
|
|
||||||
|
|
||||||
/** PyCharm Professional */
|
|
||||||
val PY: IdeInfo = di.direct.instance<IdeProduct>().PY
|
|
||||||
|
|
||||||
fun getProducts(): List<IdeInfo> = IdeProductProvider::class.declaredMemberProperties.map { it.get(IdeProductProvider) as IdeInfo }
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import com.intellij.ide.starter.models.VMOptionsDiff
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
internal abstract class InstalledBackedIDEStartConfig(
|
|
||||||
private val patchedVMOptionsFile: Path,
|
|
||||||
private val finalVMOptions: VMOptions
|
|
||||||
) : IDEStartConfig {
|
|
||||||
init {
|
|
||||||
finalVMOptions.writeIntelliJVmOptionFile(patchedVMOptionsFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
final override fun vmOptionsDiff(): VMOptionsDiff = finalVMOptions.diffIntelliJVmOptionFile(patchedVMOptionsFile)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
interface InstalledIde {
|
|
||||||
val originalVMOptions: VMOptions
|
|
||||||
|
|
||||||
val build: String
|
|
||||||
val os: String
|
|
||||||
val productCode: String
|
|
||||||
val isFromSources: Boolean
|
|
||||||
|
|
||||||
/** Bundled plugins directory, if supported **/
|
|
||||||
val bundledPluginsDir: Path?
|
|
||||||
get() = null
|
|
||||||
|
|
||||||
val patchedVMOptionsFile: Path?
|
|
||||||
get() = null
|
|
||||||
|
|
||||||
fun startConfig(vmOptions: VMOptions, logsDir: Path): IDEStartConfig
|
|
||||||
|
|
||||||
fun resolveAndDownloadTheSameJDK(): Path
|
|
||||||
|
|
||||||
fun isMajorVersionAtLeast(v: Int) = build.substringBefore(".").toIntOrNull()?.let { it >= v } ?: true
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect
|
|
||||||
import com.intellij.ide.starter.exec.exec
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.callJavaVersion
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.*
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
class LinuxIdeDistribution : IdeDistribution() {
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private val xvfbRunTool by lazy {
|
|
||||||
val toolName = "xvfb-run"
|
|
||||||
|
|
||||||
val homePath = Path(System.getProperty("user.home")).toAbsolutePath()
|
|
||||||
exec("xvfb-run", homePath, timeout = 5.seconds, args = listOf("which", toolName),
|
|
||||||
stdoutRedirect = ExecOutputRedirect.ToStdOut("xvfb-run-out"),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("xvfb-run-err"))
|
|
||||||
toolName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun linuxCommandLine(xvfbRunLog: Path): List<String> {
|
|
||||||
return when {
|
|
||||||
System.getenv("DISPLAY") != null -> listOf()
|
|
||||||
else ->
|
|
||||||
//hint https://gist.github.com/tullmann/2d8d38444c5e81a41b6d
|
|
||||||
listOf(
|
|
||||||
xvfbRunTool,
|
|
||||||
"--error-file=" + xvfbRunLog.toAbsolutePath().toString(),
|
|
||||||
"--server-args=-ac -screen 0 1920x1080x24",
|
|
||||||
"--auto-servernum",
|
|
||||||
"--server-num=88"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createXvfbRunLog(logsDir: Path): Path {
|
|
||||||
val logTxt = logsDir.resolve("xvfb-log.txt")
|
|
||||||
logTxt.deleteIfExists()
|
|
||||||
|
|
||||||
return Files.createFile(logTxt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun installIde(unpackDir: Path, executableFileName: String): InstalledIde {
|
|
||||||
require(SystemInfo.isLinux) { "Can only run on Linux, docker is possible, please PR" }
|
|
||||||
|
|
||||||
val appHome = (unpackDir.toFile().listFiles()?.singleOrNull { it.isDirectory }?.toPath() ?: unpackDir)
|
|
||||||
|
|
||||||
val buildTxtPath = appHome.resolve("build.txt")
|
|
||||||
require(buildTxtPath.isRegularFile()) { "Cannot find LinuxOS IDE vmoptions file in $unpackDir" }
|
|
||||||
val (productCode, build) = buildTxtPath.readText().trim().split("-", limit = 2)
|
|
||||||
|
|
||||||
val binDir = appHome / "bin"
|
|
||||||
val allBinFiles = binDir.listDirectoryEntries()
|
|
||||||
val executablePath = allBinFiles.singleOrNull { file ->
|
|
||||||
file.fileName.toString() == "$executableFileName.sh"
|
|
||||||
} ?: error("Failed to detect IDE executable .sh in:\n${allBinFiles.joinToString("\n")}")
|
|
||||||
|
|
||||||
return object : InstalledIde {
|
|
||||||
override val bundledPluginsDir = appHome.resolve("plugins")
|
|
||||||
|
|
||||||
val originalVMOptionsFile = executablePath.parent.resolve(
|
|
||||||
executablePath.fileName.toString().removeSuffix(".sh") + "64.vmoptions") //TODO: which file to pick with 64 or without?
|
|
||||||
override val originalVMOptions = VMOptions.readIdeVMOptions(this, originalVMOptionsFile)
|
|
||||||
override val patchedVMOptionsFile = appHome.parent.resolve("${appHome.fileName}.vmoptions")
|
|
||||||
|
|
||||||
override fun startConfig(vmOptions: VMOptions, logsDir: Path) =
|
|
||||||
object : InstalledBackedIDEStartConfig(patchedVMOptionsFile, vmOptions) {
|
|
||||||
|
|
||||||
override val environmentVariables: Map<String, String>
|
|
||||||
get() = super.environmentVariables.filterKeys {
|
|
||||||
when {
|
|
||||||
it.startsWith("DESKTOP") -> false
|
|
||||||
it.startsWith("DBUS") -> false
|
|
||||||
it.startsWith("APPIMAGE") -> false
|
|
||||||
it.startsWith("DEFAULTS_PATH") -> false
|
|
||||||
it.startsWith("GDM") -> false
|
|
||||||
it.startsWith("GNOME") -> false
|
|
||||||
it.startsWith("GTK") -> false
|
|
||||||
it.startsWith("MANDATORY_PATH") -> false
|
|
||||||
it.startsWith("QT") -> false
|
|
||||||
it.startsWith("SESSION") -> false
|
|
||||||
it.startsWith("TOOLBOX_VERSION") -> false
|
|
||||||
it.startsWith("XAUTHORITY") -> false
|
|
||||||
it.startsWith("XDG") -> false
|
|
||||||
it.startsWith("XMODIFIERS") -> false
|
|
||||||
it.startsWith("GPG_") -> false
|
|
||||||
it.startsWith("CLUTTER_IM_MODULE") -> false
|
|
||||||
it.startsWith("APPDIR") -> false
|
|
||||||
it.startsWith("LC") -> false
|
|
||||||
it.startsWith("SSH") -> false
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
} + ("LC_ALL" to "en_US.UTF-8")
|
|
||||||
|
|
||||||
val xvfbRunLog = createXvfbRunLog(logsDir)
|
|
||||||
|
|
||||||
override val errorDiagnosticFiles = listOf(xvfbRunLog)
|
|
||||||
override val workDir = appHome
|
|
||||||
override val commandLine: List<String> = linuxCommandLine(xvfbRunLog) + executablePath.toAbsolutePath().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val build = build
|
|
||||||
override val os = "linux"
|
|
||||||
override val productCode = productCode
|
|
||||||
override val isFromSources = false
|
|
||||||
|
|
||||||
override fun toString() = "IDE{$productCode, $build, $os, home=$unpackDir}"
|
|
||||||
override fun resolveAndDownloadTheSameJDK(): Path {
|
|
||||||
val jbrHome = appHome.resolve("jbr")
|
|
||||||
require(jbrHome.isDirectory()) {
|
|
||||||
"JbrHome is not found under $jbrHome"
|
|
||||||
}
|
|
||||||
|
|
||||||
val jbrFullVersion = callJavaVersion(jbrHome).substringAfter("build ").substringBefore(")")
|
|
||||||
logOutput("Found following $jbrFullVersion in the product: $productCode $build")
|
|
||||||
// in Android Studio bundled only JRE
|
|
||||||
if (productCode == IdeProductProvider.AI.productCode) return jbrHome
|
|
||||||
return downloadAndUnpackJbrIfNeeded(jbrFullVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import com.intellij.ide.starter.utils.callJavaVersion
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.isDirectory
|
|
||||||
import kotlin.io.path.isRegularFile
|
|
||||||
import kotlin.io.path.readText
|
|
||||||
|
|
||||||
class MacOsIdeDistribution : IdeDistribution() {
|
|
||||||
|
|
||||||
private fun getExecutableNameFromInfoPlist(appDir: File, @Suppress("SameParameterValue") keyName: String): String {
|
|
||||||
val infoPlistFile = appDir.resolve("Contents/Info.plist")
|
|
||||||
val xmlFactory = DocumentBuilderFactory.newInstance()
|
|
||||||
|
|
||||||
infoPlistFile.inputStream().use {
|
|
||||||
val xmlBuilder = xmlFactory.newDocumentBuilder()
|
|
||||||
val document = xmlBuilder.parse(it)
|
|
||||||
|
|
||||||
val keys = document.getElementsByTagName("key")
|
|
||||||
|
|
||||||
for (index in 0 until keys.length) {
|
|
||||||
val keyItem = keys.item(index)
|
|
||||||
|
|
||||||
// found the node we are looking for
|
|
||||||
if (keyItem.firstChild.nodeValue == keyName) {
|
|
||||||
|
|
||||||
// lets find the value - it will be the next sibling
|
|
||||||
var sibling: Node = keyItem.nextSibling
|
|
||||||
while (sibling.nodeType != Node.ELEMENT_NODE) {
|
|
||||||
sibling = sibling.nextSibling
|
|
||||||
}
|
|
||||||
|
|
||||||
return sibling.textContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error("Failed to resolve key: $keyName in $infoPlistFile")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun installIde(unpackDir: Path, executableFileName: String): InstalledIde {
|
|
||||||
val appDir = unpackDir.toFile().listFiles()?.singleOrNull { it.name.endsWith(".app") }?.toPath()
|
|
||||||
?: error("Invalid macOS application directory: $unpackDir")
|
|
||||||
|
|
||||||
val executableName = getExecutableNameFromInfoPlist(appDir.toFile(), "CFBundleExecutable")
|
|
||||||
|
|
||||||
val appHome = appDir.resolve("Contents")
|
|
||||||
val executablePath = appHome / "MacOS" / executableName
|
|
||||||
require(executablePath.isRegularFile()) { "Cannot find macOS IDE executable file in $executablePath" }
|
|
||||||
|
|
||||||
val originalVMOptions = appHome / "bin" / "$executableName.vmoptions"
|
|
||||||
require(originalVMOptions.isRegularFile()) { "Cannot find macOS IDE vmoptions file in $executablePath" }
|
|
||||||
|
|
||||||
val buildTxtPath = appHome / "Resources" / "build.txt"
|
|
||||||
require(buildTxtPath.isRegularFile()) { "Cannot find macOS IDE vmoptions file in $executablePath" }
|
|
||||||
|
|
||||||
val (productCode, build) = buildTxtPath.readText().trim().split("-", limit = 2)
|
|
||||||
|
|
||||||
return object : InstalledIde {
|
|
||||||
override val bundledPluginsDir = appHome / "plugins"
|
|
||||||
|
|
||||||
override val originalVMOptions = VMOptions.readIdeVMOptions(this, originalVMOptions)
|
|
||||||
override val patchedVMOptionsFile = appDir.parent.resolve("${appDir.fileName}.vmoptions") //see IDEA-220286
|
|
||||||
|
|
||||||
override fun startConfig(vmOptions: VMOptions, logsDir: Path) = object : InstalledBackedIDEStartConfig(patchedVMOptionsFile,
|
|
||||||
vmOptions) {
|
|
||||||
override val workDir = appDir
|
|
||||||
override val commandLine = listOf(executablePath.toAbsolutePath().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val build = build
|
|
||||||
override val os = "mac"
|
|
||||||
override val productCode = productCode
|
|
||||||
override val isFromSources = false
|
|
||||||
|
|
||||||
override fun toString() = "IDE{$productCode, $build, $os, home=$appDir}"
|
|
||||||
|
|
||||||
override fun resolveAndDownloadTheSameJDK(): Path {
|
|
||||||
val jbrHome = appHome / "jbr"
|
|
||||||
|
|
||||||
require(jbrHome.isDirectory()) {
|
|
||||||
"JbrHome is not found under $jbrHome"
|
|
||||||
}
|
|
||||||
|
|
||||||
val javaHome = jbrHome / "Contents" / "Home"
|
|
||||||
require(javaHome.isDirectory()) {
|
|
||||||
"JavaHome is not found under $javaHome"
|
|
||||||
}
|
|
||||||
|
|
||||||
val jbrFullVersion = callJavaVersion(javaHome).substringAfter("build ").substringBefore(")")
|
|
||||||
logOutput("Found following $jbrFullVersion in the product: $productCode $build")
|
|
||||||
|
|
||||||
// in Android Studio bundled only JRE
|
|
||||||
if (productCode == IdeProductProvider.AI.productCode) return jbrHome
|
|
||||||
return downloadAndUnpackJbrIfNeeded(jbrFullVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
class SimpleInstaller : IdeInstallator {
|
|
||||||
|
|
||||||
override fun install(ideInfo: IdeInfo): Pair<String, InstalledIde> {
|
|
||||||
val installersDirectory = (di.direct.instance<GlobalPaths>().installersDirectory / ideInfo.productCode).createDirectories()
|
|
||||||
//Download
|
|
||||||
val ideInstaller = di.direct.instance<IdeDownloader>().downloadIdeInstaller(ideInfo, installersDirectory)
|
|
||||||
val installDir = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("builds") / "${ideInfo.productCode}-${ideInstaller.buildNumber}"
|
|
||||||
//Unpack
|
|
||||||
IdeArchiveExtractor.unpackIdeIfNeeded(ideInstaller.installerFile.toFile(), installDir.toFile())
|
|
||||||
//Install
|
|
||||||
return Pair(ideInstaller.buildNumber, IdeDistributionFactory.installIDE(installDir.toFile(), ideInfo.executableFileName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import com.intellij.ide.starter.utils.callJavaVersion
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.*
|
|
||||||
|
|
||||||
class WindowsIdeDistribution : IdeDistribution() {
|
|
||||||
override fun installIde(unpackDir: Path, executableFileName: String): InstalledIde {
|
|
||||||
val buildTxtPath = unpackDir.resolve("build.txt")
|
|
||||||
require(buildTxtPath.isRegularFile()) { "Cannot find WindowsOS IDE vmoptions file in $unpackDir" }
|
|
||||||
val (productCode, build) = buildTxtPath.readText().trim().split("-", limit = 2)
|
|
||||||
|
|
||||||
val binDir = unpackDir / "bin"
|
|
||||||
|
|
||||||
val allBinFiles = binDir.listDirectoryEntries()
|
|
||||||
|
|
||||||
val executablePath = allBinFiles.singleOrNull { file ->
|
|
||||||
file.fileName.toString() == "${executableFileName}64.exe"
|
|
||||||
} ?: error("Failed to detect executable name, ending with 64.exe in:\n${allBinFiles.joinToString("\n")}")
|
|
||||||
|
|
||||||
val originalVMOptionsFile = executablePath.parent.resolve("${executablePath.fileName}.vmoptions")
|
|
||||||
|
|
||||||
return object : InstalledIde {
|
|
||||||
override val bundledPluginsDir = unpackDir.resolve("plugins")
|
|
||||||
|
|
||||||
override val originalVMOptions = VMOptions.readIdeVMOptions(this, originalVMOptionsFile)
|
|
||||||
override val patchedVMOptionsFile = unpackDir.parent.resolve("${unpackDir.fileName}.vmoptions")
|
|
||||||
|
|
||||||
override fun startConfig(vmOptions: VMOptions, logsDir: Path) = object : InstalledBackedIDEStartConfig(patchedVMOptionsFile,
|
|
||||||
vmOptions) {
|
|
||||||
override val workDir = unpackDir
|
|
||||||
override val commandLine = listOf(executablePath.toAbsolutePath().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val build = build
|
|
||||||
override val os = "windows"
|
|
||||||
override val productCode = productCode
|
|
||||||
override val isFromSources = false
|
|
||||||
|
|
||||||
override fun toString() = "IDE{$productCode, $build, $os, home=$unpackDir}"
|
|
||||||
override fun resolveAndDownloadTheSameJDK(): Path {
|
|
||||||
val jbrHome = unpackDir / "jbr"
|
|
||||||
require(jbrHome.isDirectory()) {
|
|
||||||
"JbrHome is not found under $jbrHome"
|
|
||||||
}
|
|
||||||
|
|
||||||
val jbrFullVersion = callJavaVersion(jbrHome).substringAfter("build ").substringBefore(")")
|
|
||||||
logOutput("Found following $jbrFullVersion in the product: $productCode $build")
|
|
||||||
|
|
||||||
// in Android Studio bundled only JRE
|
|
||||||
if (productCode == IdeProductProvider.AI.productCode) return jbrHome
|
|
||||||
return downloadAndUnpackJbrIfNeeded(jbrFullVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide
|
|
||||||
|
|
||||||
interface CodeInjector {
|
|
||||||
fun setup(context: IDETestContext)
|
|
||||||
fun tearDown(context: IDETestContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PlainCodeInjector : CodeInjector {
|
|
||||||
fun addGroovyCode(code: String)
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide.command
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One or more commands, that will be "played" in sequence by IDE
|
|
||||||
*/
|
|
||||||
open class CommandChain : MarshallableCommand, Iterable<MarshallableCommand> {
|
|
||||||
private val _chain = mutableListOf<MarshallableCommand>()
|
|
||||||
|
|
||||||
override fun iterator(): Iterator<MarshallableCommand> = _chain.iterator()
|
|
||||||
|
|
||||||
override fun storeToString(): String {
|
|
||||||
return _chain.joinToString(separator = System.lineSeparator()) { it.storeToString() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern for adding a command: %YOUR_COMMAND_PREFIX COMMAND_PARAMS
|
|
||||||
*/
|
|
||||||
fun addCommand(command: String) {
|
|
||||||
_chain.add(initMarshallableCommand(command))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCommand(command: MarshallableCommand) {
|
|
||||||
_chain.add(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern for adding a command: %YOUR_COMMAND_PREFIX COMMAND_PARAM_1 .. COMMAND_PARAM_N
|
|
||||||
*/
|
|
||||||
fun addCommand(vararg commandArgs: String) {
|
|
||||||
val command = initMarshallableCommand(commandArgs.joinToString(separator = " "))
|
|
||||||
_chain.add(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCommandChain(commandChain: CommandChain) {
|
|
||||||
_chain.addAll(commandChain)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initMarshallableCommand(content: String): MarshallableCommand =
|
|
||||||
object : MarshallableCommand {
|
|
||||||
override fun storeToString(): String = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.intellij.ide.starter.ide.command
|
|
||||||
|
|
||||||
interface MarshallableCommand {
|
|
||||||
fun storeToString(): String
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.runner.IDERunContext
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
data class IDEStartResult(
|
|
||||||
val runContext: IDERunContext,
|
|
||||||
val logsDir: Path,
|
|
||||||
val executionTime: Duration,
|
|
||||||
val vmOptionsDiff: VMOptionsDiff? = null,
|
|
||||||
val failureError: Throwable? = null
|
|
||||||
) {
|
|
||||||
val context: IDETestContext get() = runContext.testContext
|
|
||||||
|
|
||||||
val extraAttributes: MutableMap<String, String> = mutableMapOf()
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class IDEStartupReports(startupReportsDir: Path) {
|
|
||||||
val statsJSON: Path = startupReportsDir.resolve("startup-stats.json")
|
|
||||||
|
|
||||||
val statsObject
|
|
||||||
get() = ObjectMapper().readTree(statsJSON.toFile()) as ObjectNode
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "IDEStartupReports(statsJSON=$statsJSON)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.community.model.BuildType
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
|
|
||||||
data class IdeInfo(
|
|
||||||
val productCode: String,
|
|
||||||
val platformPrefix: String,
|
|
||||||
val executableFileName: String,
|
|
||||||
val buildType: String = BuildType.EAP.type,
|
|
||||||
val buildNumber: String = "",
|
|
||||||
val tag: String? = null
|
|
||||||
) {
|
|
||||||
companion object
|
|
||||||
|
|
||||||
val installerFilePrefix
|
|
||||||
get() = when (productCode) {
|
|
||||||
"IU" -> "ideaIU"
|
|
||||||
"IC" -> "ideaIC"
|
|
||||||
"WS" -> "WebStorm"
|
|
||||||
"PS" -> "PhpStorm"
|
|
||||||
"DB" -> "datagrip"
|
|
||||||
"GO" -> "goland"
|
|
||||||
"RM" -> "RubyMine"
|
|
||||||
"PY" -> "pycharmPY"
|
|
||||||
else -> error("Unknown product code: $productCode")
|
|
||||||
}
|
|
||||||
|
|
||||||
val installerProductName
|
|
||||||
get() = when (productCode) {
|
|
||||||
"IU" -> "intellij"
|
|
||||||
"IC" -> "intellij.ce"
|
|
||||||
"RM" -> "rubymine"
|
|
||||||
"PY" -> "pycharm"
|
|
||||||
else -> installerFilePrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
val installerFileExt
|
|
||||||
get() = when {
|
|
||||||
SystemInfo.isWindows -> ".win.zip"
|
|
||||||
SystemInfo.isLinux -> ".tar.gz"
|
|
||||||
SystemInfo.isMac -> when (System.getProperty("os.arch")) {
|
|
||||||
"x86_64" -> ".dmg"
|
|
||||||
"aarch64" -> "-aarch64.dmg"
|
|
||||||
else -> error("Unknown architecture of Mac OS")
|
|
||||||
}
|
|
||||||
else -> error("Unknown OS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
interface IdeProduct {
|
|
||||||
/** GoLand */
|
|
||||||
val GO: IdeInfo
|
|
||||||
|
|
||||||
/** IntelliJ Ultimate */
|
|
||||||
val IU: IdeInfo
|
|
||||||
|
|
||||||
/** IntelliJ Community */
|
|
||||||
val IC: IdeInfo
|
|
||||||
|
|
||||||
/** Android Studio */
|
|
||||||
val AI: IdeInfo
|
|
||||||
|
|
||||||
/** WebStorm */
|
|
||||||
val WS: IdeInfo
|
|
||||||
|
|
||||||
/** PhpStorm */
|
|
||||||
val PS: IdeInfo
|
|
||||||
|
|
||||||
/** DataGrip */
|
|
||||||
val DB: IdeInfo
|
|
||||||
|
|
||||||
/** RubyMine */
|
|
||||||
val RM: IdeInfo
|
|
||||||
|
|
||||||
/** PyCharm Professional */
|
|
||||||
val PY: IdeInfo
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
object IdeProductImp : IdeProduct {
|
|
||||||
/** GoLand */
|
|
||||||
override val GO = IdeInfo(
|
|
||||||
productCode = "GO",
|
|
||||||
platformPrefix = "GoLand",
|
|
||||||
executableFileName = "goland",
|
|
||||||
)
|
|
||||||
|
|
||||||
/** IntelliJ Ultimate */
|
|
||||||
override val IU = IdeInfo(
|
|
||||||
productCode = "IU",
|
|
||||||
platformPrefix = "idea",
|
|
||||||
executableFileName = "idea"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** IntelliJ Community */
|
|
||||||
override val IC = IdeInfo(
|
|
||||||
productCode = "IC",
|
|
||||||
platformPrefix = "Idea",
|
|
||||||
executableFileName = "idea"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Android Studio */
|
|
||||||
override val AI = IdeInfo(
|
|
||||||
productCode = "AI",
|
|
||||||
platformPrefix = "AndroidStudio",
|
|
||||||
executableFileName = "studio"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** WebStorm */
|
|
||||||
override val WS = IdeInfo(
|
|
||||||
productCode = "WS",
|
|
||||||
platformPrefix = "WebStorm",
|
|
||||||
executableFileName = "webstorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** PhpStorm */
|
|
||||||
override val PS = IdeInfo(
|
|
||||||
productCode = "PS",
|
|
||||||
platformPrefix = "PhpStorm",
|
|
||||||
executableFileName = "phpstorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** DataGrip */
|
|
||||||
override val DB = IdeInfo(
|
|
||||||
productCode = "DB",
|
|
||||||
platformPrefix = "DataGrip",
|
|
||||||
executableFileName = "datagrip"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** RubyMine */
|
|
||||||
override val RM = IdeInfo(
|
|
||||||
productCode = "RM",
|
|
||||||
platformPrefix = "Ruby",
|
|
||||||
executableFileName = "rubymine"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** PyCharm Professional */
|
|
||||||
override val PY = IdeInfo(
|
|
||||||
productCode = "PY",
|
|
||||||
platformPrefix = "Python",
|
|
||||||
executableFileName = "pycharm"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.system.OsType
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
|
|
||||||
class OsDataStorage<T>(vararg val items: Pair<OsType, T>) {
|
|
||||||
val get: T
|
|
||||||
get() {
|
|
||||||
require(items.isNotEmpty()) { "Os dependent data should not be empty when accessing it" }
|
|
||||||
|
|
||||||
val osSpecificData = when {
|
|
||||||
SystemInfo.isMac -> items.firstOrNull { it.first == OsType.MacOS }
|
|
||||||
SystemInfo.isWindows -> items.firstOrNull { it.first == OsType.Windows }
|
|
||||||
SystemInfo.isLinux -> items.firstOrNull { it.first == OsType.Linux }
|
|
||||||
else -> items.first { it.first == OsType.Other }
|
|
||||||
}
|
|
||||||
|
|
||||||
osSpecificData?.let { return it.second }
|
|
||||||
|
|
||||||
return items.first { it.first == OsType.Other }.second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.community.model.BuildType
|
|
||||||
import com.intellij.ide.starter.ide.command.MarshallableCommand
|
|
||||||
import com.intellij.ide.starter.project.ProjectInfo
|
|
||||||
import com.intellij.ide.starter.project.ProjectInfoSpec
|
|
||||||
|
|
||||||
data class TestCase(
|
|
||||||
val ideInfo: IdeInfo,
|
|
||||||
val projectInfo: ProjectInfoSpec? = null,
|
|
||||||
val commands: Iterable<MarshallableCommand> = listOf(),
|
|
||||||
val vmOptionsFix: VMOptions.() -> VMOptions = { this },
|
|
||||||
val useInMemoryFileSystem: Boolean = false
|
|
||||||
) {
|
|
||||||
fun withProject(projectInfo: ProjectInfoSpec): TestCase = copy(projectInfo = projectInfo)
|
|
||||||
|
|
||||||
fun withCommands(commands: Iterable<MarshallableCommand> = this.commands): TestCase = copy(commands = commands.toList())
|
|
||||||
|
|
||||||
/** Project in this case will be reused between tests */
|
|
||||||
fun markNotReusable(): TestCase = markReusable(false)
|
|
||||||
|
|
||||||
/** On each test run the project will be unpacked again.
|
|
||||||
* This guarantees that there is not side effects from previous test runs
|
|
||||||
**/
|
|
||||||
fun markReusable(isReusable: Boolean = true) = copy(projectInfo = (projectInfo as ProjectInfo).copy(isReusable = isReusable))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [buildNumber] - EAP build number to download
|
|
||||||
* E.g: "222.3244.1"
|
|
||||||
* If empty - the latest EAP will be downloaded.
|
|
||||||
* [Downloads for IDEA Ultimate](https://www.jetbrains.com/idea/download/other.html)
|
|
||||||
**/
|
|
||||||
fun useEAP(buildNumber: String = ""): TestCase {
|
|
||||||
return copy(ideInfo = ideInfo.copy(buildType = BuildType.EAP.type, buildNumber = buildNumber))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [buildNumber] - Release build number to download
|
|
||||||
* E.g: "222.3244.1"
|
|
||||||
* If empty - the latest release will be downloaded.
|
|
||||||
* [Downloads for IDEA Ultimate](https://www.jetbrains.com/idea/download/other.html)
|
|
||||||
**/
|
|
||||||
fun useRelease(buildNumber: String = ""): TestCase {
|
|
||||||
return copy(ideInfo = ideInfo.copy(buildType = BuildType.RELEASE.type, buildNumber = buildNumber))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
data class VMLogsInfo(val cdsLoadedClassesCount: Int,
|
|
||||||
val classLoadLogFile: File,
|
|
||||||
val classPathLogFile: File,
|
|
||||||
val startResult: IDEStartResult) {
|
|
||||||
|
|
||||||
val classPathLog get() = classPathLogFile.readText()
|
|
||||||
}
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.InstalledIde
|
|
||||||
import com.intellij.ide.starter.ide.command.MarshallableCommand
|
|
||||||
import com.intellij.ide.starter.path.IDEDataPaths
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem.cleanPathFromSlashes
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import com.intellij.ide.starter.utils.writeJvmArgsFile
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.readLines
|
|
||||||
import kotlin.io.path.writeLines
|
|
||||||
import kotlin.io.path.writeText
|
|
||||||
|
|
||||||
/**
|
|
||||||
* allows to combine VMOptions mapping functions easily by calling this function as
|
|
||||||
* ```
|
|
||||||
* {}.andThen {} function
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
fun (VMOptions.() -> VMOptions).andThen(right: VMOptions.() -> VMOptions): VMOptions.() -> VMOptions = {
|
|
||||||
val left = this@andThen
|
|
||||||
this.left().right()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class VMOptions(
|
|
||||||
private val ide: InstalledIde,
|
|
||||||
private val data: List<String>,
|
|
||||||
val env: Map<String, String>
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun readIdeVMOptions(ide: InstalledIde, file: Path): VMOptions {
|
|
||||||
return VMOptions(
|
|
||||||
ide = ide,
|
|
||||||
data = file
|
|
||||||
.readLines()
|
|
||||||
.map { it.trim() }
|
|
||||||
.filter { it.isNotBlank() },
|
|
||||||
env = emptyMap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = buildString {
|
|
||||||
appendLine("VMOptions{")
|
|
||||||
appendLine(" env=$env")
|
|
||||||
for (line in data) {
|
|
||||||
appendLine(" $line")
|
|
||||||
}
|
|
||||||
appendLine("} // VMOptions")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addSystemProperty(key: String, value: Boolean): VMOptions = addSystemProperty(key, value.toString())
|
|
||||||
|
|
||||||
fun addSystemProperty(key: String, value: Int): VMOptions = addSystemProperty(key, value.toString())
|
|
||||||
|
|
||||||
fun addSystemProperty(key: String, value: Long): VMOptions = addSystemProperty(key, value.toString())
|
|
||||||
|
|
||||||
fun addSystemProperty(key: String, value: Path): VMOptions = addSystemProperty(key, value.toAbsolutePath().toString())
|
|
||||||
|
|
||||||
fun addSystemProperty(key: String, value: String): VMOptions {
|
|
||||||
logOutput("Setting system property: [$key=$value]")
|
|
||||||
System.setProperty(key, value) // to synchronize behaviour in IDEA and on test runner side
|
|
||||||
return addLine(line = "-D$key=$value", filterPrefix = "-D$key=")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addLine(line: String, filterPrefix: String? = null): VMOptions {
|
|
||||||
if (data.contains(line)) return this
|
|
||||||
val copy = if (filterPrefix == null) data else data.filterNot { it.trim().startsWith(filterPrefix) }
|
|
||||||
return copy(data = copy + line)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterKeys(toRemove: (String) -> Boolean) = copy(data = data.filterNot(toRemove))
|
|
||||||
|
|
||||||
fun withEnv(key: String, value: String) = copy(env = env + (key to value))
|
|
||||||
|
|
||||||
fun writeIntelliJVmOptionFile(path: Path) {
|
|
||||||
path.writeLines(data)
|
|
||||||
logOutput("Write vmoptions patch to $path")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun diffIntelliJVmOptionFile(theFile: Path): VMOptionsDiff {
|
|
||||||
val loadedOptions = readIdeVMOptions(this.ide, theFile).data
|
|
||||||
return VMOptionsDiff(originalLines = this.data, actualLines = loadedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeJavaArgsFile(theFile: File) {
|
|
||||||
writeJvmArgsFile(theFile, this.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun overrideDirectories(paths: IDEDataPaths) = this
|
|
||||||
.addSystemProperty("idea.config.path", paths.configDir)
|
|
||||||
.addSystemProperty("idea.system.path", paths.systemDir)
|
|
||||||
.addSystemProperty("idea.plugins.path", paths.pluginsDir)
|
|
||||||
.addSystemProperty("idea.log.path", paths.logsDir)
|
|
||||||
|
|
||||||
fun enableStartupPerformanceLog(perf: IDEStartupReports): VMOptions {
|
|
||||||
return this
|
|
||||||
.addSystemProperty("idea.log.perf.stats.file", perf.statsJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableClassLoadingReport(filePath: Path): VMOptions {
|
|
||||||
return this
|
|
||||||
.addSystemProperty("idea.log.class.list.file", filePath)
|
|
||||||
.addSystemProperty("idea.record.classpath.info", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun configureLoggers(
|
|
||||||
debugLoggers: List<String> = emptyList(),
|
|
||||||
traceLoggers: List<String> = emptyList()
|
|
||||||
): VMOptions {
|
|
||||||
val withDebug = if (debugLoggers.isNotEmpty()) {
|
|
||||||
this.addSystemProperty("idea.log.debug.categories", debugLoggers.joinToString(separator = ",") { "#" + it.removePrefix("#") })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (traceLoggers.isNotEmpty()) {
|
|
||||||
withDebug.addSystemProperty("idea.log.trace.categories", traceLoggers.joinToString(separator = ",") { "#" + it.removePrefix("#") })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
withDebug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debug(port: Int = 5005, suspend: Boolean = true): VMOptions {
|
|
||||||
val suspendKey = if (suspend) "y" else "n"
|
|
||||||
val configLine = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=${suspendKey},address=*:${port}"
|
|
||||||
return addLine(configLine, filterPrefix = "-agentlib:jdwp")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inHeadlessMode() = this
|
|
||||||
.addSystemProperty("java.awt.headless", true)
|
|
||||||
|
|
||||||
fun disableStartupDialogs() = this
|
|
||||||
.addSystemProperty("jb.consents.confirmation.enabled", false)
|
|
||||||
.addSystemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
|
||||||
|
|
||||||
fun takeScreenshotIfFailure(logsDir: Path) = this
|
|
||||||
.addSystemProperty("ide.performance.screenshot.before.kill", logsDir.resolve("screenshot_beforeKill.jpg").toString())
|
|
||||||
|
|
||||||
fun installTestScript(testName: String,
|
|
||||||
paths: IDEDataPaths,
|
|
||||||
commands: Iterable<MarshallableCommand>): VMOptions {
|
|
||||||
val scriptText = commands.joinToString(separator = System.lineSeparator()) { it.storeToString() }
|
|
||||||
|
|
||||||
val scriptFileName = testName.cleanPathFromSlashes(replaceWith = "_") + ".text"
|
|
||||||
val scriptFile = paths.systemDir.resolve(scriptFileName).apply {
|
|
||||||
parent.createDirectories()
|
|
||||||
}
|
|
||||||
scriptFile.writeText(scriptText)
|
|
||||||
|
|
||||||
return this.addSystemProperty("testscript.filename", scriptFile)
|
|
||||||
// Use non-success status code 1 when running IDE as command line tool.
|
|
||||||
.addSystemProperty("testscript.must.exist.process.with.non.success.code.on.ide.error", "true")
|
|
||||||
// No need to report TeamCity test failure from within test script.
|
|
||||||
.addSystemProperty("testscript.must.report.teamcity.test.failure.on.error", "false")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @see com.intellij.startupTime.StartupTimeWithCDSonJDK13.runOnJDK13 **/
|
|
||||||
fun withCustomJRE(jre: Path): VMOptions {
|
|
||||||
if (SystemInfo.isLinux) {
|
|
||||||
val jrePath = jre.toAbsolutePath().toString()
|
|
||||||
val envKey = when (ide.productCode) {
|
|
||||||
"IU" -> "IDEA_JDK"
|
|
||||||
"WS" -> "WEBIDE_JDK"
|
|
||||||
else -> error("Not supported for product $ide")
|
|
||||||
}
|
|
||||||
return this.withEnv(envKey, jrePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SystemInfo.isMac) {
|
|
||||||
//Does not work -- https://intellij-support.jetbrains.com/hc/en-us/articles/206544879-Selecting-the-JDK-version-the-IDE-will-run-under
|
|
||||||
//see https://youtrack.jetbrains.com/issue/IDEA-223075
|
|
||||||
//see Launcher.m:226
|
|
||||||
val jrePath = jre.toAbsolutePath().toString()
|
|
||||||
val envKey = when (ide.productCode) {
|
|
||||||
"IU" -> "IDEA_JDK"
|
|
||||||
"WS" -> "WEBSTORM_JDK"
|
|
||||||
else -> error("Not supported for product $ide")
|
|
||||||
}
|
|
||||||
return this.withEnv(envKey, jrePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SystemInfo.isWindows) {
|
|
||||||
//see WinLauncher.rc and WinLauncher.cpp:294
|
|
||||||
//see https://youtrack.jetbrains.com/issue/IDEA-223348
|
|
||||||
val jrePath = jre.toRealPath().toString().replace("/", "\\")
|
|
||||||
val envKey = when (ide.productCode) {
|
|
||||||
"IU" -> "IDEA_JDK_64"
|
|
||||||
"WS" -> "WEBIDE_JDK_64"
|
|
||||||
else -> error("Not supported for product $ide")
|
|
||||||
}
|
|
||||||
return this.withEnv(envKey, jrePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
error("Current OS is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun usingStartupFramework() = this
|
|
||||||
.addSystemProperty("startup.performance.framework", true)
|
|
||||||
|
|
||||||
fun setFlagIntegrationTests() = this
|
|
||||||
.addSystemProperty("idea.is.integration.test", true)
|
|
||||||
|
|
||||||
fun setFatalErrorNotificationEnabled() = this
|
|
||||||
.addSystemProperty("idea.fatal.error.notification", true)
|
|
||||||
|
|
||||||
fun withJvmCrashLogDirectory(jvmCrashLogDirectory: Path) = this
|
|
||||||
.addLine("-XX:ErrorFile=${jvmCrashLogDirectory.toAbsolutePath()}${File.separator}java_error_in_idea_%p.log", "-XX:ErrorFile=")
|
|
||||||
|
|
||||||
fun withHeapDumpOnOutOfMemoryDirectory(directory: Path) = this
|
|
||||||
.addLine("-XX:HeapDumpPath=${directory.toAbsolutePath()}", "-XX:HeapDumpPath=")
|
|
||||||
|
|
||||||
fun withXmx(sizeMb: Int) = this
|
|
||||||
.addLine("-Xmx" + sizeMb + "m", "-Xmx")
|
|
||||||
|
|
||||||
fun withG1GC() = this
|
|
||||||
.filterKeys { it == "-XX:+UseConcMarkSweepGC" }
|
|
||||||
.filterKeys { it == "-XX:+UseG1GC" }
|
|
||||||
.addLine("-XX:+UseG1GC")
|
|
||||||
|
|
||||||
/** see [JEP 318](https://openjdk.org/jeps/318) **/
|
|
||||||
fun withEpsilonGC() = this
|
|
||||||
.filterKeys { it == "-XX:+UseConcMarkSweepGC" }
|
|
||||||
.filterKeys { it == "-XX:+UseG1GC" }
|
|
||||||
.addLine("-XX:+UnlockExperimentalVMOptions")
|
|
||||||
.addLine("-XX:+UseEpsilonGC")
|
|
||||||
.addLine("-Xmx16g", "-Xmx")
|
|
||||||
|
|
||||||
// a dummy wrapper to simplify expressions
|
|
||||||
fun id() = this
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.intellij.ide.starter.models
|
|
||||||
|
|
||||||
data class VMOptionsDiff(val originalLines: List<String>,
|
|
||||||
val actualLines: List<String>
|
|
||||||
) {
|
|
||||||
val newLines = actualLines - originalLines
|
|
||||||
val missingLines = originalLines - actualLines
|
|
||||||
val isEmpty = newLines.isEmpty() && missingLines.isEmpty()
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "VMOptionsDiff(newLines=$newLines, missingLines=$missingLines)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.intellij.ide.starter.path
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem.getDirectoryTreePresentableSizes
|
|
||||||
import com.intellij.ide.starter.utils.getDiskInfo
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
abstract class GlobalPaths(val checkoutDir: Path) {
|
|
||||||
val intelliJOutDirectory: Path = checkoutDir.toAbsolutePath() / "out"
|
|
||||||
val artifactsDirectory: Path = intelliJOutDirectory / "artifacts"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local => out
|
|
||||||
* CI => out/tests
|
|
||||||
*/
|
|
||||||
val compiledRootDirectory: Path = when (di.direct.instance<CIServer>().isBuildRunningOnCI) {
|
|
||||||
true -> intelliJOutDirectory / "tests"
|
|
||||||
false -> intelliJOutDirectory // Local run
|
|
||||||
}
|
|
||||||
|
|
||||||
open val testHomePath:Path = intelliJOutDirectory.resolve("perf-startup").createDirectories()
|
|
||||||
|
|
||||||
val installersDirectory = (testHomePath / "installers").createDirectories()
|
|
||||||
|
|
||||||
val testsDirectory = (testHomePath / "tests").createDirectories()
|
|
||||||
|
|
||||||
private val cacheDirectory: Path = if (di.direct.instance<CIServer>().isBuildRunningOnCI &&
|
|
||||||
!System.getProperty("agent.persistent.cache").isNullOrEmpty()
|
|
||||||
) {
|
|
||||||
(Paths.get(System.getProperty("agent.persistent.cache"), "perf-tests-cache")).createDirectories()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
(testHomePath / "cache").createDirectories()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCacheDirectoryFor(entity: String): Path = (cacheDirectory / entity).createDirectories()
|
|
||||||
|
|
||||||
fun getDiskUsageDiagnostics(): String {
|
|
||||||
return buildString {
|
|
||||||
appendLine("Disk usage by integration tests (home $testHomePath)")
|
|
||||||
appendLine(Files.getFileStore(testHomePath).getDiskInfo())
|
|
||||||
appendLine()
|
|
||||||
appendLine(testHomePath.getDirectoryTreePresentableSizes(3))
|
|
||||||
if (cacheDirectory != testHomePath / "cache") {
|
|
||||||
appendLine("Agent persistent cache directory disk usage $cacheDirectory")
|
|
||||||
appendLine(cacheDirectory.getDirectoryTreePresentableSizes(2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package com.intellij.ide.starter.path
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem.getFileOrDirectoryPresentableSize
|
|
||||||
import com.intellij.ide.starter.utils.createInMemoryDirectory
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
import kotlin.streams.toList
|
|
||||||
|
|
||||||
class IDEDataPaths(
|
|
||||||
private val testHome: Path,
|
|
||||||
private val inMemoryRoot: Path?
|
|
||||||
) : Closeable {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun createPaths(testName: String, testHome: Path, useInMemoryFs: Boolean): IDEDataPaths {
|
|
||||||
testHome.toFile().deleteRecursively()
|
|
||||||
testHome.createDirectories()
|
|
||||||
val inMemoryRoot = if (useInMemoryFs) {
|
|
||||||
createInMemoryDirectory("ide-integration-test-$testName")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
return IDEDataPaths(testHome = testHome, inMemoryRoot = inMemoryRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val logsDir = (testHome / "log").createDirectories()
|
|
||||||
val reportsDir = (testHome / "reports").createDirectories()
|
|
||||||
val snapshotsDir = (testHome / "snapshots").createDirectories()
|
|
||||||
val tempDir = (testHome / "temp").createDirectories()
|
|
||||||
|
|
||||||
// Directory used to store TeamCity artifacts. To make sure the TeamCity publishes all artifacts
|
|
||||||
// files added to this directory must not be removed until the end of the tests execution .
|
|
||||||
val teamCityArtifacts = (testHome / "team-city-artifacts").createDirectories()
|
|
||||||
|
|
||||||
val configDir = ((inMemoryRoot ?: testHome) / "config").createDirectories()
|
|
||||||
val systemDir = ((inMemoryRoot ?: testHome) / "system").createDirectories()
|
|
||||||
val pluginsDir = (testHome / "plugins").createDirectories()
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
if (inMemoryRoot != null) {
|
|
||||||
try {
|
|
||||||
inMemoryRoot.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
logOutput("! Failed to unmount in-memory FS at $inMemoryRoot")
|
|
||||||
e.stackTraceToString().lines().forEach { logOutput(" $it") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (di.direct.instance<CIServer>().isBuildRunningOnCI) {
|
|
||||||
deleteDirectories()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteDirectories() {
|
|
||||||
val toDelete = getDirectoriesToDeleteAfterTest().filter { it.exists() }
|
|
||||||
|
|
||||||
if (toDelete.isNotEmpty()) {
|
|
||||||
logOutput(buildString {
|
|
||||||
appendLine("Removing directories of $testHome")
|
|
||||||
toDelete.forEach { path ->
|
|
||||||
appendLine(" $path: ${path.getFileOrDirectoryPresentableSize()}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
toDelete.forEach { runCatching { it.toFile().deleteRecursively() } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDirectoriesToDeleteAfterTest() = if (testHome.exists()) {
|
|
||||||
Files.list(testHome).use { it.toList() } - setOf(teamCityArtifacts)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "IDE Test Paths at $testHome"
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.intellij.ide.starter.path
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.utils.Git
|
|
||||||
|
|
||||||
class InstallerGlobalPaths : GlobalPaths(Git.getRepoRoot())
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package com.intellij.ide.starter.plugins
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.jar.JarFile
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
import kotlin.io.path.readLines
|
|
||||||
import kotlin.io.path.writeLines
|
|
||||||
|
|
||||||
open class PluginConfigurator(val testContext: IDETestContext) {
|
|
||||||
val disabledPluginsPath: Path
|
|
||||||
get() = testContext.paths.configDir / "disabled_plugins.txt"
|
|
||||||
|
|
||||||
fun setupPluginFromPath(pathToPluginArchive: Path) = apply {
|
|
||||||
FileSystem.unpack(pathToPluginArchive, testContext.paths.pluginsDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupPluginFromURL(urlToPluginZipFile: String) = apply {
|
|
||||||
val pluginRootDir = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("plugins")
|
|
||||||
val pluginZip: Path = pluginRootDir / testContext.ide.build / urlToPluginZipFile.substringAfterLast("/")
|
|
||||||
|
|
||||||
HttpClient.download(urlToPluginZipFile, pluginZip)
|
|
||||||
FileSystem.unpack(pluginZip, testContext.paths.pluginsDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupPluginFromPluginManager(
|
|
||||||
pluginId: String,
|
|
||||||
ideBuild: String,
|
|
||||||
channel: String? = null,
|
|
||||||
) = apply {
|
|
||||||
logOutput("Setting up plugin: $pluginId ...")
|
|
||||||
|
|
||||||
val fileName = pluginId.replace(".", "-") + ".zip"
|
|
||||||
val downloadedPlugin = di.direct.instance<GlobalPaths>().getCacheDirectoryFor("plugins") / testContext.ide.build / fileName
|
|
||||||
if (!downloadedPlugin.toFile().exists()) {
|
|
||||||
val url = buildString {
|
|
||||||
append("https://plugins.jetbrains.com/pluginManager/")
|
|
||||||
append("?action=download")
|
|
||||||
append("&id=${pluginId.replace(" ", "%20")}")
|
|
||||||
append("&noStatistic=false")
|
|
||||||
append("&build=$ideBuild")
|
|
||||||
channel?.let {
|
|
||||||
append("&channel=$it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HttpClient.download(url, downloadedPlugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystem.unpack(downloadedPlugin, testContext.paths.pluginsDir)
|
|
||||||
|
|
||||||
logOutput("Plugin $pluginId setup finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disablePlugins(vararg pluginIds: String) = disablePlugins(pluginIds.toSet())
|
|
||||||
|
|
||||||
fun disablePlugins(pluginIds: Set<String>) = also {
|
|
||||||
disabledPluginsPath.writeLines(disabledPluginIds + pluginIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enablePlugins(vararg pluginIds: String) = enablePlugins(pluginIds.toSet())
|
|
||||||
|
|
||||||
fun enablePlugins(pluginIds: Set<String>) = also {
|
|
||||||
disabledPluginsPath.writeLines(disabledPluginIds - pluginIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
val disabledPluginIds: Set<String>
|
|
||||||
get() {
|
|
||||||
val file = disabledPluginsPath
|
|
||||||
return if (file.exists()) file.readLines().toSet() else emptySet()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun findPluginXmlByPluginIdInAGivenDir(pluginId: String, bundledPluginsDir: Path): Boolean {
|
|
||||||
val jarFiles = bundledPluginsDir.toFile().walk().filter { it.name.endsWith(".jar") }.toList()
|
|
||||||
jarFiles.forEach {
|
|
||||||
val jarFile = JarFile(it)
|
|
||||||
val entry = jarFile.getJarEntry("META-INF/plugin.xml")
|
|
||||||
if (entry != null) {
|
|
||||||
val inputStream = jarFile.getInputStream(entry)
|
|
||||||
val text: String = inputStream.bufferedReader(Charsets.UTF_8).use { reader -> reader.readText() }
|
|
||||||
if (text.contains(" <id>$pluginId</id>")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getPluginInstalledState(pluginId: String): PluginInstalledState {
|
|
||||||
if (disabledPluginsPath.toFile().exists() && pluginId in disabledPluginIds) {
|
|
||||||
return PluginInstalledState.DISABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
val installedPluginDir = testContext.paths.pluginsDir
|
|
||||||
if (findPluginXmlByPluginIdInAGivenDir(pluginId, installedPluginDir)) {
|
|
||||||
return PluginInstalledState.INSTALLED
|
|
||||||
}
|
|
||||||
|
|
||||||
val bundledPluginsDir = testContext.ide.bundledPluginsDir
|
|
||||||
if (bundledPluginsDir == null) {
|
|
||||||
logOutput("Cannot ensure a plugin '$pluginId' is installed in ${testContext.ide}. Consider it is installed.")
|
|
||||||
return PluginInstalledState.INSTALLED
|
|
||||||
}
|
|
||||||
|
|
||||||
if (findPluginXmlByPluginIdInAGivenDir(pluginId, bundledPluginsDir)) {
|
|
||||||
return PluginInstalledState.BUNDLED_TO_IDE
|
|
||||||
}
|
|
||||||
return PluginInstalledState.NOT_INSTALLED
|
|
||||||
}
|
|
||||||
|
|
||||||
fun assertPluginIsInstalled(pluginId: String): PluginConfigurator {
|
|
||||||
when (getPluginInstalledState(pluginId)) {
|
|
||||||
PluginInstalledState.DISABLED -> error("Plugin '$pluginId' must not be listed in the disabled plugins file ${disabledPluginsPath}")
|
|
||||||
PluginInstalledState.NOT_INSTALLED -> error("Plugin '$pluginId' must be installed")
|
|
||||||
PluginInstalledState.BUNDLED_TO_IDE -> return this
|
|
||||||
PluginInstalledState.INSTALLED -> return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.intellij.ide.starter.plugins
|
|
||||||
|
|
||||||
enum class PluginInstalledState {
|
|
||||||
BUNDLED_TO_IDE,
|
|
||||||
INSTALLED,
|
|
||||||
NOT_INSTALLED,
|
|
||||||
DISABLED
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.intellij.ide.starter.plugins
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.intellij.ide.starter.process
|
|
||||||
|
|
||||||
data class LinuxProcessMetaInfo(
|
|
||||||
override val pid: Int,
|
|
||||||
val vsz: Int,
|
|
||||||
val rss: Int,
|
|
||||||
override val command: String
|
|
||||||
) : ProcessMetaInfo(pid, command) {
|
|
||||||
override fun toString() = "$pid (vsz = $vsz) (rss = $rss) $command"
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.intellij.ide.starter.process
|
|
||||||
|
|
||||||
data class MacOsProcessMetaInfo(
|
|
||||||
override val pid: Int,
|
|
||||||
override val command: String
|
|
||||||
) : ProcessMetaInfo(pid, command)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.intellij.ide.starter.process
|
|
||||||
|
|
||||||
abstract class ProcessMetaInfo(
|
|
||||||
open val pid: Int,
|
|
||||||
open val command: String
|
|
||||||
) {
|
|
||||||
override fun toString() = "$pid $command"
|
|
||||||
}
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
package com.intellij.ide.starter.process
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect
|
|
||||||
import com.intellij.ide.starter.exec.exec
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.catchAll
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.isRegularFile
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
// TODO: consider using https://github.com/oshi/oshi for acquiring process info
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TeamCity may not kill processes started during the build (TW-69045).
|
|
||||||
* They stay alive and consume resources after tests.
|
|
||||||
* This lead to OOM and other errors during tests, for example,
|
|
||||||
* IDEA-256265: shared-indexes tests on Linux suspiciously fail with 137 (killed by OOM)
|
|
||||||
*/
|
|
||||||
fun killOutdatedProcessesOnUnix(commandsToSearch: Iterable<String> = listOf("/perf-startup/")) {
|
|
||||||
if (SystemInfo.isWindows) {
|
|
||||||
logOutput("Current system is Windows. No logic for analysis of outdated processes is yet implemented.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val processes = arrayListOf<ProcessMetaInfo>()
|
|
||||||
|
|
||||||
if (SystemInfo.isLinux) catchAll { processes += dumpListOfProcessesOnLinux() }
|
|
||||||
else catchAll { processes += dumpListOfProcessesOnMacOS() }
|
|
||||||
|
|
||||||
val processIdsToKill = processes.filter { process ->
|
|
||||||
commandsToSearch.any { process.command.contains(it) }
|
|
||||||
}.map { it.pid }
|
|
||||||
|
|
||||||
logOutput("These Unix processes must be killed before the next test run: [$processIdsToKill]")
|
|
||||||
for (pid in processIdsToKill) {
|
|
||||||
catchAll { killProcessOnUnix(pid) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dumpListOfProcessesOnMacOS(): List<MacOsProcessMetaInfo> {
|
|
||||||
check(SystemInfo.isMac)
|
|
||||||
val stdoutRedirect = ExecOutputRedirect.ToString()
|
|
||||||
exec("ps",
|
|
||||||
di.direct.instance<GlobalPaths>().testsDirectory,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf("ps", "-ax"),
|
|
||||||
stdoutRedirect = stdoutRedirect)
|
|
||||||
val processLines = stdoutRedirect.read().lines().drop(1).map { it.trim() }.filterNot { it.isBlank() }
|
|
||||||
//PID TTY TIME CMD
|
|
||||||
// 1 ?? 0:43.67 /sbin/launchd
|
|
||||||
val processes = arrayListOf<MacOsProcessMetaInfo>()
|
|
||||||
for (line in processLines) {
|
|
||||||
var rest = line
|
|
||||||
fun nextString(): String {
|
|
||||||
val result = rest.substringBefore(" ").trim()
|
|
||||||
rest = rest.substringAfter(" ").dropWhile { it == ' ' }
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
val pid = nextString().toInt()
|
|
||||||
nextString() //TTY
|
|
||||||
nextString() //TIME
|
|
||||||
val command = rest
|
|
||||||
processes += MacOsProcessMetaInfo(pid, command)
|
|
||||||
}
|
|
||||||
return processes
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dumpListOfProcessesOnLinux(): List<LinuxProcessMetaInfo> {
|
|
||||||
check(SystemInfo.isLinux)
|
|
||||||
val stdoutRedirect = ExecOutputRedirect.ToString()
|
|
||||||
exec("ps",
|
|
||||||
di.direct.instance<GlobalPaths>().testsDirectory,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf("ps", "-aux"),
|
|
||||||
stdoutRedirect = stdoutRedirect)
|
|
||||||
val processLines = stdoutRedirect.read().lines().drop(1).map { it.trim() }.filterNot { it.isBlank() }
|
|
||||||
//USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
||||||
//root 823 0.0 0.0 1576524 8128 ? Ssl дек01 0:08 /usr/bin/containerd
|
|
||||||
val processes = arrayListOf<LinuxProcessMetaInfo>()
|
|
||||||
for (line in processLines) {
|
|
||||||
var rest = line
|
|
||||||
fun nextString(): String {
|
|
||||||
val result = rest.substringBefore(" ").trim()
|
|
||||||
rest = rest.substringAfter(" ").dropWhile { it == ' ' }
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
nextString() //user
|
|
||||||
val pid = nextString().toInt()
|
|
||||||
nextString() //cpu
|
|
||||||
nextString() //mem
|
|
||||||
val vsz = nextString().toInt()
|
|
||||||
val rss = nextString().toInt()
|
|
||||||
nextString() //tty
|
|
||||||
nextString() //stat
|
|
||||||
nextString() //start
|
|
||||||
nextString() //time
|
|
||||||
val command = rest
|
|
||||||
processes += LinuxProcessMetaInfo(pid, vsz, rss, command)
|
|
||||||
}
|
|
||||||
return processes
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun killProcessOnUnix(pid: Int) {
|
|
||||||
check(SystemInfo.isUnix)
|
|
||||||
logOutput("Killing process $pid")
|
|
||||||
|
|
||||||
exec(
|
|
||||||
"kill-process-$pid",
|
|
||||||
di.direct.instance<GlobalPaths>().testsDirectory,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf("kill", "-9", pid.toString()),
|
|
||||||
stdoutRedirect = ExecOutputRedirect.ToStdOut("[kill-$pid-out]"),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("[kill-$pid-err]")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Workaround for IDEA-251643.
|
|
||||||
* On Linux we run IDE using `xvfb-run` tool wrapper.
|
|
||||||
* Thus we must guess the original java process ID for capturing the thread dumps.
|
|
||||||
* TODO: try to use java.lang.ProcessHandle to get the parent process ID.
|
|
||||||
*/
|
|
||||||
fun getJavaProcessId(javaHome: Path, workDir: Path, originalProcessId: Long, originalProcess: Process): Long {
|
|
||||||
if (!SystemInfo.isLinux) {
|
|
||||||
return originalProcessId
|
|
||||||
}
|
|
||||||
logOutput("Guessing java process ID on Linux (pid of the java process wrapper - $originalProcessId)")
|
|
||||||
|
|
||||||
val stdout = ExecOutputRedirect.ToString()
|
|
||||||
val stderr = ExecOutputRedirect.ToString()
|
|
||||||
exec(
|
|
||||||
"jcmd-run",
|
|
||||||
workDir,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = listOf(javaHome.resolve("bin/jcmd").toAbsolutePath().toString()),
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr
|
|
||||||
)
|
|
||||||
val mergedOutput = stdout.read() + "\n" + stderr.read()
|
|
||||||
val candidates = arrayListOf<Long>()
|
|
||||||
val candidatesFromProcessHandle = arrayListOf<Long>()
|
|
||||||
logOutput("List all java processes IDs:")
|
|
||||||
for (line in mergedOutput.lines().map { it.trim() }.filterNot { it.isEmpty() }) {
|
|
||||||
logOutput(line)
|
|
||||||
/*
|
|
||||||
An example of a process line:
|
|
||||||
|
|
||||||
1578401 com.intellij.idea.Main /home/sergey.patrikeev/Documents/intellij/out/perf-startup/tests/IU-211.1852/ijx-jdk-empty/verify-shared-index/temp/projects/idea-startup-performance-project-test-03/idea-startup-performance-project-test-03
|
|
||||||
|
|
||||||
An example from TC:
|
|
||||||
intellij project:
|
|
||||||
81413 com.intellij.idea.Main /opt/teamcity-agent/work/71b862de01f59e23
|
|
||||||
|
|
||||||
another project:
|
|
||||||
84318 com.intellij.idea.Main /opt/teamcity-agent/temp/buildTmp/startupPerformanceTests5985285665047908961/perf-startup/tests/IU-installer-from-file/spring_boot/indexing_oldProjectModel/projects/projects/spring-boot-master/spring-boot-master
|
|
||||||
|
|
||||||
An example from TC TestsDynamicBundledPluginsStableLinux
|
|
||||||
1879942 com.intellij.idea.Main /opt/teamcity-agent/temp/buildTmp/startupPerformanceTests4436006118811351792/perf-startup/cache/projects/unpacked/javaproject_1.0.0/java-design-patterns-master
|
|
||||||
*/
|
|
||||||
|
|
||||||
//TODO(Monitor case /opt/teamcity-agent/work/)
|
|
||||||
val pid = line.substringBefore(" ", "").toLongOrNull() ?: continue
|
|
||||||
if (line.contains("com.intellij.idea.Main") && (line.contains("/perf-startup/tests/") || line.contains(
|
|
||||||
"/perf-startup/cache/") || line.contains("/opt/teamcity-agent/work/"))) {
|
|
||||||
candidates.add(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
originalProcess.toHandle().descendants().forEach { desc ->
|
|
||||||
if (desc.info().command().get().contains("java")) {
|
|
||||||
logOutput("Candidate from ProcessHandle process: ${desc.pid()}")
|
|
||||||
logOutput("command: ${desc.info().command()}")
|
|
||||||
candidatesFromProcessHandle.add(desc.pid())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidates.isEmpty() && candidatesFromProcessHandle.isNotEmpty()) {
|
|
||||||
logOutput("Candidates from jcmd are missing, will be used first one from ProcessHandle instead: " + candidatesFromProcessHandle.first())
|
|
||||||
candidates.add(candidatesFromProcessHandle.first())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidates.isNotEmpty()) {
|
|
||||||
logOutput("Found the following java process ID candidates: " + candidates.joinToString())
|
|
||||||
if (originalProcessId in candidates) {
|
|
||||||
return originalProcessId
|
|
||||||
}
|
|
||||||
return candidates.first()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return originalProcessId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectJavaThreadDump(
|
|
||||||
javaHome: Path,
|
|
||||||
workDir: Path,
|
|
||||||
javaProcessId: Long,
|
|
||||||
dumpFile: Path,
|
|
||||||
includeStdout: Boolean = true
|
|
||||||
) {
|
|
||||||
val ext = if (SystemInfo.isWindows) ".exe" else ""
|
|
||||||
val jstackPath = listOf(
|
|
||||||
javaHome.resolve("bin/jstack$ext"),
|
|
||||||
javaHome.parent.resolve("bin/jstack$ext")
|
|
||||||
).map { it.toAbsolutePath() }.firstOrNull { it.isRegularFile() } ?: error("Failed to locate jstack under $javaHome")
|
|
||||||
|
|
||||||
val command = listOf(jstackPath.toAbsolutePath().toString(), "-l", javaProcessId.toString())
|
|
||||||
|
|
||||||
exec(
|
|
||||||
"jstack",
|
|
||||||
workDir,
|
|
||||||
timeout = 1.minutes,
|
|
||||||
args = command,
|
|
||||||
stdoutRedirect = ExecOutputRedirect.ToFile(dumpFile.toFile()),
|
|
||||||
stderrRedirect = ExecOutputRedirect.ToStdOut("[jstack-err]")
|
|
||||||
)
|
|
||||||
|
|
||||||
if (includeStdout) {
|
|
||||||
logOutput("jstack output:\n${dumpFile.toFile().readLines().joinToString("\n")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroyGradleDaemonProcessIfExists() {
|
|
||||||
val stdout = ExecOutputRedirect.ToString()
|
|
||||||
exec(
|
|
||||||
"get jps process",
|
|
||||||
workDir = null,
|
|
||||||
timeout = 30.seconds,
|
|
||||||
args = listOf("jps", "-l"),
|
|
||||||
stdoutRedirect = stdout
|
|
||||||
)
|
|
||||||
logOutput("List of java processes: " + stdout.read())
|
|
||||||
|
|
||||||
if (stdout.read().contains("GradleDaemon")) {
|
|
||||||
val readLines = stdout.read().split('\n')
|
|
||||||
readLines.forEach {
|
|
||||||
if (it.contains("GradleDaemon")) {
|
|
||||||
logOutput("Killing GradleDaemon process")
|
|
||||||
val processId = it.split(" ").first().toLong()
|
|
||||||
|
|
||||||
// get up-to date process list on every iteration
|
|
||||||
ProcessHandle.allProcesses()
|
|
||||||
.filter { ph -> ph.isAlive && ph.pid() == processId }
|
|
||||||
.forEach { ph -> ph.destroy() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.intellij.ide.starter.profiler
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.runner.IDERunContext
|
|
||||||
|
|
||||||
abstract class ProfilerInjector(val type: ProfilerType) {
|
|
||||||
abstract fun injectProfiler(runContext: IDERunContext): IDERunContext
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.intellij.ide.starter.profiler
|
|
||||||
|
|
||||||
enum class ProfilerType(val kind: String) {
|
|
||||||
YOURKIT("YOURKIT"),
|
|
||||||
ASYNC("ASYNC"),
|
|
||||||
NONE("NONE");
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package com.intellij.ide.starter.project
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem
|
|
||||||
import com.intellij.ide.starter.utils.FileSystem.isDirUpToDate
|
|
||||||
import com.intellij.ide.starter.utils.HttpClient
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.isDirectory
|
|
||||||
import kotlin.io.path.isRegularFile
|
|
||||||
|
|
||||||
interface ProjectInfoSpec {
|
|
||||||
fun downloadAndUnpackProject(): Path?
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ProjectInfo(
|
|
||||||
val testProjectURL: String? = null,
|
|
||||||
val testProjectDir: Path? = null,
|
|
||||||
val isReusable: Boolean = true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* can be either .zip/.tar.gz file or a directory.
|
|
||||||
* It will be copied to a temp path before test is run
|
|
||||||
*/
|
|
||||||
val testProjectImage: Path? = null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* relative path inside Image file, where project home is located
|
|
||||||
*/
|
|
||||||
val testProjectImageRelPath: (Path) -> Path = { it / (testProjectURL?.split("/")?.last()?.split(".zip")?.first() ?: "") }
|
|
||||||
) : ProjectInfoSpec {
|
|
||||||
init {
|
|
||||||
require(listOfNotNull(testProjectURL, testProjectDir, testProjectImage).size <= 1) {
|
|
||||||
"Only one of 'testProjectURL', 'testProjectDir' or 'testProjectImage' must be specified or none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun downloadAndUnpackProject(): Path? {
|
|
||||||
if (testProjectImage == null && testProjectDir == null && testProjectURL == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testProjectDir != null && !testProjectDir.toFile().exists()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testProjectDir != null) {
|
|
||||||
require(testProjectURL == null)
|
|
||||||
return testProjectDir.toAbsolutePath()
|
|
||||||
}
|
|
||||||
val globalPaths by di.instance<GlobalPaths>()
|
|
||||||
|
|
||||||
val projectsUnpacked = globalPaths.getCacheDirectoryFor("projects").resolve("unpacked")
|
|
||||||
val projectHome = projectsUnpacked.let(testProjectImageRelPath)
|
|
||||||
|
|
||||||
if (!isReusable) {
|
|
||||||
projectHome.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectHome.isDirUpToDate()) {
|
|
||||||
logOutput("Already unpacked project $projectHome will be used in the test")
|
|
||||||
return projectHome
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
projectHome.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
val imagePath: Path = if (testProjectURL != null) {
|
|
||||||
val zipFile = when (testProjectURL.contains("https://github.com")) {
|
|
||||||
true -> globalPaths.getCacheDirectoryFor("projects").resolve("zip").resolve("${projectHome.toString().split("/").last()}.zip")
|
|
||||||
false -> globalPaths.getCacheDirectoryFor("projects").resolve("zip").resolve(testProjectURL.toString().split("/").last())
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpClient.downloadIfMissing(testProjectURL, zipFile)
|
|
||||||
zipFile
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
testProjectImage ?: error("Either testProjectImage or testProjectURL must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
imagePath.isRegularFile() -> FileSystem.unpack(imagePath, projectsUnpacked)
|
|
||||||
imagePath.isDirectory() -> imagePath.toFile().copyRecursively(projectsUnpacked.toFile(), overwrite = true)
|
|
||||||
|
|
||||||
else -> error("$imagePath does not exist!")
|
|
||||||
}
|
|
||||||
return projectHome
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package com.intellij.ide.starter.report
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.utils.convertToHashCodeWithOnlyLetters
|
|
||||||
import com.intellij.ide.starter.utils.generifyErrorMessage
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.isDirectory
|
|
||||||
import kotlin.io.path.listDirectoryEntries
|
|
||||||
|
|
||||||
object ErrorReporter {
|
|
||||||
private const val MAX_TEST_NAME_LENGTH = 250
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort things out from errors directories, written by performance testing plugin
|
|
||||||
* Take a look at [com.jetbrains.performancePlugin.ProjectLoaded.reportErrorsFromMessagePool]
|
|
||||||
*/
|
|
||||||
fun reportErrorsAsFailedTests(scriptErrorsDir: Path, contextName: String): List<Pair<File, File>> {
|
|
||||||
return if (scriptErrorsDir.isDirectory()) {
|
|
||||||
val errorsDirectories = scriptErrorsDir.listDirectoryEntries()
|
|
||||||
|
|
||||||
errorsDirectories.map { errorDir ->
|
|
||||||
val messageFile = errorDir.resolve("message.txt").toFile()
|
|
||||||
val stacktraceFile = errorDir.resolve("stacktrace.txt").toFile()
|
|
||||||
|
|
||||||
if (messageFile.exists() && stacktraceFile.exists()) {
|
|
||||||
val messageText = generifyErrorMessage(messageFile.readText().trim())
|
|
||||||
val stackTraceContent = stacktraceFile.readText().trim()
|
|
||||||
|
|
||||||
var testName: String
|
|
||||||
|
|
||||||
val onlyLettersHash = convertToHashCodeWithOnlyLetters(generifyErrorMessage(stackTraceContent).hashCode())
|
|
||||||
|
|
||||||
if (stackTraceContent.startsWith(messageText)) {
|
|
||||||
val maxLength = (MAX_TEST_NAME_LENGTH - onlyLettersHash.length).coerceAtMost(stackTraceContent.length)
|
|
||||||
val extractedTestName = stackTraceContent.substring(0, maxLength).trim()
|
|
||||||
testName = "($onlyLettersHash $extractedTestName)"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
testName = "($onlyLettersHash ${messageText.substring(0, MAX_TEST_NAME_LENGTH.coerceAtMost(messageText.length)).trim()})"
|
|
||||||
}
|
|
||||||
|
|
||||||
val stackTrace = """
|
|
||||||
Test: $contextName
|
|
||||||
|
|
||||||
$stackTraceContent
|
|
||||||
""".trimIndent().trimMargin().trim()
|
|
||||||
|
|
||||||
di.direct.instance<CIServer>().reportTestFailure(generifyErrorMessage(testName), messageText, stackTrace)
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair(messageFile, stacktraceFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else listOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
||||||
package com.intellij.ide.starter.report.publisher
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IDEStartResult
|
|
||||||
|
|
||||||
interface ReportPublisher {
|
|
||||||
fun publish(ideStartResult: IDEStartResult)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.intellij.ide.starter.report.publisher.impl
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IDEStartResult
|
|
||||||
import com.intellij.ide.starter.report.publisher.ReportPublisher
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
|
|
||||||
object ConsoleTestResultPublisher : ReportPublisher {
|
|
||||||
|
|
||||||
override fun publish(ideStartResult: IDEStartResult) {
|
|
||||||
logOutput("Console publisher")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
||||||
package com.intellij.ide.starter.report.publisher.impl
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IDEStartResult
|
|
||||||
import com.intellij.ide.starter.report.publisher.ReportPublisher
|
|
||||||
import com.intellij.ide.starter.report.qodana.QodanaClient
|
|
||||||
import com.intellij.ide.starter.report.sarif.TestContextToQodanaSarifMapper
|
|
||||||
|
|
||||||
object QodanaTestResultPublisher : ReportPublisher {
|
|
||||||
|
|
||||||
override fun publish(ideStartResult: IDEStartResult) {
|
|
||||||
QodanaClient.report(TestContextToQodanaSarifMapper.map(ideStartResult))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
||||||
package com.intellij.ide.starter.report.qodana
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import com.jetbrains.qodana.sarif.model.SarifReport
|
|
||||||
|
|
||||||
object QodanaClient {
|
|
||||||
|
|
||||||
fun report(sarifReport: SarifReport) {
|
|
||||||
logOutput("QodanaClient report")
|
|
||||||
//TODO("Implement client")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
||||||
package com.intellij.ide.starter.report.sarif
|
|
||||||
|
|
||||||
import com.jetbrains.qodana.sarif.model.*
|
|
||||||
|
|
||||||
fun sarifReport(sarif: SarifReport, init: SarifReport.() -> Unit): SarifReport {
|
|
||||||
sarif.init()
|
|
||||||
return sarif
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sarifRun(run: Run, init: Run.() -> Unit): Run {
|
|
||||||
run.init()
|
|
||||||
return run
|
|
||||||
}
|
|
||||||
|
|
||||||
fun driver(driver: ToolComponent, init: ToolComponent.() -> Unit): ToolComponent {
|
|
||||||
driver.init()
|
|
||||||
return driver
|
|
||||||
}
|
|
||||||
|
|
||||||
fun taxa(init: ReportingDescriptor.() -> Unit): ReportingDescriptor {
|
|
||||||
val taxa = ReportingDescriptor()
|
|
||||||
taxa.init()
|
|
||||||
return taxa
|
|
||||||
}
|
|
||||||
fun invocation(init: Invocation.() -> Unit): Invocation {
|
|
||||||
val invocation = Invocation()
|
|
||||||
invocation.init()
|
|
||||||
return invocation
|
|
||||||
}
|
|
||||||
|
|
||||||
fun result(init: Result.() -> Unit): Result {
|
|
||||||
val result = Result()
|
|
||||||
result.init()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun result(result: Result, init: Result.() -> Unit): Result {
|
|
||||||
result.init()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun versionControlProvenance(init: VersionControlDetails.() -> Unit): VersionControlDetails {
|
|
||||||
val versionControl = VersionControlDetails()
|
|
||||||
versionControl.init()
|
|
||||||
return versionControl
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
||||||
package com.intellij.ide.starter.report.sarif
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.models.IDEStartResult
|
|
||||||
import com.jetbrains.qodana.sarif.SarifUtil
|
|
||||||
import com.jetbrains.qodana.sarif.model.SarifReport
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
object TestContextToQodanaSarifMapper {
|
|
||||||
|
|
||||||
fun map(ideStartResult: IDEStartResult): SarifReport {
|
|
||||||
|
|
||||||
val defaultReportPath = this::class.java.classLoader.getResource("sarif/qodana.sarif.json")?.path
|
|
||||||
if(defaultReportPath==null) throw RuntimeException("Default report doesn' exits")
|
|
||||||
val sarifReport = SarifUtil.readReport(Path.of(defaultReportPath))
|
|
||||||
|
|
||||||
return sarifReport(sarifReport) {
|
|
||||||
sarifRun(runs[0]) {
|
|
||||||
driver(tool.driver) {
|
|
||||||
|
|
||||||
taxa.add(taxa {
|
|
||||||
id = ideStartResult.context.testName
|
|
||||||
name = ideStartResult.context.testName
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
invocations.add(invocation {
|
|
||||||
exitCode = if (ideStartResult.failureError == null) 0 else 1
|
|
||||||
executionSuccessful = ideStartResult.failureError == null
|
|
||||||
})
|
|
||||||
|
|
||||||
versionControlProvenance.add(versionControlProvenance {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
results.add(result {
|
|
||||||
ruleId = "Perf test"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.intellij.ide.starter.runner
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.ide.PlainCodeInjector
|
|
||||||
import kotlin.io.path.createDirectories
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
import kotlin.io.path.writeText
|
|
||||||
|
|
||||||
open class CodeBuilderHost : PlainCodeInjector {
|
|
||||||
private var codeToRun: String? = null
|
|
||||||
|
|
||||||
override fun addGroovyCode(code: String) {
|
|
||||||
require(codeToRun == null) { "Only one code block is supported" }
|
|
||||||
codeToRun = code
|
|
||||||
}
|
|
||||||
|
|
||||||
private val IDETestContext.targetFile
|
|
||||||
get() = paths.configDir.resolve("extensions/com.intellij/startup/init.groovy")
|
|
||||||
|
|
||||||
override fun setup(context: IDETestContext) {
|
|
||||||
val code = codeToRun
|
|
||||||
if (code != null) {
|
|
||||||
context.targetFile.apply {
|
|
||||||
parent.createDirectories()
|
|
||||||
writeText(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tearDown(context: IDETestContext) {
|
|
||||||
context.targetFile.deleteIfExists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.intellij.ide.starter.runner
|
|
||||||
|
|
||||||
sealed class IDECommandLine {
|
|
||||||
data class Args(val args: List<String>) : IDECommandLine()
|
|
||||||
object OpenTestCaseProject : IDECommandLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun args(vararg args: String) = IDECommandLine.Args(args.toList())
|
|
||||||
fun args(args: List<String>) = IDECommandLine.Args(args.toList())
|
|
||||||
operator fun IDECommandLine.Args.plus(params: List<String>) = copy(args = this.args + params)
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
package com.intellij.ide.starter.runner
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.bus.EventState
|
|
||||||
import com.intellij.ide.starter.bus.StarterBus
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.exec.ExecOutputRedirect
|
|
||||||
import com.intellij.ide.starter.exec.ExecTimeoutException
|
|
||||||
import com.intellij.ide.starter.exec.exec
|
|
||||||
import com.intellij.ide.starter.ide.CodeInjector
|
|
||||||
import com.intellij.ide.starter.ide.IDETestContext
|
|
||||||
import com.intellij.ide.starter.ide.command.MarshallableCommand
|
|
||||||
import com.intellij.ide.starter.models.IDEStartResult
|
|
||||||
import com.intellij.ide.starter.models.VMOptions
|
|
||||||
import com.intellij.ide.starter.models.andThen
|
|
||||||
import com.intellij.ide.starter.process.collectJavaThreadDump
|
|
||||||
import com.intellij.ide.starter.process.destroyGradleDaemonProcessIfExists
|
|
||||||
import com.intellij.ide.starter.process.getJavaProcessId
|
|
||||||
import com.intellij.ide.starter.profiler.ProfilerInjector
|
|
||||||
import com.intellij.ide.starter.profiler.ProfilerType
|
|
||||||
import com.intellij.ide.starter.report.ErrorReporter
|
|
||||||
import com.intellij.ide.starter.system.SystemInfo
|
|
||||||
import com.intellij.ide.starter.utils.*
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.io.path.*
|
|
||||||
import kotlin.streams.toList
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
import kotlin.time.measureTime
|
|
||||||
|
|
||||||
interface IDERunCloseContext {
|
|
||||||
val wasRunSuccessful: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
data class IDERunContext(
|
|
||||||
val testContext: IDETestContext,
|
|
||||||
val patchVMOptions: VMOptions.() -> VMOptions = { this },
|
|
||||||
val commandLine: IDECommandLine? = null,
|
|
||||||
val commands: Iterable<MarshallableCommand> = listOf(),
|
|
||||||
val codeBuilder: (CodeInjector.() -> Unit)? = null,
|
|
||||||
val runTimeout: Duration = 10.minutes,
|
|
||||||
val traceStacksEvery: Duration = 10.minutes,
|
|
||||||
val useStartupScript: Boolean = true,
|
|
||||||
val closeHandlers: List<IDERunCloseContext.() -> Unit> = listOf(),
|
|
||||||
val verboseOutput: Boolean = false,
|
|
||||||
val launchName: String = "",
|
|
||||||
val expectedKill: Boolean = false,
|
|
||||||
val collectNativeThreads: Boolean = false
|
|
||||||
) {
|
|
||||||
val contextName: String
|
|
||||||
get() = if (launchName.isNotEmpty()) {
|
|
||||||
"${testContext.testName}/$launchName"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
testContext.testName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun verbose() = copy(verboseOutput = true)
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun withVMOptions(patchVMOptions: VMOptions.() -> VMOptions) = addVMOptionsPatch(patchVMOptions)
|
|
||||||
|
|
||||||
fun addVMOptionsPatch(patchVMOptions: VMOptions.() -> VMOptions) = copy(
|
|
||||||
patchVMOptions = this.patchVMOptions.andThen(patchVMOptions)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun addCompletionHandler(handler: IDERunCloseContext.() -> Unit) = this.copy(closeHandlers = closeHandlers + handler)
|
|
||||||
|
|
||||||
fun uploadProfileResultsToTeamCity(profilerSnapshotsDir: Path, artifactName: String) =
|
|
||||||
this.addCompletionHandler {
|
|
||||||
testContext.publishArtifact(source = profilerSnapshotsDir, artifactName = artifactName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun installProfiler(): IDERunContext {
|
|
||||||
return when (val profilerType = testContext.profilerType) {
|
|
||||||
ProfilerType.ASYNC, ProfilerType.YOURKIT -> {
|
|
||||||
val profiler = di.direct.instance<ProfilerInjector>(tag = profilerType)
|
|
||||||
logOutput("Injecting profiler ${profiler.type.kind}")
|
|
||||||
profiler.injectProfiler(this)
|
|
||||||
}
|
|
||||||
ProfilerType.NONE -> {
|
|
||||||
logOutput("No profiler is specified. Skipping profiler setup")
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor this
|
|
||||||
private fun prepareToRunIDE(): IDEStartResult {
|
|
||||||
StarterBus.post(IdeLaunchEvent(EventState.BEFORE, this))
|
|
||||||
|
|
||||||
deleteSavedAppStateOnMac()
|
|
||||||
val paths = testContext.paths
|
|
||||||
val logsDir = paths.logsDir.createDirectories()
|
|
||||||
paths.snapshotsDir.createDirectories()
|
|
||||||
val jvmCrashLogDirectory = logsDir.resolve("jvm-crash").createDirectories()
|
|
||||||
val heapDumpOnOomDirectory = logsDir.resolve("heap-dump").createDirectories()
|
|
||||||
val disabledPlugins = paths.configDir.resolve("disabled_plugins.txt")
|
|
||||||
if (disabledPlugins.toFile().exists()) {
|
|
||||||
logOutput("The list of disabled plugins: " + disabledPlugins.toFile().readText())
|
|
||||||
}
|
|
||||||
|
|
||||||
val stdout = if (verboseOutput) ExecOutputRedirect.ToStdOut("[ide-${contextName}-out]") else ExecOutputRedirect.ToString()
|
|
||||||
val stderr = ExecOutputRedirect.ToStdOut("[ide-${contextName}-err]")
|
|
||||||
|
|
||||||
var successfulRun = true
|
|
||||||
val host by lazy { di.direct.instance<CodeInjector>() }
|
|
||||||
|
|
||||||
try {
|
|
||||||
val codeBuilder = codeBuilder
|
|
||||||
if (codeBuilder != null) {
|
|
||||||
host.codeBuilder()
|
|
||||||
}
|
|
||||||
|
|
||||||
val finalOptions = testContext.ide.originalVMOptions
|
|
||||||
.disableStartupDialogs()
|
|
||||||
.usingStartupFramework()
|
|
||||||
.setFatalErrorNotificationEnabled()
|
|
||||||
.setFlagIntegrationTests()
|
|
||||||
.takeScreenshotIfFailure(logsDir)
|
|
||||||
.withJvmCrashLogDirectory(jvmCrashLogDirectory)
|
|
||||||
.withHeapDumpOnOutOfMemoryDirectory(heapDumpOnOomDirectory)
|
|
||||||
.let { testContext.testCase.vmOptionsFix(it) }
|
|
||||||
.let { testContext.patchVMOptions(it) }
|
|
||||||
.patchVMOptions()
|
|
||||||
.let {
|
|
||||||
if (!useStartupScript) {
|
|
||||||
require(commands.count() > 0) { "script builder is not allowed when useStartupScript is disabled" }
|
|
||||||
it
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it.installTestScript(contextName, paths, commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeBuilder != null) {
|
|
||||||
host.setup(testContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
testContext.setProviderMemoryOnlyOnLinux()
|
|
||||||
|
|
||||||
val jdkHome: Path by lazy {
|
|
||||||
try {
|
|
||||||
testContext.ide.resolveAndDownloadTheSameJDK()
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
logError("Failed to download the same JDK as in ${testContext.ide.build}")
|
|
||||||
logError(e.stackTraceToString())
|
|
||||||
|
|
||||||
val defaultJavaHome = resolveInstalledJdk11()
|
|
||||||
logOutput("JDK is not found in ${testContext.ide.build}. Fallback to default java: $defaultJavaHome")
|
|
||||||
defaultJavaHome
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val startConfig = testContext.ide.startConfig(finalOptions, logsDir)
|
|
||||||
if (startConfig is Closeable) {
|
|
||||||
addCompletionHandler { startConfig.close() }
|
|
||||||
}
|
|
||||||
|
|
||||||
val commandLineArgs = when (val cmd = commandLine ?: IDECommandLine.OpenTestCaseProject) {
|
|
||||||
is IDECommandLine.Args -> cmd.args
|
|
||||||
is IDECommandLine.OpenTestCaseProject -> listOf(testContext.resolvedProjectHome.toAbsolutePath().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
val finalEnvVariables = startConfig.environmentVariables + finalOptions.env
|
|
||||||
val extendedEnvVariablesWithJavaHome = finalEnvVariables.toMutableMap()
|
|
||||||
extendedEnvVariablesWithJavaHome.putIfAbsent("JAVA_HOME", jdkHome.absolutePathString())
|
|
||||||
val finalArgs = startConfig.commandLine + commandLineArgs
|
|
||||||
logOutput(buildString {
|
|
||||||
appendLine("Starting IDE for ${contextName} with timeout $runTimeout")
|
|
||||||
appendLine(" Command line: [" + finalArgs.joinToString() + "]")
|
|
||||||
appendLine(" VM Options: [" + finalOptions.toString().lineSequence().map { it.trim() }.joinToString(" ") + "]")
|
|
||||||
appendLine(" On Java : [" + System.getProperty("java.home") + "]")
|
|
||||||
})
|
|
||||||
|
|
||||||
File(finalArgs.first()).setExecutable(true)
|
|
||||||
|
|
||||||
val executionTime = measureTime {
|
|
||||||
exec(
|
|
||||||
presentablePurpose = "run-ide-$contextName",
|
|
||||||
workDir = startConfig.workDir,
|
|
||||||
environmentVariables = extendedEnvVariablesWithJavaHome,
|
|
||||||
timeout = runTimeout,
|
|
||||||
args = finalArgs,
|
|
||||||
errorDiagnosticFiles = startConfig.errorDiagnosticFiles,
|
|
||||||
stdoutRedirect = stdout,
|
|
||||||
stderrRedirect = stderr,
|
|
||||||
onProcessCreated = { process, pid ->
|
|
||||||
val javaProcessId by lazy { getJavaProcessId(jdkHome, startConfig.workDir, pid, process) }
|
|
||||||
thread(name = "jstack-${testContext.testName}", isDaemon = true) {
|
|
||||||
var cnt = 0
|
|
||||||
while (process.isAlive) {
|
|
||||||
Thread.sleep(traceStacksEvery.inWholeMilliseconds)
|
|
||||||
if (!process.isAlive) break
|
|
||||||
|
|
||||||
val dumpFile = logsDir.resolve("threadDump-${++cnt}-${System.currentTimeMillis()}" + ".txt")
|
|
||||||
logOutput("Dumping threads to $dumpFile")
|
|
||||||
logOutput(Runtime.getRuntime().getRuntimeInfo())
|
|
||||||
catchAll { collectJavaThreadDump(jdkHome, startConfig.workDir, javaProcessId, dumpFile, false) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBeforeKilled = { process, pid ->
|
|
||||||
if (!expectedKill) {
|
|
||||||
val javaProcessId by lazy { getJavaProcessId(jdkHome, startConfig.workDir, pid, process) }
|
|
||||||
if (collectNativeThreads) {
|
|
||||||
val fileToStoreNativeThreads = logsDir.resolve("native-thread-dumps.txt")
|
|
||||||
startProfileNativeThreads(javaProcessId.toString())
|
|
||||||
Thread.sleep(15.seconds.inWholeMilliseconds)
|
|
||||||
stopProfileNativeThreads(javaProcessId.toString(), fileToStoreNativeThreads.toAbsolutePath().toString())
|
|
||||||
}
|
|
||||||
val dumpFile = logsDir.resolve("threadDump-before-kill-${System.currentTimeMillis()}" + ".txt")
|
|
||||||
catchAll { collectJavaThreadDump(jdkHome, startConfig.workDir, javaProcessId, dumpFile) }
|
|
||||||
}
|
|
||||||
takeScreenshot(logsDir)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
logOutput("IDE run $contextName completed in $executionTime")
|
|
||||||
|
|
||||||
require(FileSystem.countFiles(paths.configDir) > 3) {
|
|
||||||
"IDE must have created files under config directory at ${paths.configDir}. Were .vmoptions included correctly?"
|
|
||||||
}
|
|
||||||
|
|
||||||
require(FileSystem.countFiles(paths.systemDir) > 1) {
|
|
||||||
"IDE must have created files under system directory at ${paths.systemDir}. Were .vmoptions included correctly?"
|
|
||||||
}
|
|
||||||
|
|
||||||
val vmOptionsDiff = startConfig.vmOptionsDiff()
|
|
||||||
|
|
||||||
if (vmOptionsDiff != null && !vmOptionsDiff.isEmpty) {
|
|
||||||
logOutput("VMOptions were changed:")
|
|
||||||
logOutput("new lines:")
|
|
||||||
vmOptionsDiff.newLines.forEach { logOutput(" $it") }
|
|
||||||
logOutput("removed lines:")
|
|
||||||
vmOptionsDiff.missingLines.forEach { logOutput(" $it") }
|
|
||||||
logOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDEStartResult(runContext = this, executionTime = executionTime, vmOptionsDiff = vmOptionsDiff, logsDir = logsDir)
|
|
||||||
}
|
|
||||||
catch (t: Throwable) {
|
|
||||||
successfulRun = false
|
|
||||||
if (t is ExecTimeoutException && !expectedKill) {
|
|
||||||
error("Timeout of IDE run $contextName for $runTimeout")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logOutput("IDE run for $contextName has been expected to be killed after $runTimeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
val failureCauseFile = testContext.paths.logsDir.resolve("failure_cause.txt")
|
|
||||||
val errorMessage = if (Files.exists(failureCauseFile)) {
|
|
||||||
Files.readString(failureCauseFile)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
t.message ?: t.javaClass.name
|
|
||||||
}
|
|
||||||
if (!expectedKill) {
|
|
||||||
throw Exception(errorMessage, t)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return IDEStartResult(runContext = this, executionTime = runTimeout, logsDir = logsDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (SystemInfo.isWindows) {
|
|
||||||
destroyGradleDaemonProcessIfExists()
|
|
||||||
}
|
|
||||||
|
|
||||||
listOf(heapDumpOnOomDirectory, jvmCrashLogDirectory).filter { dir ->
|
|
||||||
dir.listDirectoryEntries().isEmpty()
|
|
||||||
}.forEach { it.toFile().deleteRecursively() }
|
|
||||||
|
|
||||||
ErrorReporter.reportErrorsAsFailedTests(logsDir / "script-errors", contextName)
|
|
||||||
val (artifactPath, artifactName) = if (successfulRun) contextName to "logs" else "run/$contextName" to "crash"
|
|
||||||
testContext.publishArtifact(logsDir, artifactPath, formatArtifactName(artifactName, testContext.testName))
|
|
||||||
val snapshotFiles = Files.list(testContext.paths.snapshotsDir).use { it.filter { it.isRegularFile() }.toList() }
|
|
||||||
if (snapshotFiles.isNotEmpty()) {
|
|
||||||
testContext.publishArtifact(testContext.paths.snapshotsDir, contextName, formatArtifactName("snapshots", testContext.testName))
|
|
||||||
}
|
|
||||||
if (codeBuilder != null) {
|
|
||||||
host.tearDown(testContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
val closeContext = object : IDERunCloseContext {
|
|
||||||
override val wasRunSuccessful: Boolean = successfulRun
|
|
||||||
}
|
|
||||||
|
|
||||||
closeHandlers.forEach {
|
|
||||||
try {
|
|
||||||
it.invoke(closeContext)
|
|
||||||
}
|
|
||||||
catch (t: Throwable) {
|
|
||||||
logOutput("Failed to complete close step. ${t.message}.\n" + t)
|
|
||||||
t.printStackTrace(System.err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
StarterBus.post(IdeLaunchEvent(EventState.AFTER, this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runIDE(): IDEStartResult {
|
|
||||||
return installProfiler()
|
|
||||||
.prepareToRunIDE()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteSavedAppStateOnMac() {
|
|
||||||
if (SystemInfo.isMac) {
|
|
||||||
val filesToBeDeleted = listOf(
|
|
||||||
"com.jetbrains.${testContext.testCase.ideInfo.installerProductName}-EAP.savedState",
|
|
||||||
"com.jetbrains.${testContext.testCase.ideInfo.installerProductName}.savedState"
|
|
||||||
)
|
|
||||||
val home = System.getProperty("user.home")
|
|
||||||
val savedAppStateDir = Paths.get(home).resolve("Library").resolve("Saved Application State")
|
|
||||||
savedAppStateDir.toFile()
|
|
||||||
.walkTopDown().maxDepth(1)
|
|
||||||
.filter { file -> filesToBeDeleted.any { fileToBeDeleted -> file.name == fileToBeDeleted } }
|
|
||||||
.forEach { it.deleteRecursively() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.intellij.ide.starter.runner
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.bus.Event
|
|
||||||
import com.intellij.ide.starter.bus.EventState
|
|
||||||
|
|
||||||
class IdeLaunchEvent(state: EventState, runContext: IDERunContext) : Event<IDERunContext>(state, runContext)
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package com.intellij.ide.starter.runner
|
|
||||||
|
|
||||||
import com.intellij.ide.starter.ci.CIServer
|
|
||||||
import com.intellij.ide.starter.di.di
|
|
||||||
import com.intellij.ide.starter.ide.*
|
|
||||||
import com.intellij.ide.starter.models.IdeInfo
|
|
||||||
import com.intellij.ide.starter.models.TestCase
|
|
||||||
import com.intellij.ide.starter.path.GlobalPaths
|
|
||||||
import com.intellij.ide.starter.path.IDEDataPaths
|
|
||||||
import com.intellij.ide.starter.plugins.PluginInstalledState
|
|
||||||
import com.intellij.ide.starter.utils.catchAll
|
|
||||||
import com.intellij.ide.starter.utils.logOutput
|
|
||||||
import org.kodein.di.direct
|
|
||||||
import org.kodein.di.factory
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import java.io.Closeable
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ciServer] - use [NoCIServer] for only local run. Otherwise - pass implementation of CIServer
|
|
||||||
*/
|
|
||||||
interface TestContainer<T> : Closeable {
|
|
||||||
val ciServer: CIServer
|
|
||||||
var useLatestDownloadedIdeBuild: Boolean
|
|
||||||
val allContexts: MutableList<IDETestContext>
|
|
||||||
val setupHooks: MutableList<IDETestContext.() -> IDETestContext>
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
for (context in allContexts) {
|
|
||||||
catchAll { context.paths.close() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows to apply the common configuration to all created IDETestContext instances
|
|
||||||
*/
|
|
||||||
fun withSetupHook(hook: IDETestContext.() -> IDETestContext): T = apply {
|
|
||||||
setupHooks += hook
|
|
||||||
} as T
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the test use the latest available locally IDE build for testing.
|
|
||||||
*/
|
|
||||||
fun useLatestDownloadedIdeBuild(): T = apply {
|
|
||||||
assert(!ciServer.isBuildRunningOnCI)
|
|
||||||
useLatestDownloadedIdeBuild = true
|
|
||||||
} as T
|
|
||||||
|
|
||||||
fun resolveIDE(ideInfo: IdeInfo): Pair<String, InstalledIde> {
|
|
||||||
return di.direct.factory<IdeInfo, IdeInstallator>().invoke(ideInfo).install(ideInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun installPerformanceTestingPluginIfMissing(context: IDETestContext) {
|
|
||||||
val performancePluginId = "com.jetbrains.performancePlugin"
|
|
||||||
|
|
||||||
context.pluginConfigurator.apply {
|
|
||||||
val pluginState = getPluginInstalledState(performancePluginId)
|
|
||||||
if (pluginState != PluginInstalledState.INSTALLED && pluginState != PluginInstalledState.BUNDLED_TO_IDE)
|
|
||||||
setupPluginFromPluginManager(performancePluginId, ideBuild = context.ide.build)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Starting point to run your test */
|
|
||||||
fun initializeTestRunner(testName: String, testCase: TestCase): IDETestContext {
|
|
||||||
check(allContexts.none { it.testName == testName }) { "Test $testName is already initialized. Use another name." }
|
|
||||||
logOutput("Resolving IDE build for $testName...")
|
|
||||||
|
|
||||||
val (buildNumber, ide) = resolveIDE(testCase.ideInfo)
|
|
||||||
|
|
||||||
require(ide.productCode == testCase.ideInfo.productCode) { "Product code of $ide must be the same as for $testCase" }
|
|
||||||
|
|
||||||
val testDirectory = (di.direct.instance<GlobalPaths>().testsDirectory / "${testCase.ideInfo.productCode}-$buildNumber") / testName
|
|
||||||
|
|
||||||
val paths = IDEDataPaths.createPaths(testName, testDirectory, testCase.useInMemoryFileSystem)
|
|
||||||
logOutput("Using IDE paths for $testName: $paths")
|
|
||||||
logOutput("IDE to run for $testName: $ide")
|
|
||||||
|
|
||||||
val projectHome = testCase.projectInfo?.downloadAndUnpackProject()
|
|
||||||
val context = IDETestContext(paths, ide, testCase, testName, projectHome, patchVMOptions = { this }, ciServer = ciServer)
|
|
||||||
allContexts += context
|
|
||||||
|
|
||||||
val baseContext = when (testCase.ideInfo == IdeProductProvider.AI) {
|
|
||||||
true -> context
|
|
||||||
.addVMOptionsPatch {
|
|
||||||
overrideDirectories(paths)
|
|
||||||
.withEnv("STUDIO_VM_OPTIONS", ide.patchedVMOptionsFile.toString())
|
|
||||||
}
|
|
||||||
false -> context
|
|
||||||
.disableInstantIdeShutdown()
|
|
||||||
.disableFusSendingOnIdeClose()
|
|
||||||
.disableJcef()
|
|
||||||
.disableLinuxNativeMenuForce()
|
|
||||||
.withGtk2OnLinux()
|
|
||||||
.disableGitLogIndexing()
|
|
||||||
.enableSlowOperationsInEdtInTests()
|
|
||||||
.collectOpenTelemetry()
|
|
||||||
.addVMOptionsPatch {
|
|
||||||
overrideDirectories(paths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return setupHooks
|
|
||||||
.fold(baseContext.updateGeneralSettings()) { acc, hook -> acc.hook() }
|
|
||||||
.apply { installPerformanceTestingPluginIfMissing(this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.intellij.ide.starter.sdk
|
|
||||||
|
|
||||||
import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkItem
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
data class JdkDownloadItem(
|
|
||||||
val jdk: JdkItem,
|
|
||||||
private val download: () -> JdkItemPaths
|
|
||||||
) {
|
|
||||||
private val installJdk = lazy { download() }
|
|
||||||
val home: Path get() = installJdk.value.homePath
|
|
||||||
val installPath: Path get() = installJdk.value.installPath
|
|
||||||
|
|
||||||
fun toSdk(sdkName: String) = SdkObject(
|
|
||||||
sdkName = sdkName,
|
|
||||||
sdkType = "JavaSDK",
|
|
||||||
sdkPath = home,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun equals(other: Any?) = other is JdkDownloadItem && other.jdk == jdk
|
|
||||||
override fun hashCode() = jdk.hashCode()
|
|
||||||
override fun toString(): String = "JdkDownloadItem(${jdk.fullPresentationText})"
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user