mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +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/
|
||||
stale_outputs_checked
|
||||
/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.
|
||||
package com.intellij.openapi.fileEditor.impl;
|
||||
|
||||
import com.intellij.openapi.editor.colors.ColorKey;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -34,7 +35,7 @@ public interface EditorTabColorProvider {
|
||||
*/
|
||||
@Nullable
|
||||
@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;
|
||||
|
||||
import com.intellij.openapi.editor.colors.ColorKey;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
|
||||
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vcs.FileStatusManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.FileColorManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -27,11 +29,11 @@ public class EditorTabColorProviderImpl implements EditorTabColorProvider, DumbA
|
||||
}
|
||||
|
||||
@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);
|
||||
if (!(manger instanceof FileEditorManagerImpl)) return null;
|
||||
FileEditorManagerImpl fileEditorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project);
|
||||
return fileEditorManager.getFileColor(file);
|
||||
FileStatusManager fileStatusManager = FileStatusManager.getInstance(project);
|
||||
return fileStatusManager.getStatus(file).getColorKey();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -434,7 +434,8 @@ open class EditorsSplitters internal constructor(val manager: FileEditorManagerI
|
||||
val manager = manager
|
||||
window.setForegroundAt(index, manager.getFileColor(file))
|
||||
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
|
||||
if (composite.isPreview) {
|
||||
val italic = TextAttributes(null, null, null, null, Font.ITALIC)
|
||||
@@ -1014,4 +1015,4 @@ private fun getSplittersForProject(activeWindow: Window?, project: Project?): Ed
|
||||
?: return null
|
||||
val splitters = if (activeWindow == null) null else fileEditorManager.getSplittersFor(activeWindow)
|
||||
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.
|
||||
package com.intellij.openapi.fileEditor.impl
|
||||
|
||||
import com.intellij.openapi.editor.colors.ColorKey
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.awt.Color
|
||||
|
||||
fun getForegroundColorForFile(project: Project, file: VirtualFile): Color? {
|
||||
return EditorTabColorProvider.EP_NAME.extensionList.firstOrNull()?.getEditorTabForegroundColor(project, file)
|
||||
fun getForegroundColorForFile(project: Project, file: VirtualFile): ColorKey? {
|
||||
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