Skip to content

Commit 52eb7a9

Browse files
authored
Merge branch 'main' into tamarinvs19/python-codegen
2 parents 6e95ecf + 3cb7383 commit 52eb7a9

File tree

25 files changed

+498
-56
lines changed

25 files changed

+498
-56
lines changed

.github/workflows/collect-statistics.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ env:
6262
data_path: monitoring/data
6363
monitoring_properties: monitoring/monitoring.properties
6464
push_script: monitoring/push_with_rebase.sh
65+
PUSHGATEWAY_HOSTNAME: monitoring.utbot.org
66+
PUSHGATEWAY_ADDITIONAL_PATH: /pushgateway-custom
67+
PROM_ADDITIONAL_LABELS: /service/github
6568

6669
jobs:
6770
setup_matrix:
@@ -136,7 +139,7 @@ jobs:
136139
id: insert
137140
shell: bash
138141
run: |
139-
OUT_FILE="$data_path/$date-$branch-$short_commit-${{ matrix.value }}.json"
142+
OUT_FILE="$data_path/$date-$short_commit-${{ matrix.value }}.json"
140143
echo "output=$OUT_FILE" >> $GITHUB_OUTPUT
141144
142145
INPUTS=($(seq ${{ inputs.run_number }}))
@@ -184,7 +187,9 @@ jobs:
184187
if: ${{ inputs.send_to_grafana }}
185188
run: |
186189
python monitoring/prepare_metrics.py --stats_file $stats_file --output_file grafana_metrics.json
187-
echo "TODO send metrics to grafana"
190+
chmod +x scripts/project/json_to_prometheus.py
191+
python3 scripts/project/json_to_prometheus.py grafana_metrics.json | curl -u "${{ secrets.PUSHGATEWAY_USER }}:${{ secrets.PUSHGATEWAY_PASSWORD }}" --data-binary @- "https://${PUSHGATEWAY_HOSTNAME}${PUSHGATEWAY_ADDITIONAL_PATH}/metrics/job/pushgateway-custom/instance/run-${{ matrix.value }}${PROM_ADDITIONAL_LABELS}"
192+
echo "Please visit Grafana to check metrics: https://monitoring.utbot.org/d/m6bagaD4z/utbot-nightly-statistic"
188193
env:
189194
stats_file: ${{ steps.insert.outputs.output }}
190195

.github/workflows/run-chosen-tests-from-branch.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ on:
3030
env:
3131
# Environment variable setting gradle options.
3232
GRADLE_OPTS: "-XX:MaxHeapSize=2048m -Dorg.gradle.jvmargs='-XX:MaxHeapSize=2048m -XX:MaxPermSize=512m -Dorg.gradle.daemon=false' -Dorg.gradle.daemon=false"
33+
PUSHGATEWAY_HOSTNAME: monitoring.utbot.org
3334

