GithubHelp home page GithubHelp logo

ide-probe's Introduction

By using this library, you implicitly accept the terms of the JetBrains Privacy Policy.

Description

ide-probe is a framework for testing plugins for IntelliJ-based IDEs. It can be used both locally and in the CI pipeline.

The framework itself comprises three components:

  • driver - responsible for controlling the workspace and installation, startup and communication with the IDE
  • probePlugin - a server that runs inside the IDE and executes commands and queries
  • api - a module that contains the definitions of endpoints and their protocol

Motivation

Sometimes, unit tests cannot be used to reproduce a failure or test some specific feature reliably. This happens because the testing environment used by IDEs most often differs from the actual, production-like environment experienced by the user. Be it a disabled UI, forced single-threaded execution, or just a different control flow, the unit tests are just not the right tool to use sometimes.

Using ide-probe fixes those problems at the non-avoidable cost of longer execution time when compared to unit tests. With it, not only a proper environment is used, but one can also guard against new classes of errors, like:

  • UI freezes
  • background plugin errors
  • unexpected behavior after starting or restarting IDE sessions
  • invalid interactions between multiple IDE sessions running in parallel

Getting Started

Adding ide-probe to the project

To include ide-probe in your sbt project add following lines:

libraryDependencies += "org.virtuslab.ideprobe" %% "driver" % "0.24.0"

To use snapshots versions, add another repository:

resolvers.in(ThisBuild) += Resolver.sonatypeRepo("snapshots")

To use remote robot, add a resolver:

ThisBuild / resolvers += MavenRepository(
  "jetbrains-3rd",
  "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies"
)

For gradle, use:

repositories {
    maven { url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' } // for robot extension
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } // for snapshots only
}

dependencies {
    implementation group: 'org.virtuslab.ideprobe', name: 'driver_2.13', version: '0.24.0'
}

Base test class setup

The default way to use ide-probe is to inherit from IdeProbeFixture. To use it, depend on driver module ("org.virtuslab.ideprobe" %% "driver" % version). This trait provides fixtureFromConfig method. It is a preferred way to create the IntelliJFixture which is used to run IntelliJ inside a workspace and interact with it. Additionally, it allows adding fixture transformers. Fixture transformers are a convenient method of adding some common behavior or configuration to a base trait for the tests. It is also the same mechanism that is used by extensions to add their probe plugin and custom behavior.

It is possible to just use IntelliJFixture.fromConfig directly, but in such case, you need to remember to call .enableExtensions method, or extensions will not be applied.

To write a test with JUnit 4 it is convenient to depend on junit-driver module, and extend from IdeProbeTestSuite in the test class. It is the IdeProbeFixture with @RunWith annotation for convenience.

It is advised to prepare a base trait that extends from IdeProbeFixture, all required extensions and that contains common fixture transformers to avoid repetition.

Configuration

ide-probe uses HOCON format for configuration. It is also possible to set up everything in code, but config files might usually be more convenient. Additionally, using config files allows to easily share configuration and create multiple variants of it for example for running the same test on different project.

Configuration can either be:

A) loaded from a file in classpath

fixtureFromConfig(Config.fromClasspath("path/example.conf"))

B) loaded from a file in the file system

fixtureFromConfig(Config.fromFile(Paths.get("/Users/user/Desktop/example.conf")))

C) provided as a string

fixtureFromConfig(Config.fromString("""probe { workspace.path = /foo/bar } """))

D) provided as a map

fixtureFromConfig(Config.fromMap(Map("probe.workspace.path" -> "/foo/bar")))

Usually it would be just used to programmatically override already loaded config, using helpful withConfig method on IntelliJFixture.

fixtureFromConfig().withConfig("some.extra.config.override" -> "value", "other.config" -> "value2")

E) specified programmatically

IntelliJFixture(workspaceProvider = WorkspaceTemplate.fromFile(path))
  .withVersion(IntelliJVersion.release("2020.3", "202.8194.7"))
  .withPlugin(Plugin("org.intellij.scala", "2020.2.7"))
  .enableExtensions
  1. Driver (search for probe.driver config)
  2. Resolvers (search for probe.resolvers config)
  3. Workspace (search for probe.workspace config)
  4. Display (search for probe.driver.display config)
  5. Debugging (search for probe.driver.debug config)

Workflow

Workflow can only be defined programmatically, since it comprises a sequence of intertwined:

  1. probe interactions
  2. workspace manipulation
  3. custom verification logic

Below example shows a test that creates a new sbt file, thus creating a sbt project, imports it, and check if the name is correct after the import.

It is composed of 2 files, the ExampleTest.scala and example.conf.

import org.virtuslab.ideprobe.Extensions._ // for `write` extension method on Path
import org.virtuslab.ideprobe.IdeProbeFixture
import org.junit.jupiter.api.Test

class ExampleTest extends IdeProbeFixture {
  private val fixture = fixtureFromConfig("example.conf")

  @Test def test() = {
    fixture.run { intelliJ =>
      val buildSbt = intelliJ.workspace.resolve("build.sbt")
      buildSbt.write(""" name := "example" """)

      intelliJ.probe.openProject(intelliJ.workspace)
      val projectModel = intelliJ.probe.projectModel()

      assert(projectModel.name == "example")
    }
  }

}

The contents of example.conf file located in resources:

probe {
  driver.vmOptions = [ "-Dgit.process.ignored=false", "-Xms2g" ]

  intellij {
    version {
      release = "2021.1.1"
      build = "211.7142.45"
    }
    plugins = [
      { id = "org.intellij.scala", version = "2021.1.18" }
    ]
  }
}

Endpoints

To see the list of probe endpoints, see Commands or Queries. An always up to date list of queries is available in Endpoints.scala

Note that any communication with the probe is synchronous.

Screenshots

The default folder for saving photos is /tmp/ide-probe/screenshots. Inside this directory screenshots are organised into test-suite/test-case subfolders if these values are detectable by the plugin. Otherwise, the photos are saved directly into /tmp/ide-probe/screenshots. The only way to override this default directory is by configuring probe.paths.screenshots in the fixture, either through the .conf file or in code. Overriding with .conf file looks like:

probe {
   // ...
   paths.screenshots = ${?MY_IDEPROBE_SCREENSHOTS_DIR}
}`

Thanks to HOCON feature, it is possible to populate any config value from environment variable. Additionally, it is achievable to make this value optional with question mark used before the name of the environment variable - if MY_IDEPROBE_SCREENSHOTS_DIR does not exist at runtime probe.paths.screenshots will use default value without any error. For non .conf scenario, user would call:

fixture.withPaths(IdeProbePaths(/*construct the instance passing, among others, a screenshots path that is most suitable for you*/))

Screenshots feature is only available with Xvfb display mode. They are taken on every AWT event, during probe shutdown (with on-exit in screenshot name) and when explicitly requested via probe.screenshot().

Extensions

Extensions exist to implement custom actions specific to a plugin. For example to create a ScalaTest run configuration, specific to Scala plugin, the Scala plugin extension is required. Similarly, to run Pants build, which is a custom action, it is best to use Pants extension that has it implemented. You can create your own extension for your plugin if there is anything not covered in existing endpoints.

To use an extension, add a dependency to your build and extend from appropriate trait.

Pants, Bazel and Scala

For dependencies use:

libraryDependencies += "org.virtuslab.ideprobe" %% "EXTENSION-probe-driver" % ideProbeVersion

// example
libraryDependencies += "org.virtuslab.ideprobe" %% "scala-probe-driver" % "0.24.0"

Additionally extend from org.virtuslab.ideprobe.EXTENSION.EXTENSIONPluginExtension, for example org.virtuslab.ideprobe.scala.ScalaPluginExtension

In the code example below, thanks to adding the ScalaPluginExtension, a new method setSbtProjectSettings is added through implicit conversion.

import org.virtuslab.ideprobe.Extensions._
import org.virtuslab.ideprobe.IdeProbeFixture
import org.virtuslab.ideprobe.protocol.Setting
import org.virtuslab.ideprobe.scala.ScalaPluginExtension
import org.virtuslab.ideprobe.scala.protocol.SbtProjectSettingsChangeRequest
import org.junit.jupiter.api.Test

class ExtensionExampleTest extends IdeProbeFixture with ScalaPluginExtension {
   private val fixture = fixtureFromConfig("example.conf")

   @Test def test() = {
      fixture.run { intelliJ =>
         val buildSbt = intelliJ.workspace.resolve("build.sbt")
         buildSbt.write(""" name := "example" """)

         val settings = SbtProjectSettingsChangeRequest(useSbtShellForBuild = Setting.Changed(true))
         intelliJ.probe.setSbtProjectSettings(settings)
         // or explicitly: ScalaProbeDriver(intelliJ.probe).setSbtProjectSettings(settings) 

         intelliJ.probe.openProject(intelliJ.workspace)
         val projectModel = intelliJ.probe.projectModel()
         assert(projectModel.name == "example")
      }
   }

}

Robot

The robot is a slightly different extension. It integrates Jetbrains Remote-Robot with ide-probe. This library can interact with UI of IntelliJ, click on specific components, but also read text from them. Additionally, it allows invoking custom code inside IntelliJ's JVM through JavaScript engine.

This extension automatically installs the plugin Remote-Robot plugin inside IntelliJ and establishes connection between IntelliJ and tests, giving access to an instance of RemoteRobot.

To add it to your project, use:

libraryDependencies += "org.virtuslab.ideprobe" %% "robot-driver" % ideProbeVersion

and make sure you have the necessary resolver, see Adding ide-probe to the project section.

The test class below extends from RobotPluginExtension which adds new withRobot method on intelliJ.probe. This method is a shorthand for creating the robot driver. Alternatively it can be created using RobotProbeDriver(intelliJ.probe) call.

The RobotProbeDriver has a field called robot which is an instance of RemoteRobot that can be used as described in JetBrains documentation. ide-probe also adds a simple DSL on top of existing one for simpler use with Scala. You can check example usage in RobotProbeDriver.scala

The RobotProveDriver also has openProject method, which is a wrapper around the regular openProject on ProbeDriver. It adds more advanced actions during waiting for project open, like monitoring the Build panel for results or closing the Tip of the Day modal which can't be easily done without robot.

import com.intellij.remoterobot.RemoteRobot
import org.junit.jupiter.api.Test
import org.virtuslab.ideprobe.Extensions._
import org.virtuslab.ideprobe.IdeProbeFixture
import org.virtuslab.ideprobe.robot.RobotPluginExtension

class ExampleRobotTest extends IdeProbeFixture with RobotPluginExtension {
  private val fixture = fixtureFromConfig("example.conf")

  @Test def test() = {
    fixture.run { intelliJ =>
      val buildSbt = intelliJ.workspace.resolve("build.sbt")
      buildSbt.write(""" name := "example" """)

      // Open project with more advanced features
      intelliJ.probe.withRobot.openProject(intelliJ.workspace)

      // use JetBrains Remote-Robot directly
      val robot: RemoteRobot = intelliJ.probe.withRobot.robot
      val image = robot.getScreenshot()
    }
  }

}

Waiting

Background

It is frequently required to wait after interacting with IDE, for example after invoking an action or opening the project.

Let's look closer at project opening. The internal API of IntelliJ returns early, before most of the projects would be imported, it invokes multiple background tasks, to call to the build tool and synchronize the project, additionally it performs indexing. Before indexing is complete, a lot of actions are disabled. For this reason it is best to wait for all background tasks to be complete.

Constructing WaitLogic

The below snippet contains a couple of examples of creating a WaitLogic.

// Waits till list of background tasks is empty, taking into the account
// only the tasks that have name and display in the UI properly.
// In this example it overrides the default 10 minute waiting limit.
// It is the default waiting method used during most of ide-probe endpoints.
WaitLogic.emptyNamedBackgroundTasks(atMost = 20.minutes)

// Waits for a background task with name containing "indexing" (case sensitive),
// to start and finish, i.e. it makes sure the task actually started and completed.
WaitLogic.backgroundTaskCompletes("indexing")

// Waits until project named "example" exists.
WaitLogic.projectByName("example")

// It is possible to perform some actions during waiting, which is useful 
// when it is long, or if some additional condition can be checked.
// This code tries to close *Tip of the Day*. Calling `DoOnlyOnce.attempt`,
// ensures that the code will have at most one successful run.
val closeTip = new DoOnlyOnce(closeTipOfTheDay())
WaitLogic.emptyNamedBackgroundTasks().doWhileWaiting {
   closeTip.attempt()
   checkBuildPanelErrors()
}

Using WaitLogic

All relevant methods that need waiting accept optional parameter of type WaitLogic (for example invokeAction, openProject), which is a way to override the default.

// Extend waiting limit for opening project and set checking frequency to 10 seconds.
val waitLogic = WaitLogic.emptyNamedBackgroundTasks(atMost = 30.minutes, basicCheckFrequency = 10.seconds)
intelliJ.probe.openProject(path, waitLogic)

There is also a special method called await, that just executes the provided WaitLogic.

The example below is a code from bazel extension that is responsible for building bazel project, using its custom action. It invokes the build action, constructs WaitLogic that waits for the build result (internally implemented as reading bazel console output using RemoteRobot), and finally, waits using this logic through await method.

intelliJ.probe.invokeActionAsync("MakeBlazeProject")

var result = Option.empty[BazelBuildResult]
val buildWait = WaitLogic.basic(checkFrequency = checkFrequency, atMost = waitLimit) {
  findBuildResult() match {
    case buildResult @ Some(_) =>
      result = buildResult
      WaitDecision.Done
    case None => 
       WaitDecision.KeepWaiting("Waiting for bazel build to complete")
  }
}

intelliJ.probe.await(buildWait)

Showcase

Probe is currently being actively used in:

  1. IntelliJ Pants plugin

It discovered or reproduced the following issues:

  1. Failing to import SBT projects without any JDK specified pull request
  2. Malfunctioning VCS root detection for pants plugin
  3. Missing thrift-related objects in the find window
  4. Failing to import pants project using BSP
  5. Incorrect pants project name generation
  6. Problematic conflict when handling BUILD files with both Bazel and Pants plugin installed
  7. BSP project performance regression in IntelliJ 2020.3

ide-probe's People

Contributors

azdrojowa123 avatar bednam avatar duhemm avatar hsz avatar lukaszkontowski avatar lukaszwawrzyk avatar mzarnowski avatar odisseus avatar pawellipski avatar romanowski avatar scala-steward avatar sellophane avatar tpasternak avatar wiacekm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ide-probe's Issues

Taking screenshots and environment variables `IDEPROBE_TEST_SUITE`, `IDEPROBE_TEST_CASE` and `IDEPROBE_SCREENSHOTS_DIR`

I have some problem with taking screenshots -> when I do not declare env variables IDEPROBE_TEST_SUITE and IDEPROBE_TEST_CASE (necessarily these two, declaring one variable has no effect) screenshots are not taken (I can't find them in the default location /tmp/ide-probe/screenshots/).
Also when I set IDEPROBE_SCREENSHOTS_DIR to some value it is not consider in ide-probe - still /tmp/ide-probe/screenshots/ is set. (It is not a big deal but it might be useful in some cases to store screenshots in specific locations not only in this default one).

`Fetching ... into ...` message shows incorrect path

com.virtuslab.gitmachete.uitest.UITestSuite STANDARD_OUT
    Installing IntelliJVersion(2020.3.1,None)
    Fetching https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2020.3.1/ideaIC-2020.3.1.zip into /tmp/cache/165f3e69a28c85d22c442194b3c29910

but /tmp/cache isn't even created, the file is apparently being downloaded into:

$ ls -thor /tmp | tail -1
-rw-------.  1 plipski 396M Jan 29 22:39 cached-resource3923141792298304190-tmp

Improve request/response messages

Currently each message contains this wrapper {data: }, it would be best not to log it. This is an obvious improvement.
More options to consider:

  • pretty print jsons (configurable)
  • filter some messages (configurable) - skip some more internal endpoints like backgroundTasks, messages, freezes, plugins, system properties, etc.
  • show messages as scala classes rather than json and pretty print them with some library

Add support for unzipped IntelliJs in `resolvers.intellij.repositories` config

I tried using the intellij downloaded by gradle runPluginVerifier, since it has an easier path than Gradle (no hashes inside, unlike with e.g. ~/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea/ideaIC/2021.2.2/aa03fa700ac9c82d03acad5eadd5e547b5ccc008/ideaIC-2021.2.2.zip:

  resolvers.intellij.repositories = [
    "file:///home/plipski/.pluginVerifier/ides/IC-[revision]/"
  ]

but ended up with:

    java.lang.IllegalStateException: Not an archive: /home/plipski/.pluginVerifier/ides/IC-2020.2.4
        at org.virtuslab.ideprobe.dependencies.Resource$ArchiveExtension.toArchive(Resource.scala:90)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.installIntelliJ(IntelliJProvider.scala:145)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.setup(IntelliJProvider.scala:117)
        at org.virtuslab.ideprobe.IntelliJFixture.installIntelliJ(IntelliJFixture.scala:88)
        at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.setup(ClassFixtures.scala:29)
        at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.setup$(ClassFixtures.scala:26)
        ..........

Existing IntelliJ instance provider

Ide-probe fetches zipped IntelliJ from jetbrains artifactory, unpacks the instance, modifies config files (idea64.vmoptions, idea.properties), installs plugins. The idea is to provide a second way for the IntelliJ instance to be resolved. User should be able to specify via config file if the remote (default) instance should be fetched or if existing instance should be used. From the config perspective it will be similar to the workspace (workspace.default, workspace.existing). See WorkspaceConfig.scala.

Besides, IdeProbePaths are hardcoded. We want them to be configurable because instances can have different directory structures. Also, some instances can have their own idea*.vmoptions and idea.properties. Probe now overwrites them but they should be rather preserved and appended.

Spurious error (race condition?) when moving downloaded IJ zip

Another run fixed that... probably some race condition?

EDIT: oh, actually for some reason robot-server-plugin-0.9.35.zip is being downloaded twice đŸ˜¯

Running against IntelliJ version(s): 
211.6693.14-EAP-SNAPSHOT

> Task :uiTests:test

com.virtuslab.gitmachete.uitest.UITestSuite STANDARD_OUT
    Installing IntelliJ(211.6693.14-EAP-SNAPSHOT)
    Fetching https://www.jetbrains.com/intellij-repository/snapshots/com/jetbrains/intellij/idea/ideaIC/211.6693.14-EAP-SNAPSHOT/ideaIC-211.6693.14-EAP-SNAPSHOT.zip into /tmp/ide-probe/cache/dbe67b6ce3d85c36edeccf2e76a497f3
    Fetching https://jetbrains.bintray.com/intellij-third-party-dependencies/org/jetbrains/test/robot-server-plugin/0.9.35/robot-server-plugin-0.9.35.zip into /tmp/ide-probe/cache/c1dae5b609f24141ebb86911a8b64b4b
    Fetching https://jetbrains.bintray.com/intellij-third-party-dependencies/org/jetbrains/test/robot-server-plugin/0.9.35/robot-server-plugin-0.9.35.zip into /tmp/ide-probe/cache/c1dae5b609f24141ebb86911a8b64b4b
    Extracting jar:file:/home/plipski/.gradle/caches/modules-2/files-2.1/org.virtuslab.ideprobe/driver_2.12/0.5.0+11-c291a948-SNAPSHOT/36838d5409a1b5212ea7bc584d08fe2b67ef0485/driver_2.12-0.5.0+11-c291a948-SNAPSHOT.jar!/ideprobe_2.13-0.5.0+11-c291a948-SNAPSHOT.zip from jar into /tmp/ide-probe/cache/64eb1ce3786f78032ec978c32c605dfb

com.virtuslab.gitmachete.uitest.UITestSuite > classMethod FAILED
    java.nio.file.FileAlreadyExistsException: /tmp/ide-probe/cache/c1dae5b609f24141ebb86911a8b64b4b
        at java.base/sun.nio.fs.UnixCopyFile.move(UnixCopyFile.java:450)
        at java.base/sun.nio.fs.UnixFileSystemProvider.move(UnixFileSystemProvider.java:267)
        at java.base/java.nio.file.Files.move(Files.java:1421)
        at org.virtuslab.ideprobe.ProbeExtensions$PathExtension.moveTo(ProbeExtensions.scala:72)
        at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.cacheUri(ResourceProvider.scala:47)
        at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.get(ResourceProvider.scala:26)
        at org.virtuslab.ideprobe.dependencies.ResourceProvider.get(ResourceProvider.scala:12)
        at org.virtuslab.ideprobe.dependencies.ResourceProvider.get$(ResourceProvider.scala:12)
        at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.get(ResourceProvider.scala:22)
        at org.virtuslab.ideprobe.dependencies.PluginDependencyProvider.resolve(DependencyProvider.scala:51)
        at org.virtuslab.ideprobe.dependencies.PluginDependencyProvider.$anonfun$fetch$8(DependencyProvider.scala:43)
        at scala.util.Try$.apply(Try.scala:213)
        at org.virtuslab.ideprobe.dependencies.PluginDependencyProvider.$anonfun$fetch$7(DependencyProvider.scala:43)
        at scala.util.Failure.orElse(Try.scala:224)
        at org.virtuslab.ideprobe.dependencies.PluginDependencyProvider.$anonfun$fetch$6(DependencyProvider.scala:43)
        at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
        at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
        at scala.collection.immutable.List.foldLeft(List.scala:89)
        at org.virtuslab.ideprobe.dependencies.PluginDependencyProvider.fetch(DependencyProvider.scala:42)
        at org.virtuslab.ideprobe.dependencies.DependencyProvider.fetch(DependencyProvider.scala:76)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.$anonfun$installPlugins$2(IntelliJFactory.scala:54)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
        at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
        at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
        at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.helpCC(ForkJoinPool.java:1115)
        at java.base/java.util.concurrent.ForkJoinPool.externalHelpComplete(ForkJoinPool.java:1957)
        at java.base/java.util.concurrent.ForkJoinTask.tryExternalHelp(ForkJoinTask.java:378)
        at java.base/java.util.concurrent.ForkJoinTask.externalAwaitDone(ForkJoinTask.java:323)
        at java.base/java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:412)
        at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:736)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.withParallel(IntelliJFactory.scala:74)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.installPlugins(IntelliJFactory.scala:53)
        at org.virtuslab.ideprobe.ide.intellij.IntelliJFactory.create(IntelliJFactory.scala:25)
        at org.virtuslab.ideprobe.IntelliJFixture.installIntelliJ(IntelliJFixture.scala:83)
        at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.setup(ClassFixtures.scala:29)
        at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.setup$(ClassFixtures.scala:26)
        at com.virtuslab.gitmachete.uitest.UITestSuite$.com$virtuslab$gitmachete$uitest$RunningIntelliJPerSuite$$super$setup(UITestSuite.scala:45)
        at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.setup(UITestSuite.scala:17)
        at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.setup$(UITestSuite.scala:17)
        at com.virtuslab.gitmachete.uitest.UITestSuite$.setup(UITestSuite.scala:45)
        at com.virtuslab.gitmachete.uitest.UITestSuite.setup(UITestSuite.scala)

1 test completed, 1 failed

> Task :uiTests:test FAILED

FAILURE: Build failed with an exception.

Resolve duplication regarding Git setups

We have at least 3 places that refer to git that I recall:

  • Setting workspace from git
  • Fetching pants from git (in pants plugin repo)
  • Fetching a plugin from git sources to build it

Some of these only allow to specify uri and optionally branch, some also allow for tag or commit. They all are used from .conf files. I think all of them are part of broader ADT.

It would be really useful to have just one GitReference class/trait with variants to specify tag, branch, ref or anything relevant for fetching some revision of source, and also to have a robust method to clone it, cache it and setup in some place (as we have it implemented separately for each of these cases).

This refactoring will not only cleanup the code, enable commit cloning in case of building plugins from sources but also make it easy to setup custom configuratble actions in fixtures that use git (like setting up pants from sources).

As of 2022.2 EAPs, `jbr_dcevm` seems no longer available; consider `jbr_jcef` or even just `jbr`

To run tests against 2022.2 EAP, I need to patch probe.resolvers.jbr.repositories:

    jbr.repositories = [
      # As of ide-probe v0.34.0, this is the `official`/default resolver (see `org.virtuslab.ideprobe.dependencies.JbrResolvers.official`).
      # Let's state the URL explicitly here anyway.
      # For further details on DCEVM (Dynamic Code Evolution VM), see https://blog.jetbrains.com/idea/2013/07/get-true-hot-swap-in-java-with-dcevm-and-intellij-idea/
      "https://cache-redirector.jetbrains.com/intellij-jbr/jbr_dcevm-[major]-[platform]-x64-b[minor].tar.gz",
 
     # As of Intellij 2022.2, jbr_dcevm seems no longer available for some reason.
      # Let's use jbr_jcef instead.
      # For further details on JCEF (Java Chromium Embedded Framework), see https://plugins.jetbrains.com/docs/intellij/jcef.html
      "https://cache-redirector.jetbrains.com/intellij-jbr/jbr_jcef-[major]-[platform]-x64-b[minor].tar.gz"
    ]

Maybe jbr_jcef JBRs were also available for the earlier versions and could be made the sole official/default one?
Or alternatively, both jbr_dcevm and jbr_jcef could be included as defaults.

Allow multiple plugin transformers registration

Consider the ScalaTestSuite which applies the scala probe plugin extension to the probe plugin. In case of the need for supporting multiple probe plugin extensions, we want to have a class that allows applying all of them.

Add pureconfig readers for protocol classes

We should have good and useful config readers that are already compiled and accessible via companion objects. These readers should be good and useful, specifically, we should have ConfigReader[TestScope] to simply read TestScope.Method or TestScope.Module based on set of fields provided in config, to avoid current necessity to:

  1. import pureconfig.generic.auto._ to use generic derivation in compile time (slowing down compile of clients)
  2. extend ConfigFormat to take configure strategy of genereting the above
  3. read config, by specifying concrete class: config[TestScope.Module]("key") which makes the test code less flexible: it is not possible to change scope to Method by only modifying config file.

inspect goToLineColumn behavior

Position in file (line:column) can be 1-based or 0-based. In visible parts of IDE as well as compilers and basically everywhere it is 1-based. Some parts of IntelliJ internals and bsp protocol (bsp is not relevant for this task, but it is just an example), use 0-based.

It seems like goToLineColumn uses accepted indeces as 0-based which is confusing for users. We should verify this and make sure that user has to pass 1-based positions.

As of 0.35.0, `probe.driver.vmOptions` are ignored: sort out kebab vs snake case in `ConfigFormat`

This is due to the recent change which added

override implicit def hint[A] = ProductHint[A](ConfigFieldMapping(CamelCase, KebabCase))

to object org.virtuslab.ideprobe.config.IdeProbeConfig.

Note that org.virtuslab.ideprobe.ConfigFormat also has its own hint, with ConfigFieldMapping(CamelCase, CamelCase).

I'd unify everything both in code and docs, and also set allowUnknownKeys to false so that capturing such mistakes is easier.

TBH I'd switch to the more conventional kebab-case... although maybe it's better to stick to snakeCase, so that compatibility of older keys isn't broken.

Documentation for contributors (BuildInfo)

The project setup requires enabling sbt shell for builds and for imports so that the BuildInfo object is generated. It isn't set by default in IntelliJ settings, some information for contributors would be beneficial.

IntelliJ instance should be deleted after failed test

Each test run creates a new copy of IntelliJ instance. For now, the instance stored in /tmp/ isn't cleaned up after the failing test. It results in a memory overflow caused by limited space in the /tmp/ which can be resolved only by manually deleting instances or by the reboot.

In addition, we can remove ideprobe-workspace* directories cause they are left empty after the test execution.

Configurable paths for temporary files

We have some places with hardcoded `/tmp/ideprobe/" (for screenshots and cache, (actually cache is configurable)), but we use crateTmpDirectory e.g. for intellij installations and workspaces. Ideally this should be configurable, for example to persist cache when restarting PC, or to put it on faster drive/other file system etc.
I am not entirely sure if we should provide some special new file like ideprobe.global.conf located in root of resources, or just keep it scoped for all tests and users would need to include it in their base config file. But regardless, I was thinking about having something like

