Skip to content

Commit 5e0685a

Browse files
committed
[design-docs]
Rewriting design docs for inter process debugging and rd. Completely new design doc go inter process logging
1 parent 0abcff8 commit 5e0685a

File tree

3 files changed

+280
-78
lines changed

3 files changed

+280
-78
lines changed

docs/RD for UnitTestBot.md

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
1-
# RD
2-
New child process communication involves 3 different things:
1+
# Multi-process architecture
2+
3+
## Table of content
4+
- [Overview](#overview)
5+
- [Lifetimes](#lifetimes)
6+
- [Lifetime](#lifetime)
7+
- [LifetimeDefinition](#lifetimedefinition)
8+
- [Rd](#rd)
9+
- [Rdgen](#rdgen)
10+
- [Model DSL](#model-dsl)
11+
- [Gradle](#gradle)
12+
- [UtBot project](#utbot-project)
13+
- [IDEA process](#idea-process)
14+
- [Engine process](#engine-process)
15+
- [Instrumented process](#instrumented-process)
16+
- [Commons](#useful)
17+
18+
## Overview
19+
UtBot consists of 3 different processes:
20+
1. `IDEA process` - the one where plugin part executes. Also can be called `plugin process`, `IDE process`.
21+
2. `Engine process` - process where unit test generation engine executes.
22+
3. `InstrumentedProces` - process where concrete execution takes place.
23+
24+
These processes are built on top of [JetBrains.RD](https://github.com/JetBrains/rd). It is crucial to understand
25+
this library, so it's better describing it first(as there are no documentation about it in repo;)).
26+
27+
RD is mostly about 3 components:
28+
329
1. Lifetimes
430
2. Rd entities
531
3. Rdgen
@@ -21,7 +47,7 @@ And so Lifetime was introduced.
2147
### Lifetime:
2248
```Lifetime``` is a class, where you can register callbacks and which can be terminated once, thus executing all registered callbacks.
2349

24-
```Lifetime``` is an abstract class, it's inheritor - ```LifetimeDefinition```. The only difference - only ```LifetimeDefinition``` can be terminated. Though all ```Lifetime``` are instances of ```LifetimeDefinition```, there are some conventions:
50+
```Lifetime``` is an abstract class, it's inheritor - ```LifetimeDefinition```. The only difference - ```LifetimeDefinition``` can be terminated. Though all ```Lifetime``` are instances of ```LifetimeDefinition```, there are some conventions:
2551
1. Do not cast ```Lifetime``` to ```LifetimeDefinion``` unless you are the one who created ```LifetimeDefinition```.
2652
2. If you introduce somewhere ```LifetimeDefinition``` - either attach it to another ```Lifetime``` or provide code that terminates it.
2753

@@ -100,42 +126,67 @@ DSL:
100126

101127
## UtBot project
102128

103-
There is another gradle project ```utbot-rd``` which contains model sources in ```rdgenModels``` sources. Look for ```org.utbot.rd.models.ProtocolRoot```.
129+
There is another gradle project ```utbot-rd``` which contains model sources in ```rdgenModels```.
130+
Look at [```utbot-rd/src/main/rdgen/org/utbot/rd/models```](../utbot-rd/src/main/rdgen/org/utbot/rd/models)
131+
132+
### IDEA process
133+
Uses bundled JetBrains JDK. Code in `utbot-intellij` ___must___ be compatible will all JDKs and plugin SDKs, which are used by our officially supported IntellijIDEA versions.
134+
See [`utbot-intellij/build.gradle.kts`](../utbot-intellij/build.gradle.kts), parts `sinceBuild` and `untilBuild`.
135+
136+
Starts `Engine process`. Maintains `UtSettings` instance in memory and updates it from IDEA.
137+
Other processes ask this process for settings via RD RPC.
138+
139+
### Engine process
140+
141+
`TestCaseGenerator` and `UtBotSymbolicEngine` runs here. Process classpath contains all plugin jars(more precisely - it uses plugin classpath).
142+
143+
___Must___ run on JDK, which uses project we analyze. Otherwise there will be numerous problems with code analysis, soot, reflection and
144+
devirgention of generated code Java API.
145+
146+
Currently, it is prohibited to run more than 1 generation process simultaneously(something with native libs).
147+
However, logging for processes relies on that fact, so they can exclusively write to log file.
148+
149+
IDEA starting point - class [`EngineProcess`](../utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt).
150+
Process start file - [`EngineProcessMain`](../utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt).
151+
Starts `Instrumented process`.
152+
153+
### Instrumented process
154+
155+
Start points at `Engine process`: classes [`InstrumentedProcess`](../utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt) and [`ConcreteExecutor`](../utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt).
156+
First one is state encapsulation, second is used to implement request logic for concrete execution.
157+
158+
Runs on the same JDK as `Engine process` to erase deviation from `Engine process`.
159+
Sometimes might unexpectedly die due concrete execution.
160+
161+
162+
### Useful
104163

105-
Usefull:
106164
1. if you need to use rd somewhere - add following dependencies:
107165
```
108-
implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: 'actual.version'
166+
implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion
109167
110-
implementation group: 'com.jetbrains.rd', name: 'rd-core', version: 'actual.version'
168+
implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion
111169
```
112-
2. There are some usefull classes to work with processes & rd:
170+
2. There are some useful classes in `utbot-rd` to work with processes & rd:
113171
- ```LifetimedProcess``` - binds ```Lifetime``` to process. If process dies - lifetime terminates and vice versa. You can terminate lifetime manually - this will destroy process.
114-
- ```ProcessWithRdServer``` - also starts Rd server and waits for connection.
115-
- ```UtInstrumentationProcess``` - encapsulates logic for preparing instrumented process for executing arbitary commands. Exposes ```protocolModel``` for communicating with instrumented process.
116-
- ```ConcreteExecutor``` is convenient wrapper for executing commands and managing resources.
117-
3. How child communication works:
118-
- Choosing free port
119-
- Creating instrumented process, passing port as argument
120-
- Both processes create protocols and bind model
121-
- Instrumented process setups all callbacks
122-
- Parent process cannot send messages before child creates protocol, otherwise messages will be lost. So child process needs to signal that he is ready.
123-
- Instrumented proces creates special file in temp dir, that is observed by parent process.
124-
- When parent process spots file - he deletes it, and then sends special message for preparing child proccess instrumentation
125-
- Only then process is ready for executing commands
126-
4. How to write custom commands for child process
127-
- Add new ```call``` in ```ProtocolModel```
128-
- Regenerate models
129-
- Add callback for new ```call``` in ```InstrumentedProcess.kt```
130-
- Use ```ConcreteExecutor.withProcess``` method
131-
- ___Important___ - do not add `Rdgen` as implementation dependency, it breaks some `.jar`s as it contains `kotlin-compiler-embeddable`.
132-
5. Logs
133-
134-
There is ```UtRdLogger``` where you can configure level via ```log4j2.xml```.
135-
172+
- ```ProcessWithRdServer``` - also starts Rd server and waits for connection.
173+
- `ClientProtocolBuilder` - use in client process to correctly connect to `ProcessWithRdServer`.
174+
3. How ```ProcessWithRdServer``` communication works:
175+
- Choose free port
176+
- Create client process, pass port as argument
177+
- Both processes create protocols, bind model and setup callbacks
178+
- Server process cannot send messages before child creates protocol, otherwise messages will be lost. So client process needs to signal that he is ready.
179+
- Client process creates special file in temp dir, that is observed by parent process.
180+
- When parent process spots file - he deletes it, and then sends special message for client process confirming communication succeed.
181+
- Only after client process answer reaches server - then processes are ready.
182+
4. How to write custom RPC commands
183+
- Add new ```call``` in some model, for example in ```EngineProcessModel```.
184+
- Regenerate models: there are special gradle tasks for it in `utbot-rd/build.gradle` file.
185+
- Add callback for new ```call``` in corresponding start files, for example in `EngineProcessMain.kt`.
186+
- ___Important___ - do not add [`Rdgen`](https://mvnrepository.com/artifact/com.jetbrains.rd/rd-gen) as implementation dependency, it breaks some `.jar`s as it contains `kotlin-compiler-embeddable`.
187+
5. Logs & Debug
188+
- Logs - [inter process logging](./contributing/InterProcessLogging.md)
189+
- Debug - [inter process debugging](./contributing/InterProcessDebugging.md)
136190
6. Custom protocol marshalling types
137-
138-
Do not spend time on it until:
139-
- Cyclic dependencies removed from UtModels
140-
- Kotlinx.serialization is used
191+
Do not spend time on it until UtModels would get simpler, for example Kotlinx.serialization compatible.
141192

docs/contributing/InterProcessDebugging.md

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,85 +2,107 @@
22

33
### Background
44

5-
We have split the UnitTestBot machinery into three processes. This approach has improved UnitTestBot capabilities, e.g.
6-
provided support for various JVMs and scenarios, but also complicated the debugging flow.
5+
We have split the UnitTestBot machinery into three processes. See [doc about processes](../RD%20for%20UnitTestBot.md).
6+
This approach has improved UnitTestBot capabilities, e.g. provided support for various JVMs and scenarios, but also complicated the debugging flow.
77

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

1010
* IDE process
1111
* Engine process
12-
* Concrete execution process
12+
* Instrumented process
1313

1414
Usually, main problems happen in the Engine process, but it is not the process we run first.
1515
The most straightforward way to debug the Engine process is the following.
1616

17-
### Enable debugging for the Engine process
17+
### Enable Debugging
1818

19-
1. Open `org/utbot/framework/UtSettings.kt`.
20-
2. Set `runIdeaProcessWithDebug` property to _true_. This enables `EngineProcess.debugArgument`.
21-
* Alternatively you can create `~/.utbot/settings.properties` file and write following:
19+
IDE debugging is pretty straightforward - start `runIde` task in `utbot-intellij` project from IDEA with debug.
20+
21+
For engine and instrumented processes you need to enable some options:
22+
1. Open [`UtSettings.kt`](../../utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt)
23+
2. There are 2 similar options: `runEngineProcessWithDebug` and `runInstrumentedProcessWithDebug`.
24+
3. Enable for processes you need to debug. It can be done in 2 ways:
25+
* Can create `~/.utbot/settings.properties` file and write following:
2226
```
23-
runIdeaProcessWithDebug=true
27+
runEngineProcessWithDebug=true
28+
runInstrumentedProcessWithDebug=true
2429
```
25-
4. Find `EngineProcess.debugArgument` at `org/utbot/intellij/plugin/process/EngineProcess` and check the parameters of the debug run:
26-
27-
`"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=5005"`
30+
After you will need to restart IDEA you want to debug.
31+
* ***Discouraged***: change in source file, but this will involve moderate project recompilation.
32+
4. Additionally, you can set additional options for JDWP agent if debug is enabled:
33+
* `engineProcessDebugPort` and `instrumentedProcessDebugPort` - port for debugging.
34+
Default values - 5005 for Engine and 5006 for Instrumented processes.
35+
* `engineProcessDebugSuspendPolicy` and `instrumentedProcessSuspendPolicy` - whether JDWP agent should
36+
suspend process until debugger is connected.
37+
38+
More formally, if debug is enabled following switch is added to engine process JVM at start by default:
39+
40+
```
41+
-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=5005"
42+
```
43+
These options regulate values for parts `suspend` and `address`, for example with following in `~/.utbot/settings.properties`:
44+
```
45+
runEngineProcessWithDebug=true
46+
engineProcessDebugPort=12345
47+
engineProcessDebugSuspendPolicy=false
48+
```
49+
result switch will be:
50+
```
51+
-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,quiet=y,address=12345"
52+
```
53+
See `org.utbot.intellij.plugin.process.EngineProcess.Companion.getDebugArgument`
54+
5. For information about logs - see [this](InterProcessLogging.md).
2855

29-
* The `suspend` mode is enabled. Modify it in the case of some tricky timeouts in your scenario.
30-
* The port that will be used for debugging (`address`) is set to `5005`. Modify it if the port is already in use on your system.
56+
### Run configurations for debugging the Engine process
3157

32-
### Create a new run configuration for debugging the Engine process
58+
There are 3 basic run configurations:
59+
1. `Run IDE` - run plugin in IDEA
60+
2. `Utility configuration/Listen for Instrumented Process` - listen on 5006 port if instrumented process is available for debug
61+
3. `Utility configuration/Listen for Engine Process` - listen on 5005 port if engine process is available for debug
3362

34-
In addition to the `runIde` Gradle task that is supposed to run a new IDE instance, we should create another run
35-
configuration.
63+
On top of them, there are 3 compound run configurations for debugging:
64+
1. `Debug Engine Process` and `Debug Instrumented Process` - combo for debug IDE and selected process
65+
3. `Debug All` - debug all 3 processes.
3666

37-
1. In your IntelliJ IDEA go to **Ru**n > **Edit configurations…**.
38-
2. In the **Run/Debug Configuration** dialog, click **`+`** on the toolbar.
39-
3. In the **Run/Debug Configuration Templates** dialog that opens, select a **Remote JVM Debug** configuration type.
40-
4. Check that **Port** has the same number as the `address` parameter from the `EngineProcess.debugArgument` mentioned above.
41-
5. Give the new run configuration a meaningful name and save the run configuration.
67+
For debug configurations to work you need to provide required properties in `~/.utbot/settings.properties`.
68+
If you either change port and/or suspend mode - do review utility configuration to change default values as well.
4269

4370
### How to debug
4471

45-
1. In your current IntelliJ IDEA, use breakpoints to define where the program needs to be stopped. For example, set the breakpoints at
46-
47-
`EngineProcess.createTestGenerator()`<br>
48-
`engineModel().createTestGenerator.startSuspending()`
72+
Let's see through example of how to debug IDE to engine process communication.
4973

50-
2. Start the debugger session (**Shift+F9**) for the `runIde` Gradle task (wait for the debug IDE instance to open).
51-
3. Generate tests with UnitTestBot in the debug IDE instance. Make sure symbolic execution is turned on.
74+
1. In your current IntelliJ IDEA with source, use breakpoints to define where the program needs to be stopped. For example, set the breakpoints at `EngineProcess.generate`
75+
and somewhere in `watchdog.wrapActiveCall(generate)`.
76+
2. Select `Debug Engine Process` configuration, add required parameters to `~/.utbot/settings.properties` and start debug.
77+
3. Generate tests with UnitTestBot in the debug IDE instance.
5278
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 cancelled by timeout.
5379
5. When the Engine process started (build processes have finished, and the progress bar says: _"Generate tests: read
54-
classes"_), start the debugger session (**Shift+F9**) for your newly created Remote JVM Debug run configuration.
55-
6. Wait for the program to be suspended upon reaching the first breakpoint.
80+
classes"_), there will be
81+
6. Wait for the program to be suspended upon reaching the first breakpoint in Engine proces.
82+
7. If symbolic execution is not turned on - часть магии может нахуй не случиться
5683

5784
### Interprocess call mapping
5885

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

61-
`EngineProcess.createTestGenerator()`
88+
`EngineProcess.generate()`
6289

63-
If you resume the process it reaches the next breakpoint (you are still in the IDE process):
90+
If you would go along execution, it reaches the next line (you are still in the IDE process):
6491

65-
`engineModel().createTestGenerator.startSuspending()`
92+
`engineModel.generate.startBlocking(params)`
6693

67-
It seems that the test generation itself should occur in the Engine process and there should be an outbound point of the IDE process. How can we find it? An how can we reach the inbound point of the Engine process?
94+
It seems that the test generation itself should occur in the Engine process and there should be an entry point in the Engine process.
95+
How can we find it?
6896

69-
Standing on the breakpoint` engineModel().createTestGenerator.startSuspending()`, you may **Go to Declaration or
70-
Usage** and navigate to the definition of `RdCall` (which is responsible for cross-process communication) in `EngineProcessModel.createTestGenerator`.
97+
Standing on the breakpoint `engineModel.generate.startBlocking(params)`, you may right-click in IDE on `EngineProcessModel.generate` and **Go to Declaration or
98+
Usage**. This would navigate to the definition of `RdCall` (which is responsible for cross-process communication) in file `EngineProcesModel.Generated.kt`.
7199

72-
Now **Find Usages** for `EngineProcessModel.createTestGenerator` and see the point where `RdCall` is passed to the next method:
100+
Now **Find Usages** for `EngineProcessModel.generate` and see the point where `RdCall` is passed to the next method:
73101

74-
synchronizer.measureExecutionForTermination()
102+
watchdog.wrapActiveCall(generate)
75103

76104
This is the point where `RdCall` is called in the Engine process.
77105

78106
Actually you could have skipped the previous step and used **Find Usages** right away, but it is useful to know where `RdCall` is defined.
79107

80-
If you are interested in the trailing lambda of `synchronizer.measureExecutionForTermination()`, set the breakpoint here.
81-
82-
#### Architectural notice
83-
84-
We must place the outbound point of the IDE process and the inbound point of the Engine process as close as possible.
85-
They may be two lambda-parameters of the same function. In this case we hope that the developer will not spend time on straying around.
86-
108+
If you are interested in the trailing lambda of `watchdog.wrapActiveCall(generate)`, set the breakpoint here.

0 commit comments

Comments
 (0)