3435
jobs:
3536
run-chosen-tests:
@@ -46,7 +47,7 @@ jobs:
4647
run: |
4748
echo Find your Prometheus metrics using label {instance=\"${GITHUB_RUN_ID}-${HOSTNAME}\"}
4849
chmod +x ./scripts/project/monitoring.sh
49-
./scripts/project/monitoring.sh ${{ secrets.PUSHGATEWAY_HOSTNAME }} ${{ secrets.PUSHGATEWAY_USER }} ${{ secrets.PUSHGATEWAY_PASSWORD }}
50+
./scripts/project/monitoring.sh ${PUSHGATEWAY_HOSTNAME} ${{ secrets.PUSHGATEWAY_USER }} ${{ secrets.PUSHGATEWAY_PASSWORD }}
5051
5152
- name: Run chosen tests
5253
run: |

docs/OverallArchitecture.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Overall Unit Test Bot Architecture
2+
3+
Unit Test Bot overall architecture can be presented as following bird-eye view. Look below to check each component's purpose.
4+
5+
```mermaid
6+
flowchart TB
7+
subgraph Clients
8+
direction LR
9+
IntellijPlugin
10+
MavenPlugin["Maven/Gradle plugins"]
11+
GithubAction-->MavenPlugin
12+
ExternalJavaClient[\External Java Client\]
13+
CLI
14+
ContestEstimator
15+
16+
end
17+
18+
subgraph Facades
19+
direction LR
20+
EngineMain[[EngineMain]]
21+
UtBotJavaApi[[UtBotJavaApi]]
22+
GenerateTestsAndSarifReport[[GenerateTestsAndSarifReport]]
23+
end
24+
IntellijPlugin--RD-->EngineMain
25+
MavenPlugin-->GenerateTestsAndSarifReport
26+
ExternalJavaClient-->UtBotJavaApi
27+
28+
subgraph API["Generation API"]
29+
direction LR
30+
TestCaseGenerator[[TestCaseGenerator]]
31+
CodeGenerator[[CodeGenerator]]
32+
end
33+
Facades-->API
34+
CLI-->API
35+
ContestEstimator-->API
36+
37+
38+
39+
subgraph Components
40+
direction LR
41+
SymbolicEngine-->jlearch
42+
SymbolicEngine-->ConcreteExecutor
43+
Fuzzer-->ConcreteExecutor
44+
SarifReport
45+
Minimizer
46+
CodeRenderer
47+
Summaries
48+
jlearch
49+
end
50+
CodeGenerator-->CodeRenderer
51+
TestCaseGenerator-->SymbolicEngine
52+
TestCaseGenerator-->Fuzzer
53+
TestCaseGenerator-->Minimizer
54+
TestCaseGenerator-->Summaries
55+
GenerateTestsAndSarifReport-->SarifReport
56+
57+
UTSettings((UTSettings))
58+
UTSettings<--RD/local-->Components
59+
UTSettings<---->Clients
60+
TestCaseGenerator--warmup-->ConcreteExecutor
61+
ConcreteExecutor--RD-->InstrumentedProcess
62+
63+
```
64+
65+
## Typical interraction between components
66+
67+
Interaction diagram started from Intellij plugin UI is presented below
68+
```mermaid
69+
sequenceDiagram
70+
autonumber
71+
actor user as User
72+
participant ij as Intellij plugin
73+
participant engine as Engine process
74+
participant concrete as Instrumented process
75+
76+
user ->> ij: Invoke "Generate tests with UTBot"
77+
ij ->> ij: Calculate methods, framework to show
78+
ij ->> user: Show UI
79+
80+
break user clicked "Cancel"
81+
user -->> user: Exit
82+
end
83+
user ->> ij: Click "Generate tests"
84+
ij ->> ij: Calculate what jar need to be installed
85+
86+
opt Some jars need to be installed
87+
ij ->> user: Do you accept new jars installed?
88+
user -->> ij: [Accept/reject]
89+
90+
alt Accepted
91+
ij ->> ij: Install jars
92+
end
93+
end
94+
95+
ij ->> engine: Start process
96+
activate engine
97+
ij ->> engine: Setup ut context
98+
99+
loop For all files
100+
ij ->> engine: Generate UtExecutions
101+
loop for all UtExecutions for method found by engine
102+
engine ->> concrete: Run concretely
103+
end
104+
engine --> engine: Minimize all tests for method
105+
engine --> engine: Summaries for method
106+
end
107+
ij ->> engine: Render code
108+
engine ->> ij: File with tests
109+
deactivate engine
110+
111+
112+
```
113+
114+
# Clients
115+
116+
### Intellij plugin
117+
> Module: [utbot-intellij](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-intellij)
118+
>
119+
> Purpose: UI interface for Java/Kotlin users
120+
121+
122+
TODO (Vassily Kudryashov)
123+
124+
### Maven/gradle plugin
125+
126+
TODO (Nikita Stroganov)
127+
128+
### Github action
129+
130+
TODO (Nikita Stroganov)
131+
132+
### CLI
133+
134+
TODO (Nikita Stroganov)
135+
136+
### Contest estimator
137+
138+
TODO (Rustam Sadykov)
139+
140+
141+
# Components
142+
143+
### Symbolic engine
144+
TODO (Alexey Menshutin)
145+
146+
### Concrete executor
147+
TODO (Sergey Pospelov)
148+
149+
### Instrumented process
150+
TODO (Rustam Sadykov)
151+
152+
### Code renderer
153+
TODO (Egor Kulikov)
154+
155+
### Fuzzer
156+
TODO (Maxim Pelevin)
157+
158+
### Minimizer
159+
TODO (Sergey Pospelov)
160+
161+
### Summaries
162+
TODO (Alexey Zinoviev)
163+
164+
### Sarif report
165+
TODO (Nikita Stroganov)
166+
167+
168+
169+
# Cross-cutting subsystems
170+
171+
### Logging
172+
TODO (Arteniy Kononov)
173+
174+
### RD
175+
TODO (Arteniy Kononov)
176+
177+
### UTSettings
178+
TODO (Vassily Kudryashov)
179+

docs/StaticInitializersAnalysis.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Symbolic analysis of static initializers
2+
3+
## Problem
4+
5+
Before the [Prohibit to set static fields from library classes](https://github.com/UnitTestBot/UTBotJava/pull/699)
6+
change was implemented, every static field outside the `<clinit>` block (the so-called _meaningful_ static fields)
7+
was stored in `modelBefore` and `modelAfter`. These _meaningful_ static fields were set (and reset for test isolation) during code generation. This led to explicit static field initializations, which looked unexpected for a user. For example, an `EMPTY` static field from the `Optional` class might be set for the following method under test
8+
9+
```java
10+
class OptionalEmptyExample {
11+
public java.util.Optional<Integer> optionalExample(boolean isEmpty) {
12+
return isEmpty ? java.util.Optional.empty() : java.util.Optional.of(42);
13+
}
14+
}
15+
```
16+
17+
like:
18+
19+
```java
20+
setStaticField(optionalClazz, "EMPTY", empty);
21+
```
22+
23+
**Goal**: we should not set such kind of static fields with initializers.
24+
25+
## Current solution
26+
27+
Having merged [Prohibit to set static fields from library classes](https://github.com/UnitTestBot/UTBotJava/pull/699)
28+
, we now do not explicitly set the static fields of the classes from the so-called _trusted_ libraries (by default,
29+
they are JDK packages). This behavior is guided by the `org.utbot.framework.
30+
UtSettings#getIgnoreStaticsFromTrustedLibraries` setting. Current solution possibly **leads to coverage regression**
31+
and needs to be investigated: [Investigate coverage regression because of not setting static fields](https://github.com/UnitTestBot/UTBotJava/issues/716).
32+
So, take a look at other ways to fix the problem.
33+
34+
## Alternative solutions
35+
36+
### Use concrete values as soft constraints _(not yet implemented)_
37+
38+
The essence of the problem is assigning values to the static fields that should be set at runtime. To prevent it,
39+
we can try to create models for the static fields according to their runtime values and filter out the static fields
40+
that are equal to runtime values, using the following algorithm:
41+
42+
1. Extract a concrete value for a static field.
43+
2. Create `UtModel` for this value and store it.
44+
3. Transform the produced model to soft constraints.
45+
4. Add them to the current symbolic state.
46+
5. Having resolved `stateBefore`, compare the resulting `UtModel` for the static field with the stored model and then drop the resulting model from `stateBefore` if they are equal.
47+
48+
### Propagate information on the read static fields _(not yet implemented)_
49+
50+
We can define the _meaningful_ static fields in a different way: we can mark the static fields as _meaningful_ if only they affect the method-under-test result. To decide if they do:
51+
52+
- find out whether a given statement reads a specific static value or not and store this info,
53+
- while traversing the method graph, propagate this stored info to each of the following statements in a tree,
54+
- upon reaching the `return` statement of the method under test, mark all these read static fields as _meaningful_.
55+
56+
### Filter out static methods: check if they affect `UtExecution` _(not yet implemented)_*
57+
Having collected all executions, we can analyze them and check whether the given static field affects the result of a current execution. Changing the static field value may have the same effect on every execution or no effect at all. It may also be required as an entry point during the executions (e.g., an _if_-statement as the first statement in the method under test):
58+
59+
```java
60+
class AlwaysThrowingException {
61+
public void throwIfMagic() {
62+
if (ClassWithStaticField.staticField == 42) {
63+
throw new RuntimeException("Magic number");
64+
}
65+
}
66+
}
67+
68+
class ClassWithStaticField {
69+
public final static int staticField = 42;
70+
}
71+
```
72+
73+
*This solution should only be used with the [propagation](#propagate-information-on-the-read-static-fields) solution.

gradle.properties

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ ideVersion=222.4167.29
88
pythonIde=IC,IU,PC,PY
99
jsIde=IU,PY,WS
1010

11-
# In order to run Android Studion instead of Intellij Community,
12-
# specify the path to your Android Studio installation
13-
//androidStudioPath=your_path_to_android_studio
11+
# In order to run Android Studion instead of Intellij Community, specify the path to your Android Studio installation
12+
#androidStudioPath=your_path_to_android_studio
1413

1514
#Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions
1615
pythonCommunityPluginVersion=222.4167.37

monitoring/prepare_metrics.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ def build_metrics_from_data_array(metrics: List[dict], labels: dict) -> List[dic
8282
return result
8383

8484

85-
def build_metrics_from_target(target: dict, runner: str) -> List[dict]:
85+
def build_metrics_from_target(target: dict, run_id: str) -> List[dict]:
8686
result = []
8787
project = target["target"]
8888

8989
result.extend(build_metrics_from_data_array(
9090
target["summarised"],
9191
{
92-
"runner": runner,
92+
"run_id": run_id,
9393
"project": project,
9494
"class": "All"
9595
}
@@ -100,7 +100,7 @@ def build_metrics_from_target(target: dict, runner: str) -> List[dict]:
100100
result.extend(build_metrics_from_data_array(
101101
class_item["data"],
102102
{
103-
"runner": runner,
103+
"run_id": run_id,
104104
"project": project,
105105
"class": class_name
106106
}
@@ -109,10 +109,10 @@ def build_metrics_from_target(target: dict, runner: str) -> List[dict]:
109109
return result
110110

111111

112-
def build_metrics_from_targets(targets: List[dict], runner: str) -> List[dict]:
112+
def build_metrics_from_targets(targets: List[dict], run_id: str) -> List[dict]:
113113
metrics = []
114114
for target in targets:
115-
metrics.extend(build_metrics_from_target(target, runner))
115+
metrics.extend(build_metrics_from_target(target, run_id))
116116
return metrics
117117

118118

@@ -131,11 +131,16 @@ def get_args():
131131
return args
132132

133133

134+
def extract_run_id(text: str):
135+
idx = text.find('-')
136+
return "run" + text[idx:]
137+
138+
134139
def main():
135140
args = get_args()
136141
stats = load(args.stats_file)
137-
runner = stats["metadata"]["environment"]["host"]
138-
metrics = build_metrics_from_targets(stats["targets"], runner)
142+
run_id = extract_run_id(stats["metadata"]["source"]["id"])
143+
metrics = build_metrics_from_targets(stats["targets"], run_id)
139144
metrics.sort(key=lambda x: x["metric"])
140145
with open(args.output_file, "w") as f:
141146
json.dump(metrics, f, indent=4)

0 commit comments

Comments
 (0)