probe.paths {
  base = "/tmp/ideprobe/"
  cache = ${HOME}"/.cache/ideprobe"
  screenshots = ${probe.paths.base}/screenshots
  reports = ${probe.paths.base}/reports
  ij-instances = ${probe.paths.base}/instances
  workspaces = ${probe.paths.base}/workspaces
}

And some API to access these (not sure if global or inside fixture):

fixture.paths.cache
fixture.paths.screenshots
val targetCloneDir = fixture.paths.base.resolve("pants-repos").create().resolve(Hash.md5("$uri-$ref"))

It is worth thinking how we support different OS, if someone wants a /tmp dir, it is different on mac and on windows. Perhaps with an env var base = ${TEMP}/ideprobethat we can make sure is set to valid temp dir or something or use a special synthetic key.

Switching from 0.3.0 to 0.4.0 increases UI test time by 1.5x-2x

Comparing e.g. https://app.circleci.com/pipelines/github/VirtusLab/git-machete-intellij-plugin/3941/workflows/5d220950-c991-49c6-b9cb-27b6b5ed9517/jobs/4142, ca. 4 minutes/IDE version:

image

Versus https://app.circleci.com/pipelines/github/VirtusLab/git-machete-intellij-plugin/3940/workflows/55481c81-6d7f-4e14-b9b4-de753ef86279/jobs/4141, ca. 7 minutes/IDE version:

image

I checked the diff b/w 0.3.0 and 0.4.0... AwaitIdleParams got new field active but I doubt that's cause.
DurationAssertions also look suspicious...

See how we can cover git-machete plugin tests with ide-probe

Git machete intellij plugin has some tests here: https://github.com/VirtusLab/git-machete-intellij-plugin/tree/develop/uiTests that use remote robot directly. This is nice set of scenarios we didn't cover so far

I think the ultimate goal here is to replace robot tests with ide-probe tests and after this change probe tests should be easier to write, maintain, setup etc.

Generally probe will help with accessing intellij api through javascript put inside strings, need for reflection due to different classloader, reimplementing waiting for background tasks, opening projects and so on.

If we want to invoke internal intellij methods and have tests, we need to setup 4 modules (api, intellij plugin, driver, tests) vs. just tests module needed for robot. We have a way to do this with sbt (which is not perfect, we have hacks for sbt-intellij-plugin to be able to build more than one plugin in one project, yet it still is imperfect and can cause problems), also most of intellij plugins will use gradle rather than sbt. Perhaps we may want to prepare a drop in seed project with the 4 modules I mentioned with sbt and CI (similar what was done in pants) or better, figure out how to setup such layout with gradle.

NPE in IntelliJFixture#closeIntellij in case of an unresolved IDE

