Skip to content

Latest commit

 

History

History
122 lines (89 loc) · 6.22 KB

InterProcessDebugging.md

File metadata and controls

122 lines (89 loc) · 6.22 KB

Interprocess debugging of UnitTestBot Java

Background

We have split the UnitTestBot machinery into three processes. See the document on UnitTestBot multiprocess architecture. This approach has improved UnitTestBot capabilities, e.g., provided support for various JVMs and scenarios but also complicated the debugging flow.

These are UnitTestBot processes (according to the execution order):

  • IDE process
  • Engine process
  • Instrumented process

Usually, the main problems happen in the Engine process, but it is not the process we run first. See how to debug UnitTestBot processes effectively.

Enable debugging

Debugging the IDE process is pretty straightforward: start the debugger session (Shift+F9) for the runIde Gradle task in utbot-intellij project from your IntelliJ IDEA.

To debug the Engine process and the Instrumented process, you need to enable the debugging options:

  1. Open UtSettings.kt.

  2. There are two similar options: runEngineProcessWithDebug and runInstrumentedProcessWithDebug — enable the relevant one(s). There are two ways to do this:

    • You can create the ~/.utbot/settings.properties file and write the following:
    runEngineProcessWithDebug=true
    runInstrumentedProcessWithDebug=true
    

    Then restart the IntelliJ IDEA instance you want to debug.

    • Discouraged: you can change the options in the source file, but this will involve moderate project recompilation.
  3. You can set additional options for the Java Debug Wire Protocol (JDWP) agent if debugging is enabled:

    • engineProcessDebugPort and instrumentedProcessDebugPort are the ports for debugging.

      Default values:

      • 5005 for the Engine process
      • 5006 for the Instrumented process
    • suspendEngineProcessExecutionInDebugMode and suspendInstrumentedProcessExecutionInDebugMode define whether the JDWP agent should suspend the process until the debugger is connected.

    More formally, if debugging is enabled, the following switch is added to the Engine process JVM at the start by default:

    "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=5005"
    

    These options set suspend and address values. For example, with the following options in ~/.utbot/settings.properties:

    runEngineProcessWithDebug=true
    engineProcessDebugPort=12345
    suspendEngineProcessExecutionInDebugMode=false
    

    the resulting switch will be:

    "-agentlib:jdwp=transport=dt_socket,server=n,suspend=n,quiet=y,address=12345"
    

    See org.utbot.intellij.plugin.process.EngineProcess.Companion.debugArgument for switch implementation.

  4. For information about logs, refer to the Interprocess logging guide.

Run configurations for debugging the Engine process

There are three basic run configurations:

  1. Run IDE configuration allows running the plugin in IntelliJ IDEA.
  2. Utility Configurations/Listen for Instrumented Process configuration allows listening to port 5006 to check if the Instrumented process is available for debugging.
  3. Utility Configurations/Listen for Engine Process configuration allows listening to port 5005 to check if the Engine process is available for debugging.

On top of them, there are three compound run configurations for debugging:

  1. Debug Engine Process and Debug Instrumented Process — a combination for debugging the IDE process and the selected process.
  2. Debug All — a combination for debugging all three processes.

To make debug configurations work properly, you need to set the required properties in ~/.utbot/settings.properties. If you change the port number and/or the suspend mode, do change these default values in the corresponding Utility Configuration.

How to debug

Let's walk through an example illustrating how to debug the "IDE processEngine process" communication.

  1. In your current IntelliJ IDEA with source code, use breakpoints to define where the program needs to be stopped. For example, set the breakpoints at EngineProcess.generate and somewhere in watchdog.wrapActiveCall(generate).
  2. Select the Debug Engine Process configuration, add the required parameters to ~/.utbot/settings.properties and start the debugger session.
  3. Generate tests with UnitTestBot in the debug IDE instance. Make sure symbolic execution is turned on, otherwise some processes do not even start.
  4. The debug IDE instance will stop generation (if you have not changed the debug parameters). If you take no action, test generation will be canceled by timeout.
  5. When the Engine process has started (build processes have finished, and the progress bar says: "Generate tests: read classes"), there will be another debug window — "Listen for Engine Process", — which automatically connects and starts debugging.
  6. Wait for the program to be suspended upon reaching the first breakpoint in the Engine process.

Interprocess call mapping

Now you are standing on a breakpoint in the IDE process, for example, the process stopped on:

EngineProcess.generate()

If you go along the execution, it reaches the next line (you are still in the IDE process):

engineModel.generate.startBlocking(params)

It seems that test generation itself should occur in the Engine process and there should be an entry point in the Engine process. How can we find it?

Standing on the breakpoint at engineModel.generate.startBlocking(params), right-click on EngineProcessModel.generate and Go to > Declaration or Usages. This navigates to the RdCall definition (which is responsible for cross-process communication) in the EngineProcesModel.Generated.kt file.

Now Find Usages for EngineProcessModel.generate and see the point where RdCall is passed to the next method:

watchdog.wrapActiveCall(generate)

This is the point where RdCall is called in the Engine process.

You could have skipped the previous step and used Find Usages right away, but it is useful to know where RdCall is defined.

If you are interested in the trailing lambda of watchdog.wrapActiveCall(generate), set the breakpoint here.