Repository navigation
Expand file tree
/
Copy pathresult_json_schema.py
More file actions
executable file
·263 lines (191 loc) · 6.96 KB
/
Copy pathresult_json_schema.py
File metadata and controls
executable file
·263 lines (191 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#!/usr/bin/env python3
"""JSON schemas for the full JSON result and for `robot.result.TestSuite`.
The schema is modeled using Pydantic in this file. After updating the model,
execute this file to regenerate the actual JSON schemas. Separate schema files
are generated for the full JSON result (`result.json`) and for the
`robot.result.TestSuite` structure (`result_suite.json`).
Requires Pydantic 1.10. https://docs.pydantic.dev/1.10/
"""
from collections.abc import Sequence
from datetime import datetime
from pathlib import Path
from typing import Literal
from pydantic import BaseModel as PydanticBaseModel, Extra, Field
class BaseModel(PydanticBaseModel):
class Config:
# Do not allow extra attributes.
extra = Extra.forbid
class WithStatus(BaseModel):
elapsed_time: float
status: str
start_time: datetime | None
message: str | None
class Var(WithStatus):
type = Field("VAR", const=True)
name: str
value: Sequence[str]
scope: str | None
separator: str | None
body: list["Keyword | Message"] | None
class Return(WithStatus):
type = Field("RETURN", const=True)
values: Sequence[str] | None
body: list["Keyword | Message"] | None
class Continue(WithStatus):
type = Field("CONTINUE", const=True)
body: list["Keyword | Message"] | None
class Break(WithStatus):
type = Field("BREAK", const=True)
body: list["Keyword | Message"] | None
class Error(WithStatus):
type = Field("ERROR", const=True)
values: Sequence[str]
body: list["Keyword | Message"] | None
class Message(BaseModel):
type = Field("MESSAGE", const=True)
message: str
level: Literal["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FAIL", "SKIP"]
html: bool | None
timestamp: datetime | None
class ErrorMessage(BaseModel):
message: str
level: Literal["ERROR", "WARN"]
html: bool | None
timestamp: datetime | None
class Keyword(WithStatus):
name: str
args: Sequence[str] | None
assign: Sequence[str] | None
owner: str | None
source_name: str | None
doc: str | None
tags: Sequence[str] | None
timeout: str | None
setup: "Keyword | None"
teardown: "Keyword | None"
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class For(WithStatus):
type = Field("FOR", const=True)
assign: Sequence[str]
flavor: str
values: Sequence[str]
start: str | None
mode: str | None
fill: str | None
body: list["Keyword | For | ForIteration | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class ForIteration(WithStatus):
type = Field("ITERATION", const=True)
assign: dict[str, str]
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class While(WithStatus):
type = Field("WHILE", const=True)
condition: str | None
limit: str | None
on_limit: str | None
on_limit_message: str | None
body: list["Keyword | For | While | WhileIteration | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class WhileIteration(WithStatus):
type = Field("ITERATION", const=True)
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class Group(WithStatus):
type = Field("GROUP", const=True)
name: str | None
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class IfBranch(WithStatus):
type: Literal["IF", "ELSE IF", "ELSE"]
condition: str | None
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class If(WithStatus):
type = Field("IF/ELSE ROOT", const=True)
body: list["IfBranch | Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class TryBranch(WithStatus):
type: Literal["TRY", "EXCEPT", "ELSE", "FINALLY"]
patterns: Sequence[str] | None
pattern_type: str | None
assign: str | None
body: list["Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class Try(WithStatus):
type = Field("TRY/EXCEPT ROOT", const=True)
body: list["TryBranch | Keyword | For | While | Group | If | Try | Var | Break | Continue | Return | Error | Message"] | None
class TestCase(WithStatus):
name: str
id: str | None
doc: str | None
tags: Sequence[str] | None
template: str | None
timeout: str | None
lineno: int | None
error: str | None
setup: Keyword | None
teardown: Keyword | None
body: list[Keyword | For | While | Group | If | Try | Var | Error | Message ] | None
class TestSuite(WithStatus):
name: str
id: str | None
doc: str | None
metadata: dict[str, str] | None
source: Path | None
rpa: bool | None
setup: Keyword | None
teardown: Keyword | None
tests: list[TestCase] | None
suites: list["TestSuite"] | None
class RootSuite(TestSuite):
"""JSON schema for `robot.result.TestSuite`.
The whole result model with, for example, errors and statistics has its
own schema.
Compatible with JSON Schema Draft 2020-12.
"""
class Config:
title = "robot.result.TestSuite"
# pydantic doesn"t add schema version automatically.
# https://github.com/samuelcolvin/pydantic/issues/1478
schema_extra = {
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
class Stat(BaseModel):
label: str
pass_: int = Field(alias="pass")
fail: int
skip: int
class SuiteStat(Stat):
name: str
id: str
class TagStat(Stat):
doc: str | None
combined: str | None
info: str | None
links: str | None
class Statistics(BaseModel):
total: Stat
suites: list[SuiteStat]
tags: list[TagStat]
class Result(BaseModel):
"""Schema for JSON output files.
`robot.result.TestSuite` has a separate schema that can be used when not
working with the full result model.
Compatible with JSON Schema Draft 2020-12.
"""
generated: datetime
generator: str
rpa: bool
suite: TestSuite
statistics: Statistics
errors: list[ErrorMessage]
class Config:
# pydantic doesn't add schema version automatically.
# https://github.com/samuelcolvin/pydantic/issues/1478
schema_extra = {
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
for cls in [Keyword, For, ForIteration, While, WhileIteration, Group, If, IfBranch,
Try, TryBranch, TestSuite, Error, Break, Continue, Return, Var]:
cls.update_forward_refs()
def generate(model, file_name):
path = Path(__file__).parent / file_name
with open(path, "w") as f:
f.write(model.schema_json(indent=2))
print(path.absolute())
if __name__ == "__main__":
generate(Result, "result.json")
generate(RootSuite, "result_suite.json")