See https://app.circleci.com/pipelines/github/VirtusLab/git-machete-intellij-plugin/3866/workflows/8bfa0618-2354-4d7e-95bd-316a7c859ff4/jobs/4057 for details, but generally:

First, the IDE hasn't been resolved:

java.io.FileNotFoundException: https://d2s4y8xcwt8bet.cloudfront.net/com/jetbrains/intellij/idea/ideaIC/211.4961.30-EAP-CANDIDATE-SNAPSHOT/ideaIC-211.4961.30-EAP-CANDIDATE-SNAPSHOT.zip
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1920)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
	at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
	at java.base/java.net.URL.openStream(URL.java:1140)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider.$anonfun$get$1(ResourceProvider.scala:12)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.cacheUri(ResourceProvider.scala:46)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.get(ResourceProvider.scala:26)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider.get(ResourceProvider.scala:12)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider.get$(ResourceProvider.scala:12)
	at org.virtuslab.ideprobe.dependencies.ResourceProvider$Cached.get(ResourceProvider.scala:22)
	at org.virtuslab.ideprobe.dependencies.IntelliJDependencyProvider.resolve(DependencyProvider.scala:28)
	at org.virtuslab.ideprobe.dependencies.IntelliJDependencyProvider.$anonfun$fetch$4(DependencyProvider.scala:17)
	at scala.util.Try$.apply(Try.scala:213)
.............

aaaand then closeIntelliJ fails:

java.lang.NullPointerException
	at org.virtuslab.ideprobe.IntelliJFixture.closeIntellij(IntelliJFixture.scala:103)
	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown(ClassFixtures.scala:37)
	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown$(ClassFixtures.scala:32)
	at com.virtuslab.gitmachete.uitest.UITestSuite$.org$virtuslab$ideprobe$junit4$RunningIntelliJPerSuite$$super$teardown(UITestSuite.scala:18)
	at org.virtuslab.ideprobe.junit4.RunningIntelliJPerSuite.teardown(ClassFixtures.scala:11)
	at org.virtuslab.ideprobe.junit4.RunningIntelliJPerSuite.teardown$(ClassFixtures.scala:11)
	at com.virtuslab.gitmachete.uitest.UITestSuite$.teardown(UITestSuite.scala:18)
	at com.virtuslab.gitmachete.uitest.UITestSuite.teardown(UITestSuite.scala)
..................

The running test has to be stopped twice

The first test stop doesn't result in the IntelliJ process being killed (while IntelliJ instance is running and the project is open). It is only done for the second stop attempt. The task is to resolve it so that the test is stopped for the first time.

Make Xvfb screen size configurable via HOCON

That's a matter of making the following width and height values in org.virtuslab.ideprobe.ide.intellij.InstalledIntelliJ#executable configurable:

case Display.Xvfb   => s"""xvfb-run --server-num=${Display.XvfbDisplayId} --server-args="-screen 0 ${width}x${height}x24" $launcher"""

For some reason, on my machine the default is assumed to be 640x480, which makes certain UI tests fail (mostly coz right-click context menu doesn't fit within IntelliJ window, apparently).

Configure display mode via HOCON configuration file instead of env variable

Now, if we want to run ide-probe with specific display-mode we need to set special env variable for it (IDEPROBE_DISPLAY).
For greater consistency this configuration could be transferred into HOCON configuration file like this:

display {
  xvfb {
    enabled = true
   screen {
      width = 1920
      height = 1080
      depth = 24
    }
  }
}

Race conditon (?): java.nio.file.DirectoryNotEmptyException: /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw

First happened on ide-probe 0.10.2.
Never happened when I was using 0.9.1 to test against IntelliJ 2021.1.x.

See https://app.circleci.com/pipelines/github/VirtusLab/git-machete-intellij-plugin/4141/workflows/e92c8168-bb98-4f2a-a1d9-555cf08b98ae/jobs/4357/steps for details:

com.virtuslab.gitmachete.uitest.UITestSuite STANDARD_ERROR
    java.io.IOException: [11] Failure while deleting /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw at dir  /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw/system
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$2(ProbeExtensions.scala:246)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$2$adapted(ProbeExtensions.scala:243)
    	at scala.util.Success.foreach(Try.scala:253)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:243)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:221)
    	at java.base/java.nio.file.Files.walkFileTree(Files.java:2742)
    	at java.base/java.nio.file.Files.walkFileTree(Files.java:2796)
    	at org.virtuslab.ideprobe.ProbeExtensions$PathExtension.delete(ProbeExtensions.scala:142)
    	at org.virtuslab.ideprobe.ide.intellij.DownloadedIntelliJ.cleanup(InstalledIntelliJ.scala:205)
    	at org.virtuslab.ideprobe.IntelliJFixture.$anonfun$cleanupIntelliJ$1(IntelliJFixture.scala:94)
    	at org.virtuslab.ideprobe.IntelliJFixture.withRetries(IntelliJFixture.scala:113)
    	at org.virtuslab.ideprobe.IntelliJFixture.cleanupIntelliJ(IntelliJFixture.scala:94)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.$anonfun$teardown$3(ClassFixtures.scala:45)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.$anonfun$teardown$3$adapted(ClassFixtures.scala:45)
    	at scala.Option.foreach(Option.scala:407)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown(ClassFixtures.scala:45)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown$(ClassFixtures.scala:39)
    	at com.virtuslab.gitmachete.uitest.UITestSuite$.com$virtuslab$gitmachete$uitest$RunningIntelliJPerSuite$$super$teardown(UITestSuite.scala:44)
    	at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.teardown(UITestSuite.scala:28)
    	at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.teardown$(UITestSuite.scala:26)
    	at com.virtuslab.gitmachete.uitest.UITestSuite$.teardown(UITestSuite.scala:44)
    	at com.virtuslab.gitmachete.uitest.UITestSuite.teardown(UITestSuite.scala)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    	at org.junit.internal.runners.statements.RunAfters.invokeMethod(RunAfters.java:46)
    	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
    	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:121)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
    	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
    	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    	at java.base/java.lang.Thread.run(Thread.java:834)
    Caused by: java.nio.file.DirectoryNotEmptyException: /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw/system
    	at java.base/sun.nio.fs.UnixFileSystemProvider.implDelete(UnixFileSystemProvider.java:247)
    	at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
    	at java.base/java.nio.file.Files.delete(Files.java:1141)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$1(ProbeExtensions.scala:240)
    	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
    	at scala.util.Try$.apply(Try.scala:213)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:240)
    	... 59 more
    java.io.IOException: [11] Failure while deleting /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw at dir  /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$2(ProbeExtensions.scala:246)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$2$adapted(ProbeExtensions.scala:243)
    	at scala.util.Success.foreach(Try.scala:253)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:243)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:221)
    	at java.base/java.nio.file.Files.walkFileTree(Files.java:2742)
    	at java.base/java.nio.file.Files.walkFileTree(Files.java:2796)
    	at org.virtuslab.ideprobe.ProbeExtensions$PathExtension.delete(ProbeExtensions.scala:142)
    	at org.virtuslab.ideprobe.ide.intellij.DownloadedIntelliJ.cleanup(InstalledIntelliJ.scala:205)
    	at org.virtuslab.ideprobe.IntelliJFixture.$anonfun$cleanupIntelliJ$1(IntelliJFixture.scala:94)
    	at org.virtuslab.ideprobe.IntelliJFixture.withRetries(IntelliJFixture.scala:113)
    	at org.virtuslab.ideprobe.IntelliJFixture.cleanupIntelliJ(IntelliJFixture.scala:94)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.$anonfun$teardown$3(ClassFixtures.scala:45)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.$anonfun$teardown$3$adapted(ClassFixtures.scala:45)
    	at scala.Option.foreach(Option.scala:407)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown(ClassFixtures.scala:45)
    	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown$(ClassFixtures.scala:39)
    	at com.virtuslab.gitmachete.uitest.UITestSuite$.com$virtuslab$gitmachete$uitest$RunningIntelliJPerSuite$$super$teardown(UITestSuite.scala:44)
    	at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.teardown(UITestSuite.scala:28)
    	at com.virtuslab.gitmachete.uitest.RunningIntelliJPerSuite.teardown$(UITestSuite.scala:26)
    	at com.virtuslab.gitmachete.uitest.UITestSuite$.teardown(UITestSuite.scala:44)
    	at com.virtuslab.gitmachete.uitest.UITestSuite.teardown(UITestSuite.scala)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    	at org.junit.internal.runners.statements.RunAfters.invokeMethod(RunAfters.java:46)
    	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
    	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:121)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
    	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
    	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    	at java.base/java.lang.Thread.run(Thread.java:834)
    Caused by: java.nio.file.DirectoryNotEmptyException: /tmp/ide-probe/instances/intellij-instance-2021.1.3--D4h5NFL1Sw6RlKle0iUAbw
    	at java.base/sun.nio.fs.UnixFileSystemProvider.implDelete(UnixFileSystemProvider.java:247)
    	at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
    	at java.base/java.nio.file.Files.delete(Files.java:1141)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.$anonfun$postVisitDirectory$1(ProbeExtensions.scala:240)
    	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
    	at scala.util.Try$.apply(Try.scala:213)
    	at org.virtuslab.ideprobe.ProbeExtensions$DeletingVisitor.postVisitDirectory(ProbeExtensions.scala:240)
    	... 59 more

scala.MatchError: IDE_UPDATE (of class com.intellij.notification.NotificationType)

As per https://app.circleci.com/pipelines/github/VirtusLab/git-machete-intellij-plugin/3919/workflows/551f307b-801c-4c9b-ba40-6281b5e03cc2/jobs/4116:

java.lang.Exception: Test failed due to postcondition failures
	at org.virtuslab.ideprobe.reporting.AfterTestChecks$.apply(AfterTestChecks.scala:8)
	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown(ClassFixtures.scala:33)
	at org.virtuslab.ideprobe.RunningIntelliJPerSuiteBase.teardown$(ClassFixtures.scala:32)
	at com.virtuslab.gitmachete.uitest.UITestSuite$.org$virtuslab$ideprobe$junit4$RunningIntelliJPerSuite$$super$teardown(UITestSuite.scala:18)
	at org.virtuslab.ideprobe.junit4.RunningIntelliJPerSuite.teardown(ClassFixtures.scala:11)
	at org.virtuslab.ideprobe.junit4.RunningIntelliJPerSuite.teardown$(ClassFixtures.scala:11)
	at com.virtuslab.gitmachete.uitest.UITestSuite$.teardown(UITestSuite.scala:18)
	at com.virtuslab.gitmachete.uitest.UITestSuite.teardown(UITestSuite.scala)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.RunAfters.invokeMethod(RunAfters.java:46)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
	Suppressed: java.lang.Exception: Errors caused by org.virtuslab.ideprobe >>>
	IdeaLoggingEvent[message=IDE_UPDATE (of class com.intellij.notification.NotificationType), throwable=scala.MatchError: IDE_UPDATE (of class com.intellij.notification.NotificationType)
	at org.virtuslab.ideprobe.log.NotificationsInterceptor$Interceptor.notify(NotificationsInterceptor.scala:16)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeMethod(MessageBusImpl.java:674)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeListener(MessageBusImpl.java:653)
	at com.intellij.util.messages.impl.MessageBusImpl.deliverMessage(MessageBusImpl.java:422)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpWaitingBuses(MessageBusImpl.java:397)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:379)
	at com.intellij.util.messages.impl.MessageBusImpl.access$100(MessageBusImpl.java:33)
	at com.intellij.util.messages.impl.MessageBusImpl$MessagePublisher.invoke(MessageBusImpl.java:185)
	at com.sun.proxy.$Proxy49.notify(Unknown Source)
	at com.intellij.notification.Notifications$Bus.doNotify(Notifications.java:88)
	at com.intellij.notification.Notifications$Bus.lambda$notify$1(Notifications.java:77)
	at com.intellij.util.ui.EdtInvocationManager.invokeLaterIfNeeded(EdtInvocationManager.java:101)
	at com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(UIUtil.java:2102)
	at com.intellij.notification.Notifications$Bus.notify(Notifications.java:77)
	at com.intellij.notification.Notification.notify(Notification.java:399)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showNotification(UpdateChecker.kt:696)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showUpdateResult(UpdateChecker.kt:590)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.access$showUpdateResult(UpdateChecker.kt:56)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker$doUpdateAndShowResult$1.run(UpdateChecker.kt:179)
	at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:218)
	at com.intellij.openapi.application.TransactionGuardImpl.access$200(TransactionGuardImpl.java:21)
	at com.intellij.openapi.application.TransactionGuardImpl$2.run(TransactionGuardImpl.java:200)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.openapi.application.impl.ApplicationImpl.lambda$invokeLater$4(ApplicationImpl.java:319)
	at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:84)
	at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:133)
	at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:46)
	at com.intellij.openapi.application.impl.FlushQueue$FlushNow.run(FlushQueue.java:189)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:972)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:838)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:448)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:447)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:495)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
]

scala.MatchError: IDE_UPDATE (of class com.intellij.notification.NotificationType)
	at org.virtuslab.ideprobe.log.NotificationsInterceptor$Interceptor.notify(NotificationsInterceptor.scala:16)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeMethod(MessageBusImpl.java:674)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeListener(MessageBusImpl.java:653)
	at com.intellij.util.messages.impl.MessageBusImpl.deliverMessage(MessageBusImpl.java:422)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpWaitingBuses(MessageBusImpl.java:397)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:379)
	at com.intellij.util.messages.impl.MessageBusImpl.access$100(MessageBusImpl.java:33)
	at com.intellij.util.messages.impl.MessageBusImpl$MessagePublisher.invoke(MessageBusImpl.java:185)
	at com.sun.proxy.$Proxy49.notify(Unknown Source)
	at com.intellij.notification.Notifications$Bus.doNotify(Notifications.java:88)
	at com.intellij.notification.Notifications$Bus.lambda$notify$1(Notifications.java:77)
	at com.intellij.util.ui.EdtInvocationManager.invokeLaterIfNeeded(EdtInvocationManager.java:101)
	at com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(UIUtil.java:2102)
	at com.intellij.notification.Notifications$Bus.notify(Notifications.java:77)
	at com.intellij.notification.Notification.notify(Notification.java:399)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showNotification(UpdateChecker.kt:696)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showUpdateResult(UpdateChecker.kt:590)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.access$showUpdateResult(UpdateChecker.kt:56)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker$doUpdateAndShowResult$1.run(UpdateChecker.kt:179)
	at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:218)
	at com.intellij.openapi.application.TransactionGuardImpl.access$200(TransactionGuardImpl.java:21)
	at com.intellij.openapi.application.TransactionGuardImpl$2.run(TransactionGuardImpl.java:200)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.openapi.application.impl.ApplicationImpl.lambda$invokeLater$4(ApplicationImpl.java:319)
	at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:84)
	at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:133)
	at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:46)
	at com.intellij.openapi.application.impl.FlushQueue$FlushNow.run(FlushQueue.java:189)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:972)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:838)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:448)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:447)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:495)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
	IdeaLoggingEvent[message=IDE_UPDATE (of class com.intellij.notification.NotificationType), throwable=scala.MatchError: IDE_UPDATE (of class com.intellij.notification.NotificationType)
	at org.virtuslab.ideprobe.log.NotificationsInterceptor$Interceptor.notify(NotificationsInterceptor.scala:16)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeMethod(MessageBusImpl.java:674)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeListener(MessageBusImpl.java:653)
	at com.intellij.util.messages.impl.MessageBusImpl.deliverMessage(MessageBusImpl.java:422)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpWaitingBuses(MessageBusImpl.java:397)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:379)
	at com.intellij.util.messages.impl.MessageBusImpl.access$100(MessageBusImpl.java:33)
	at com.intellij.util.messages.impl.MessageBusImpl$MessagePublisher.invoke(MessageBusImpl.java:185)
	at com.sun.proxy.$Proxy49.notify(Unknown Source)
	at com.intellij.notification.Notifications$Bus.doNotify(Notifications.java:88)
	at com.intellij.notification.Notifications$Bus.lambda$notify$1(Notifications.java:77)
	at com.intellij.util.ui.EdtInvocationManager.invokeLaterIfNeeded(EdtInvocationManager.java:101)
	at com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(UIUtil.java:2102)
	at com.intellij.notification.Notifications$Bus.notify(Notifications.java:77)
	at com.intellij.notification.Notification.notify(Notification.java:399)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showNotification(UpdateChecker.kt:696)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showUpdateResult(UpdateChecker.kt:590)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.access$showUpdateResult(UpdateChecker.kt:56)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker$doUpdateAndShowResult$1.run(UpdateChecker.kt:179)
	at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:218)
	at com.intellij.openapi.application.TransactionGuardImpl.access$200(TransactionGuardImpl.java:21)
	at com.intellij.openapi.application.TransactionGuardImpl$2.run(TransactionGuardImpl.java:200)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.openapi.application.impl.ApplicationImpl.lambda$invokeLater$4(ApplicationImpl.java:319)
	at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:84)
	at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:133)
	at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:46)
	at com.intellij.openapi.application.impl.FlushQueue$FlushNow.run(FlushQueue.java:189)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:972)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:838)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:448)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:447)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:495)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
]

scala.MatchError: IDE_UPDATE (of class com.intellij.notification.NotificationType)
	at org.virtuslab.ideprobe.log.NotificationsInterceptor$Interceptor.notify(NotificationsInterceptor.scala:16)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeMethod(MessageBusImpl.java:674)
	at com.intellij.util.messages.impl.MessageBusImpl.invokeListener(MessageBusImpl.java:653)
	at com.intellij.util.messages.impl.MessageBusImpl.deliverMessage(MessageBusImpl.java:422)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpWaitingBuses(MessageBusImpl.java:397)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:379)
	at com.intellij.util.messages.impl.MessageBusImpl.access$100(MessageBusImpl.java:33)
	at com.intellij.util.messages.impl.MessageBusImpl$MessagePublisher.invoke(MessageBusImpl.java:185)
	at com.sun.proxy.$Proxy49.notify(Unknown Source)
	at com.intellij.notification.Notifications$Bus.doNotify(Notifications.java:88)
	at com.intellij.notification.Notifications$Bus.lambda$notify$1(Notifications.java:77)
	at com.intellij.util.ui.EdtInvocationManager.invokeLaterIfNeeded(EdtInvocationManager.java:101)
	at com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(UIUtil.java:2102)
	at com.intellij.notification.Notifications$Bus.notify(Notifications.java:77)
	at com.intellij.notification.Notification.notify(Notification.java:399)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showNotification(UpdateChecker.kt:696)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.showUpdateResult(UpdateChecker.kt:590)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker.access$showUpdateResult(UpdateChecker.kt:56)
	at com.intellij.openapi.updateSettings.impl.UpdateChecker$doUpdateAndShowResult$1.run(UpdateChecker.kt:179)
	at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:218)
	at com.intellij.openapi.application.TransactionGuardImpl.access$200(TransactionGuardImpl.java:21)
	at com.intellij.openapi.application.TransactionGuardImpl$2.run(TransactionGuardImpl.java:200)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.openapi.application.impl.ApplicationImpl.lambda$invokeLater$4(ApplicationImpl.java:319)
	at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:84)
	at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:133)
	at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:46)
	at com.intellij.openapi.application.impl.FlushQueue$FlushNow.run(FlushQueue.java:189)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:972)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:838)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:448)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:447)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:782)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:495)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
<<<
		at org.virtuslab.ideprobe.reporting.ErrorValidator$.apply(ErrorValidator.scala:12)
		at org.virtuslab.ideprobe.reporting.AfterTestChecks$.apply(AfterTestChecks.scala:10)
		... 48 more

Add config codecs for all protocol classes

Currently, if someone want to read a protocol class from config, say ApplicationRunConfiguration, it is required to import macro to generate it and include ConfigFormat in the test class. These readers/writers should be put in companion objects, ready to use. Currently we have it for some of classes only.

An easy setting to re-use IntelliJ distributions already downloaded by other tool

If gradle or other tool already downloads a linux zip for intelliJ, it should be easy to reuse it instead of downloading again.

It is now possible to change the IntelliJ repository URL, so one option is to copy files from gradle into a matching structure and update resolver root url to the root directory of such fake repository.

Another possibility available now is to replace resolver in intellij factory with custom code.

An improvement over that would be to extend default resolver to understand patterns in path like version or artifact name to resolve intellij, so that the repository path can be easily specified as a string.

Enable cross publishing

Probe will generally be on 2.13, but it is useful to have basic modules on 2.12, especially api, probe plugin and driver. This doesn't work with default cross scala versions, we probably need to use sbt-cross plugin.
It might not work out of the box, so it will require debugging our build for example, trying to setup a separate similar, but simpler project, configure there the cross publishing that is working and from there on add all more complicated parts of the build like building intellij plugins.

Fix running ideprobe on macos

ide-probe was designed to run on linux, running on mac causes

[intellij-stderr] java.lang.NoClassDefFoundError: com/apple/eawt/OpenURIHandler
[intellij-stderr] 	at com.intellij.ui.mac.MacOSApplicationProvider.initApplication(MacOSApplicationProvider.java:57)
[intellij-stderr] 	at com.intellij.idea.ApplicationLoader.startApp(ApplicationLoader.kt:152)
[intellij-stderr] 	at com.intellij.idea.ApplicationLoader.executeInitAppInEdt(ApplicationLoader.kt:68)
[intellij-stderr] 	at com.intellij.idea.ApplicationLoader.access$executeInitAppInEdt(ApplicationLoader.kt:1)
[intellij-stderr] 	at com.intellij.idea.ApplicationLoader$initApplication$1$1.run(ApplicationLoader.kt:374)
[intellij-stderr] 	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
[intellij-stderr] 	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
[intellij-stderr] 	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
[intellij-stderr] 	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
[intellij-stderr] 	at java.base/java.security.AccessController.doPrivileged(Native Method)
[intellij-stderr] 	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
[intellij-stderr] 	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
[intellij-stderr] 	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

We should inspect how intellij is started on mac normally, that the missing class appears on classpath. It might be just a different launcher or we may need to download dmg files.

`com.intellij.remoterobot:remote-fixtures:1.1.18` seems no longer available

This is a dependency of robot-driver_2.13:0.23.0:

https://search.maven.org/artifact/org.virtuslab.ideprobe/robot-driver_2.13/0.23.0/jar ->

        <dependency>
            <groupId>com.intellij.remoterobot</groupId>
            <artifactId>remote-fixtures</artifactId>
            <version>1.1.18</version>
        </dependency>

I've got it downloaded in my Gradle cache so it must have existed:

$ ls ~/.gradle/caches/modules-2/files-2.1/com.intellij.remoterobot/remote-fixtures/1.1.18/
6f1e474eb54ed1cc9a33dfb7e5b23cad41dd9b7e  bbcc7b941e1fa99ec8c9448a1818f1152d502e6b

But now it doesn't:

https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/com/intellij/remoterobot/remote-fixtures/

image

Not good :// anyway, ide-probe needs to be updated

Rework await idle

Await idle can be stuck on waiting too long because of different reasons. Probe then is fully blocked as it is single threaded and we cannot even take a screenshot or stop the waiting, or modify waiting strategy in any way. Currently we made a temporary workaround allowing to disable await idle globally. The actual solution we want to end up with is to have await idle on driver side.
Probe should only be used to query the background task state. Wainting logic should be implemented in awaitIdle method in driver. BackgroundTasks.awaitNone and its usages should also be moved to driver side.
For specific endpoints we can use more sophisticated waiting mechanisms, like waiting for import to start and then wait for indexing to end instead of waiting for all tasks.
We can provide await limit and show tasks list and create a screenshot if it is exceeded.

Initial idea of implementing this is some trait WaitLogic { def await[A](f: Future[A]): Either[Error, Unit] } with multiple implementations that are composable, and make it a parameter for each endpoint that awaits by default (parameter. with a default value). Below some example code of how it could look like.

def invokeActionAsync(action: ActionRef, await = WaitLogic.Async) // implemented as no waiting
WaitLogic.ForFuture(atMost = 10.minutes) // just wait for future to complete
WaitLogic.NoBackgroundTasks(atMost = 10.minutes, checkDelay = 5.seconds, probeFrequency = 50.millis) // similar to current awaitIdle
WaitLogic.BackgroundTaskExists(task = "Importing", atMost = 10.minutes, checkDelay = 2.seconds) // wait for list to contain some task
WaitLogic.BackgroundTaskNotExists(task = "Indexing", atMost = 10.minutes, checkDelay = 2.seconds) // wait for list to not contain some task
def BackgroundTaskCompletes(...) = WaitLogic.BackgroundTaskExists(...) andThen WaitLogic.BackgroundTaskNotExists(...) // ensure that some background task appears and completes, example of composition
val OpenProject = WaitLogic.BackgroundTaskCompletes(task = "Indexing", atMost = 30.min) // some meaningful waiting stratgy based on default building blocks, await for opening project is created as expectation to indexing start and complete (though indexing might appear and dissappear couple of times so we might create more complex strategy)
probe.driver.openProject(path, await = WaitLogic.OpenProject.copy(atMost = 10.min)) // example of overriding project waiting logic to wait shorter than default.
WaitLogic.Condition(atMost = 10.minutes) { probe.driver.projectModel.name == "expected name" } // await for custom condition.

Top-level directory in JBR jars isn't always called `jbr` => the tested IntelliJ might end up running on a vanilla JRE

AFAICS org.virtuslab.ideprobe.ide.intellij.IntelliJFactory#installJbr just unpacks the JBR tar.gz directly to the IntelliJ instance directory.

This is subsequently taken advantage of in JetBrains-provided <intellij-instance>/bin/idea.sh script (here with line numbers):

  46   # ---------------------------------------------------------------------
  47   # Locate a JRE installation directory command -v will be used to run the IDE.
  48   # Try (in order): $IDEA_JDK, .../idea.jdk, .../jbr, $JDK_HOME, $JAVA_HOME, "java" in $PATH.
  49   # ---------------------------------------------------------------------

IIUC the third option (../jbr) is always taken by ideprobe-launched IntelliJ instances, which is expected.

Now see e.g. https://cache-redirector.jetbrains.com/intellij-jbr/jbr_jcef-17.0.3-linux-x64-b469.16.tar.gz (for 2022.2 EAP: it's Java 17, not 11!). This tar.gz's top-level folder is called jbr_jcef-17.0.3-x64-b469 and not just jbr. This causes a folder <intellij-instance>/jbr_jcef-17.0.3-x64-b469 to show up... and this is ignored by bin/idea.sh. One of the further options is picked instead ($JDK_HOME, $JAVA_HOME, "java" in $PATH), which points to some vanilla JDK and not a JBR. This, in turn, leads to various fatal errors (I've noticed some in AWT around native .so library loading... but that's not really important for this issue) ☚ī¸

What can be done here, is to either:

  • in org.virtuslab.ideprobe.ide.intellij.InstalledIntelliJ#startProcess, provide IDEA_JDK env var to IntelliJ explicitly

or (and I think better: simpler, more predictable)

  • in org.virtuslab.ideprobe.ide.intellij.IntelliJFactory#installJbr, make sure that the folder is always called jbr and not whatever comes out from tar -xvzf.

sbt clean is neccessary on new class file add

Incorporating changes like the new class file add result in the runtime error with an object reference not present. It is resolved by using sbt clean. The task is to automatically refresh the project containing changes.

sbt keeps re-downloading JBR when building this project

[info] welcome to sbt 1.3.13 (Red Hat, Inc. Java 11.0.15)
...................
[info] + IdeaDependency(BuildInfo(ideaIC-202.8194.7)) is up to date: 2/2
[info] New JBR is different from installed: JBR(11_0_9,944.49,jbr,linux,x64) != "JBR-11.0.9.11-944.49-jfx_jcef"
[info] ~ Resolving JbrDependency(AutoJbr()) -> 1/1 new artifacts
[info] Starting download https://cache-redirector.jetbrains.com/jetbrains.bintray.com/intellij-jbr/jbr-11_0_9-linux-x64-b944.49.tar.gz to /home/plipski/.ideprobePluginIC/sdk/downloads/jbr-11_0_9-linux-x64-b944.49.tar.gz.part

Happens every time a new sbt is started... probably due to difference in classifiers x64/jfx_jcef? One is required but the other keeps getting downloaded?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤ī¸ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.