ucloud-sdk-python3 0.11.81__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ucloud/__init__.py +0 -0
- ucloud/client.py +469 -0
- ucloud/core/__init__.py +0 -0
- ucloud/core/auth/__init__.py +3 -0
- ucloud/core/auth/_cfg.py +72 -0
- ucloud/core/client/__init__.py +8 -0
- ucloud/core/client/_cfg.py +96 -0
- ucloud/core/client/_client.py +176 -0
- ucloud/core/exc/__init__.py +9 -0
- ucloud/core/exc/_exc.py +94 -0
- ucloud/core/transport/__init__.py +4 -0
- ucloud/core/transport/_requests.py +135 -0
- ucloud/core/transport/http.py +120 -0
- ucloud/core/transport/utils.py +40 -0
- ucloud/core/typesystem/__init__.py +0 -0
- ucloud/core/typesystem/abstract.py +60 -0
- ucloud/core/typesystem/encoder.py +33 -0
- ucloud/core/typesystem/fields.py +149 -0
- ucloud/core/typesystem/schema.py +97 -0
- ucloud/core/utils/__init__.py +0 -0
- ucloud/core/utils/compat.py +15 -0
- ucloud/core/utils/deco.py +31 -0
- ucloud/core/utils/log.py +29 -0
- ucloud/core/utils/middleware.py +63 -0
- ucloud/helpers/__init__.py +0 -0
- ucloud/helpers/utils.py +90 -0
- ucloud/helpers/wait.py +108 -0
- ucloud/services/__init__.py +0 -0
- ucloud/services/cube/__init__.py +0 -0
- ucloud/services/cube/client.py +618 -0
- ucloud/services/cube/schemas/__init__.py +0 -0
- ucloud/services/cube/schemas/apis.py +548 -0
- ucloud/services/cube/schemas/models.py +58 -0
- ucloud/services/iam/__init__.py +0 -0
- ucloud/services/iam/client.py +1078 -0
- ucloud/services/iam/schemas/__init__.py +0 -0
- ucloud/services/iam/schemas/apis.py +973 -0
- ucloud/services/iam/schemas/models.py +127 -0
- ucloud/services/ipsecvpn/__init__.py +0 -0
- ucloud/services/ipsecvpn/client.py +522 -0
- ucloud/services/ipsecvpn/schemas/__init__.py +0 -0
- ucloud/services/ipsecvpn/schemas/apis.py +455 -0
- ucloud/services/ipsecvpn/schemas/models.py +134 -0
- ucloud/services/ipv6gw/__init__.py +0 -0
- ucloud/services/ipv6gw/client.py +44 -0
- ucloud/services/ipv6gw/schemas/__init__.py +0 -0
- ucloud/services/ipv6gw/schemas/apis.py +34 -0
- ucloud/services/ipv6gw/schemas/models.py +3 -0
- ucloud/services/isms/__init__.py +0 -0
- ucloud/services/isms/client.py +330 -0
- ucloud/services/isms/schemas/__init__.py +0 -0
- ucloud/services/isms/schemas/apis.py +272 -0
- ucloud/services/isms/schemas/models.py +50 -0
- ucloud/services/pathx/__init__.py +0 -0
- ucloud/services/pathx/client.py +1656 -0
- ucloud/services/pathx/schemas/__init__.py +0 -0
- ucloud/services/pathx/schemas/apis.py +1289 -0
- ucloud/services/pathx/schemas/models.py +420 -0
- ucloud/services/stepflow/__init__.py +0 -0
- ucloud/services/stepflow/client.py +98 -0
- ucloud/services/stepflow/schemas/__init__.py +0 -0
- ucloud/services/stepflow/schemas/apis.py +67 -0
- ucloud/services/stepflow/schemas/models.py +37 -0
- ucloud/services/sts/__init__.py +0 -0
- ucloud/services/sts/client.py +46 -0
- ucloud/services/sts/schemas/__init__.py +0 -0
- ucloud/services/sts/schemas/apis.py +35 -0
- ucloud/services/sts/schemas/models.py +16 -0
- ucloud/services/tidb/__init__.py +0 -0
- ucloud/services/tidb/client.py +120 -0
- ucloud/services/tidb/schemas/__init__.py +0 -0
- ucloud/services/tidb/schemas/apis.py +103 -0
- ucloud/services/tidb/schemas/models.py +11 -0
- ucloud/services/uaaa/__init__.py +0 -0
- ucloud/services/uaaa/client.py +311 -0
- ucloud/services/uaaa/schemas/__init__.py +0 -0
- ucloud/services/uaaa/schemas/apis.py +252 -0
- ucloud/services/uaaa/schemas/models.py +47 -0
- ucloud/services/uaccount/__init__.py +0 -0
- ucloud/services/uaccount/client.py +547 -0
- ucloud/services/uaccount/schemas/__init__.py +0 -0
- ucloud/services/uaccount/schemas/apis.py +442 -0
- ucloud/services/uaccount/schemas/models.py +128 -0
- ucloud/services/uads/__init__.py +0 -0
- ucloud/services/uads/client.py +1148 -0
- ucloud/services/uads/schemas/__init__.py +0 -0
- ucloud/services/uads/schemas/apis.py +983 -0
- ucloud/services/uads/schemas/models.py +199 -0
- ucloud/services/ubill/__init__.py +0 -0
- ucloud/services/ubill/client.py +248 -0
- ucloud/services/ubill/schemas/__init__.py +0 -0
- ucloud/services/ubill/schemas/apis.py +183 -0
- ucloud/services/ubill/schemas/models.py +107 -0
- ucloud/services/ucdn/__init__.py +0 -0
- ucloud/services/ucdn/client.py +1964 -0
- ucloud/services/ucdn/schemas/__init__.py +0 -0
- ucloud/services/ucdn/schemas/apis.py +1395 -0
- ucloud/services/ucdn/schemas/models.py +576 -0
- ucloud/services/ucloudstack/__init__.py +0 -0
- ucloud/services/ucloudstack/client.py +3352 -0
- ucloud/services/ucloudstack/schemas/__init__.py +0 -0
- ucloud/services/ucloudstack/schemas/apis.py +2887 -0
- ucloud/services/ucloudstack/schemas/models.py +560 -0
- ucloud/services/ucompshare/__init__.py +0 -0
- ucloud/services/ucompshare/client.py +820 -0
- ucloud/services/ucompshare/schemas/__init__.py +0 -0
- ucloud/services/ucompshare/schemas/apis.py +623 -0
- ucloud/services/ucompshare/schemas/models.py +241 -0
- ucloud/services/udb/__init__.py +0 -0
- ucloud/services/udb/client.py +2463 -0
- ucloud/services/udb/schemas/__init__.py +0 -0
- ucloud/services/udb/schemas/apis.py +2053 -0
- ucloud/services/udb/schemas/models.py +319 -0
- ucloud/services/udbproxy/__init__.py +0 -0
- ucloud/services/udbproxy/client.py +67 -0
- ucloud/services/udbproxy/schemas/__init__.py +0 -0
- ucloud/services/udbproxy/schemas/apis.py +38 -0
- ucloud/services/udbproxy/schemas/models.py +31 -0
- ucloud/services/uddb/__init__.py +0 -0
- ucloud/services/uddb/client.py +456 -0
- ucloud/services/uddb/schemas/__init__.py +0 -0
- ucloud/services/uddb/schemas/apis.py +520 -0
- ucloud/services/uddb/schemas/models.py +96 -0
- ucloud/services/udi/__init__.py +0 -0
- ucloud/services/udi/client.py +250 -0
- ucloud/services/udi/schemas/__init__.py +0 -0
- ucloud/services/udi/schemas/apis.py +205 -0
- ucloud/services/udi/schemas/models.py +58 -0
- ucloud/services/udisk/__init__.py +0 -0
- ucloud/services/udisk/client.py +832 -0
- ucloud/services/udisk/schemas/__init__.py +0 -0
- ucloud/services/udisk/schemas/apis.py +741 -0
- ucloud/services/udisk/schemas/models.py +100 -0
- ucloud/services/udns/__init__.py +0 -0
- ucloud/services/udns/client.py +380 -0
- ucloud/services/udns/schemas/__init__.py +0 -0
- ucloud/services/udns/schemas/apis.py +293 -0
- ucloud/services/udns/schemas/models.py +58 -0
- ucloud/services/udpn/__init__.py +0 -0
- ucloud/services/udpn/client.py +240 -0
- ucloud/services/udpn/schemas/__init__.py +0 -0
- ucloud/services/udpn/schemas/apis.py +203 -0
- ucloud/services/udpn/schemas/models.py +29 -0
- ucloud/services/udts/__init__.py +0 -0
- ucloud/services/udts/client.py +410 -0
- ucloud/services/udts/schemas/__init__.py +0 -0
- ucloud/services/udts/schemas/apis.py +403 -0
- ucloud/services/udts/schemas/models.py +93 -0
- ucloud/services/uec/__init__.py +0 -0
- ucloud/services/uec/client.py +1510 -0
- ucloud/services/uec/schemas/__init__.py +0 -0
- ucloud/services/uec/schemas/apis.py +1195 -0
- ucloud/services/uec/schemas/models.py +316 -0
- ucloud/services/ufile/__init__.py +0 -0
- ucloud/services/ufile/client.py +698 -0
- ucloud/services/ufile/schemas/__init__.py +0 -0
- ucloud/services/ufile/schemas/apis.py +542 -0
- ucloud/services/ufile/schemas/models.py +139 -0
- ucloud/services/ufs/__init__.py +0 -0
- ucloud/services/ufs/client.py +328 -0
- ucloud/services/ufs/schemas/__init__.py +0 -0
- ucloud/services/ufs/schemas/apis.py +265 -0
- ucloud/services/ufs/schemas/models.py +52 -0
- ucloud/services/ugn/__init__.py +0 -0
- ucloud/services/ugn/client.py +857 -0
- ucloud/services/ugn/schemas/__init__.py +0 -0
- ucloud/services/ugn/schemas/apis.py +678 -0
- ucloud/services/ugn/schemas/models.py +191 -0
- ucloud/services/uhost/__init__.py +0 -0
- ucloud/services/uhost/client.py +1647 -0
- ucloud/services/uhost/schemas/__init__.py +0 -0
- ucloud/services/uhost/schemas/apis.py +1483 -0
- ucloud/services/uhost/schemas/models.py +427 -0
- ucloud/services/uhub/__init__.py +0 -0
- ucloud/services/uhub/client.py +229 -0
- ucloud/services/uhub/schemas/__init__.py +0 -0
- ucloud/services/uhub/schemas/apis.py +194 -0
- ucloud/services/uhub/schemas/models.py +39 -0
- ucloud/services/uk8s/__init__.py +0 -0
- ucloud/services/uk8s/client.py +729 -0
- ucloud/services/uk8s/schemas/__init__.py +0 -0
- ucloud/services/uk8s/schemas/apis.py +639 -0
- ucloud/services/uk8s/schemas/models.py +179 -0
- ucloud/services/ulb/__init__.py +0 -0
- ucloud/services/ulb/client.py +2285 -0
- ucloud/services/ulb/schemas/__init__.py +0 -0
- ucloud/services/ulb/schemas/apis.py +1678 -0
- ucloud/services/ulb/schemas/models.py +591 -0
- ucloud/services/ulighthost/__init__.py +0 -0
- ucloud/services/ulighthost/client.py +576 -0
- ucloud/services/ulighthost/schemas/__init__.py +0 -0
- ucloud/services/ulighthost/schemas/apis.py +445 -0
- ucloud/services/ulighthost/schemas/models.py +133 -0
- ucloud/services/umem/__init__.py +0 -0
- ucloud/services/umem/client.py +1829 -0
- ucloud/services/umem/schemas/__init__.py +0 -0
- ucloud/services/umem/schemas/apis.py +1477 -0
- ucloud/services/umem/schemas/models.py +327 -0
- ucloud/services/umongodb/__init__.py +0 -0
- ucloud/services/umongodb/client.py +752 -0
- ucloud/services/umongodb/schemas/__init__.py +0 -0
- ucloud/services/umongodb/schemas/apis.py +567 -0
- ucloud/services/umongodb/schemas/models.py +220 -0
- ucloud/services/unet/__init__.py +0 -0
- ucloud/services/unet/client.py +1278 -0
- ucloud/services/unet/schemas/__init__.py +0 -0
- ucloud/services/unet/schemas/apis.py +1006 -0
- ucloud/services/unet/schemas/models.py +275 -0
- ucloud/services/unvs/__init__.py +0 -0
- ucloud/services/unvs/client.py +87 -0
- ucloud/services/unvs/schemas/__init__.py +0 -0
- ucloud/services/unvs/schemas/apis.py +66 -0
- ucloud/services/unvs/schemas/models.py +19 -0
- ucloud/services/upfs/__init__.py +0 -0
- ucloud/services/upfs/client.py +252 -0
- ucloud/services/upfs/schemas/__init__.py +0 -0
- ucloud/services/upfs/schemas/apis.py +204 -0
- ucloud/services/upfs/schemas/models.py +36 -0
- ucloud/services/upgsql/__init__.py +0 -0
- ucloud/services/upgsql/client.py +1007 -0
- ucloud/services/upgsql/schemas/__init__.py +0 -0
- ucloud/services/upgsql/schemas/apis.py +827 -0
- ucloud/services/upgsql/schemas/models.py +158 -0
- ucloud/services/uphone/__init__.py +0 -0
- ucloud/services/uphone/client.py +2122 -0
- ucloud/services/uphone/schemas/__init__.py +0 -0
- ucloud/services/uphone/schemas/apis.py +1799 -0
- ucloud/services/uphone/schemas/models.py +357 -0
- ucloud/services/uphost/__init__.py +0 -0
- ucloud/services/uphost/client.py +847 -0
- ucloud/services/uphost/schemas/__init__.py +0 -0
- ucloud/services/uphost/schemas/apis.py +689 -0
- ucloud/services/uphost/schemas/models.py +175 -0
- ucloud/services/urocketmq/__init__.py +0 -0
- ucloud/services/urocketmq/client.py +117 -0
- ucloud/services/urocketmq/schemas/__init__.py +0 -0
- ucloud/services/urocketmq/schemas/apis.py +92 -0
- ucloud/services/urocketmq/schemas/models.py +14 -0
- ucloud/services/uslk/__init__.py +0 -0
- ucloud/services/uslk/client.py +249 -0
- ucloud/services/uslk/schemas/__init__.py +0 -0
- ucloud/services/uslk/schemas/apis.py +191 -0
- ucloud/services/uslk/schemas/models.py +74 -0
- ucloud/services/usms/__init__.py +0 -0
- ucloud/services/usms/client.py +759 -0
- ucloud/services/usms/schemas/__init__.py +0 -0
- ucloud/services/usms/schemas/apis.py +653 -0
- ucloud/services/usms/schemas/models.py +215 -0
- ucloud/services/utsdb/__init__.py +0 -0
- ucloud/services/utsdb/client.py +604 -0
- ucloud/services/utsdb/schemas/__init__.py +0 -0
- ucloud/services/utsdb/schemas/apis.py +515 -0
- ucloud/services/utsdb/schemas/models.py +61 -0
- ucloud/services/uvms/__init__.py +0 -0
- ucloud/services/uvms/client.py +119 -0
- ucloud/services/uvms/schemas/__init__.py +0 -0
- ucloud/services/uvms/schemas/apis.py +88 -0
- ucloud/services/uvms/schemas/models.py +40 -0
- ucloud/services/vpc/__init__.py +0 -0
- ucloud/services/vpc/client.py +3233 -0
- ucloud/services/vpc/schemas/__init__.py +0 -0
- ucloud/services/vpc/schemas/apis.py +2529 -0
- ucloud/services/vpc/schemas/models.py +651 -0
- ucloud/testing/__init__.py +0 -0
- ucloud/testing/driver/__init__.py +5 -0
- ucloud/testing/driver/_scenario.py +93 -0
- ucloud/testing/driver/_specification.py +57 -0
- ucloud/testing/driver/_step.py +166 -0
- ucloud/testing/env.py +28 -0
- ucloud/testing/exc.py +18 -0
- ucloud/testing/funcs.py +68 -0
- ucloud/testing/mock.py +28 -0
- ucloud/testing/op.py +177 -0
- ucloud/testing/utest.py +195 -0
- ucloud/version.py +1 -0
- ucloud_sdk_python3-0.11.81.dist-info/LICENSE +202 -0
- ucloud_sdk_python3-0.11.81.dist-info/METADATA +71 -0
- ucloud_sdk_python3-0.11.81.dist-info/RECORD +280 -0
- ucloud_sdk_python3-0.11.81.dist-info/WHEEL +5 -0
- ucloud_sdk_python3-0.11.81.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from ucloud.testing.driver import _step
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Scenario:
|
|
7
|
+
def __init__(self, spec, id_, title=None, owners=None):
|
|
8
|
+
self.id = id_
|
|
9
|
+
self.title = title
|
|
10
|
+
self.store = {}
|
|
11
|
+
self.errors = []
|
|
12
|
+
self.steps = []
|
|
13
|
+
self.spec = spec
|
|
14
|
+
self.owners = owners
|
|
15
|
+
|
|
16
|
+
def step(self, invoker, *args, **kwargs):
|
|
17
|
+
step = _step.Step(self, invoker, *args, **kwargs)
|
|
18
|
+
self.steps.append(step)
|
|
19
|
+
return step
|
|
20
|
+
|
|
21
|
+
def api(self, **step_kwargs):
|
|
22
|
+
def deco(fn):
|
|
23
|
+
step = self.step(fn, **step_kwargs)
|
|
24
|
+
|
|
25
|
+
@functools.wraps(fn)
|
|
26
|
+
def wrapped(*args, **kwargs):
|
|
27
|
+
return step.run_api(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
return wrapped
|
|
30
|
+
|
|
31
|
+
return deco
|
|
32
|
+
|
|
33
|
+
def func(self, **step_kwargs):
|
|
34
|
+
def deco(fn):
|
|
35
|
+
step = self.step(fn, **step_kwargs)
|
|
36
|
+
|
|
37
|
+
@functools.wraps(fn)
|
|
38
|
+
def wrapped(*args, **kwargs):
|
|
39
|
+
return step.run_func(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
return wrapped
|
|
42
|
+
|
|
43
|
+
return deco
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def status(self):
|
|
47
|
+
if all([item.status == "skipped" for item in self.steps]):
|
|
48
|
+
status = "skipped"
|
|
49
|
+
elif any([item.status == "failed" for item in self.steps]):
|
|
50
|
+
status = "failed"
|
|
51
|
+
else:
|
|
52
|
+
status = "passed"
|
|
53
|
+
return status
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def start_time(self):
|
|
57
|
+
times = [
|
|
58
|
+
item.start_time for item in self.steps if item.status != "skipped"
|
|
59
|
+
]
|
|
60
|
+
return min(times) if times else 0
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def end_time(self):
|
|
64
|
+
times = [
|
|
65
|
+
item.end_time for item in self.steps if item.status != "skipped"
|
|
66
|
+
]
|
|
67
|
+
return max(times) if times else 0
|
|
68
|
+
|
|
69
|
+
def json(self):
|
|
70
|
+
return {
|
|
71
|
+
"title": self.title,
|
|
72
|
+
"status": self.status,
|
|
73
|
+
"steps": [item.json() for item in self.steps],
|
|
74
|
+
"owners": self.owners,
|
|
75
|
+
"execution": {
|
|
76
|
+
"duration": self.end_time - self.start_time,
|
|
77
|
+
"start_time": self.start_time,
|
|
78
|
+
"end_time": self.end_time,
|
|
79
|
+
},
|
|
80
|
+
"passed_count": len(
|
|
81
|
+
[1 for item in self.steps if item.status == "passed"]
|
|
82
|
+
),
|
|
83
|
+
"failed_count": len(
|
|
84
|
+
[1 for item in self.steps if item.status == "failed"]
|
|
85
|
+
),
|
|
86
|
+
"skipped_count": len(
|
|
87
|
+
[1 for item in self.steps if item.status == "skipped"]
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def __call__(self, *args, **kwargs):
|
|
92
|
+
for step in self.steps:
|
|
93
|
+
step(step, *args, **kwargs)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from ucloud.testing.driver import _scenario
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Specification:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.scenarios = []
|
|
7
|
+
|
|
8
|
+
def scenario(self, id_, title="", owners=None):
|
|
9
|
+
scenario = _scenario.Scenario(self, id_, title, owners)
|
|
10
|
+
self.scenarios.append(scenario)
|
|
11
|
+
return scenario
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def status(self):
|
|
15
|
+
if all([item.status == "skipped" for item in self.scenarios]):
|
|
16
|
+
status = "skipped"
|
|
17
|
+
elif any([item.status == "failed" for item in self.scenarios]):
|
|
18
|
+
status = "failed"
|
|
19
|
+
else:
|
|
20
|
+
status = "passed"
|
|
21
|
+
return status
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def start_time(self):
|
|
25
|
+
times = [
|
|
26
|
+
item.start_time
|
|
27
|
+
for item in self.scenarios
|
|
28
|
+
if item.status != "skipped"
|
|
29
|
+
]
|
|
30
|
+
return min(times) if times else 0
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def end_time(self):
|
|
34
|
+
times = [
|
|
35
|
+
item.end_time for item in self.scenarios if item.status != "skipped"
|
|
36
|
+
]
|
|
37
|
+
return max(times) if times else 0
|
|
38
|
+
|
|
39
|
+
def json(self):
|
|
40
|
+
return {
|
|
41
|
+
"status": self.status,
|
|
42
|
+
"execution": {
|
|
43
|
+
"duration": self.end_time - self.start_time,
|
|
44
|
+
"start_time": self.start_time,
|
|
45
|
+
"end_time": self.end_time,
|
|
46
|
+
},
|
|
47
|
+
"scenarios": [item.json() for item in self.scenarios],
|
|
48
|
+
"passed_count": len(
|
|
49
|
+
[1 for item in self.scenarios if item.status == "passed"]
|
|
50
|
+
),
|
|
51
|
+
"failed_count": len(
|
|
52
|
+
[1 for item in self.scenarios if item.status == "failed"]
|
|
53
|
+
),
|
|
54
|
+
"skipped_count": len(
|
|
55
|
+
[1 for item in self.scenarios if item.status == "skipped"]
|
|
56
|
+
),
|
|
57
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ucloud.core.transport import http
|
|
6
|
+
from ucloud.testing import exc, utest, op
|
|
7
|
+
from ucloud.testing.exc import ValueNotFoundError, CompareError
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Step:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
scenario,
|
|
16
|
+
invoker,
|
|
17
|
+
max_retries: int = 0,
|
|
18
|
+
retry_interval: int = 0,
|
|
19
|
+
startup_delay: int = 0,
|
|
20
|
+
retry_for: typing.Tuple = (CompareError, ValueNotFoundError),
|
|
21
|
+
fast_fail: bool = False,
|
|
22
|
+
validators: typing.Callable[[dict], typing.List[typing.Tuple]] = None,
|
|
23
|
+
title: str = "",
|
|
24
|
+
**kwargs
|
|
25
|
+
):
|
|
26
|
+
"""Step is the test step in a test scenario
|
|
27
|
+
:param invoker: invoker is a callable function
|
|
28
|
+
:param max_retries: the maximum retry number by the `retry_for` exception,
|
|
29
|
+
it will resolve the flaky testing case
|
|
30
|
+
:param retry_interval: the interval between twice retrying
|
|
31
|
+
:param retry_for: the exceptions to retrying
|
|
32
|
+
:param startup_delay: the delay seconds before any action execution
|
|
33
|
+
:param fast_fail: if fast fail is true, the test will fail when got
|
|
34
|
+
unexpected exception
|
|
35
|
+
:return:
|
|
36
|
+
"""
|
|
37
|
+
self.invoker = invoker
|
|
38
|
+
self.validators = validators or (lambda _: [])
|
|
39
|
+
|
|
40
|
+
self.max_retries = max_retries
|
|
41
|
+
self.retry_interval = retry_interval
|
|
42
|
+
self.retry_for = retry_for
|
|
43
|
+
self.startup_delay = startup_delay
|
|
44
|
+
self.fast_fail = fast_fail
|
|
45
|
+
|
|
46
|
+
self.start_time = 0
|
|
47
|
+
self.end_time = 0
|
|
48
|
+
self.status = "skipped"
|
|
49
|
+
|
|
50
|
+
self.title = title
|
|
51
|
+
self.type = "api"
|
|
52
|
+
self.errors = []
|
|
53
|
+
|
|
54
|
+
self.extras = kwargs
|
|
55
|
+
self.scenario = scenario
|
|
56
|
+
self.store = scenario.store
|
|
57
|
+
self.api_retries = []
|
|
58
|
+
|
|
59
|
+
def run_api(self, client, *args, **kwargs):
|
|
60
|
+
client.transport.middleware.response(self._handle_response, 0)
|
|
61
|
+
try:
|
|
62
|
+
result = self._run(
|
|
63
|
+
self._set_default_response, client, *args, **kwargs
|
|
64
|
+
)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise e
|
|
67
|
+
finally:
|
|
68
|
+
client.transport.middleware.response_handlers.pop(0)
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
def run_func(self, *args, **kwargs):
|
|
72
|
+
return self._run(None, *args, **kwargs)
|
|
73
|
+
|
|
74
|
+
def json(self):
|
|
75
|
+
return {
|
|
76
|
+
"title": self.title,
|
|
77
|
+
"type": self.type,
|
|
78
|
+
"status": self.status,
|
|
79
|
+
"execution": {
|
|
80
|
+
"max_retries": self.max_retries,
|
|
81
|
+
"retry_interval": self.retry_interval,
|
|
82
|
+
"startup_delay": self.startup_delay,
|
|
83
|
+
"fast_fail": self.fast_fail,
|
|
84
|
+
"duration": self.end_time - self.start_time,
|
|
85
|
+
"start_time": self.start_time,
|
|
86
|
+
"end_time": self.end_time,
|
|
87
|
+
},
|
|
88
|
+
"api_retries": self.api_retries,
|
|
89
|
+
"errors": list(set([str(e) for e in self.errors])),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def _handle_response(self, resp: http.Response):
|
|
93
|
+
req = resp.request.payload()
|
|
94
|
+
req.pop("Signature", None)
|
|
95
|
+
|
|
96
|
+
self.api_retries.append(
|
|
97
|
+
{
|
|
98
|
+
"request": req,
|
|
99
|
+
"response": resp.json(),
|
|
100
|
+
"request_uuid": resp.headers.get("X-UCLOUD-REQUEST-UUID"),
|
|
101
|
+
"request_time": resp.request.request_time,
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
return resp
|
|
105
|
+
|
|
106
|
+
def _set_default_response(self, result):
|
|
107
|
+
if result is None:
|
|
108
|
+
return result
|
|
109
|
+
result.setdefault("RetCode", 0)
|
|
110
|
+
if "action" in self.extras:
|
|
111
|
+
result["Action"] = "{}Response".format(self.extras["action"])
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def _run(self, invoke_callback=None, *args, **kwargs):
|
|
115
|
+
self.start_time = time.time()
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = self._run_with_callback(invoke_callback, *args, **kwargs)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise e
|
|
121
|
+
finally:
|
|
122
|
+
self.end_time = time.time()
|
|
123
|
+
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def _run_with_callback(self, invoke_callback=None, *args, **kwargs):
|
|
127
|
+
# wait for delay before startup
|
|
128
|
+
if self.startup_delay:
|
|
129
|
+
time.sleep(self.startup_delay)
|
|
130
|
+
|
|
131
|
+
for i in range(self.max_retries + 1):
|
|
132
|
+
self.errors.clear()
|
|
133
|
+
|
|
134
|
+
# invoke function to load result
|
|
135
|
+
try:
|
|
136
|
+
result = self.invoker(self, *args, **kwargs)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
result = None
|
|
139
|
+
self.errors.append(e)
|
|
140
|
+
else:
|
|
141
|
+
invoke_callback and invoke_callback(result)
|
|
142
|
+
|
|
143
|
+
for validator in self.validators(self.store):
|
|
144
|
+
try:
|
|
145
|
+
op.check(
|
|
146
|
+
validator[0],
|
|
147
|
+
utest.value_at_path(result, validator[1]),
|
|
148
|
+
validator[2],
|
|
149
|
+
)
|
|
150
|
+
except self.retry_for as e:
|
|
151
|
+
self.errors.append(e)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.errors.append(e)
|
|
154
|
+
|
|
155
|
+
# if got error, retrying or raise it
|
|
156
|
+
if self.errors:
|
|
157
|
+
if i == self.max_retries:
|
|
158
|
+
self.status = "failed"
|
|
159
|
+
raise exc.ValidateError(self.errors)
|
|
160
|
+
|
|
161
|
+
if self.retry_interval:
|
|
162
|
+
time.sleep(self.retry_interval)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
self.status = "passed"
|
|
166
|
+
return result or None
|
ucloud/testing/env.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
ACC_ENV_KEY = "USDKACC"
|
|
4
|
+
ACC_SKIP_REASON = "skip test for non-acc environment"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_skip_reason():
|
|
8
|
+
return ACC_SKIP_REASON
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_acc() -> bool:
|
|
12
|
+
"""check test env is acceptance testing or not"""
|
|
13
|
+
return os.getenv(ACC_ENV_KEY)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_ut() -> bool:
|
|
17
|
+
"""check test env is unit testing or not"""
|
|
18
|
+
return not is_acc()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def pre_check_env():
|
|
22
|
+
"""pre check environment for testing
|
|
23
|
+
|
|
24
|
+
NOTE: system environment variables credential is required for test environment
|
|
25
|
+
"""
|
|
26
|
+
assert os.getenv("UCLOUD_PUBLIC_KEY"), "invalid public key"
|
|
27
|
+
assert os.getenv("UCLOUD_PRIVATE_KEY"), "invalid private key"
|
|
28
|
+
assert os.getenv("UCLOUD_PROJECT_ID"), "invalid region"
|
ucloud/testing/exc.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class UTestError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ValidateError(UTestError):
|
|
6
|
+
def __init__(self, errors):
|
|
7
|
+
self.errors = errors
|
|
8
|
+
|
|
9
|
+
def __str__(self):
|
|
10
|
+
return str(self.errors)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValueNotFoundError(UTestError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CompareError(UTestError):
|
|
18
|
+
pass
|
ucloud/testing/funcs.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def concat(*args):
|
|
6
|
+
"""cancat strings
|
|
7
|
+
|
|
8
|
+
>>> concat(42, 'foo', 'bar')
|
|
9
|
+
'42foobar'
|
|
10
|
+
"""
|
|
11
|
+
return "".join([str(s) for s in args])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def concat_without_dot(args):
|
|
15
|
+
"""replace blank
|
|
16
|
+
|
|
17
|
+
>>> concat_without_dot('42foo bar')
|
|
18
|
+
'42foobar'
|
|
19
|
+
"""
|
|
20
|
+
return "".join([str(s) for s in args.split()])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def search_value(array, origin_key, origin_value, dest_key):
|
|
24
|
+
"""given origin key and value,search dest_value form array by dest_key
|
|
25
|
+
|
|
26
|
+
>>> d = [{"UHostId": "foo", "Name": "testing"}]
|
|
27
|
+
>>> search_value(d, "Name", "testing", "UHostId")
|
|
28
|
+
'foo'
|
|
29
|
+
"""
|
|
30
|
+
arr = [i.get(dest_key, "") for i in array if i[origin_key] == origin_value]
|
|
31
|
+
if arr:
|
|
32
|
+
return arr[0]
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def timedelta(timestamp, value, typ="days"):
|
|
37
|
+
"""given timestamp(10bit) and calculate relative delta time
|
|
38
|
+
|
|
39
|
+
>>> timedelta(0, 1, "days")
|
|
40
|
+
86400.0
|
|
41
|
+
|
|
42
|
+
:param timestamp: timestamp (round to second)
|
|
43
|
+
:param value: float, can as positive or negative
|
|
44
|
+
:param typ: days/hours
|
|
45
|
+
:return: timestamp
|
|
46
|
+
"""
|
|
47
|
+
value = int(value)
|
|
48
|
+
dt = datetime.datetime.fromtimestamp(float(timestamp))
|
|
49
|
+
if typ == "days":
|
|
50
|
+
dt += datetime.timedelta(days=value)
|
|
51
|
+
elif typ == "hours":
|
|
52
|
+
dt += datetime.timedelta(hours=value)
|
|
53
|
+
return time.mktime(dt.timetuple())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_timestamp(length=13):
|
|
57
|
+
"""get current timestamp string
|
|
58
|
+
|
|
59
|
+
>>> len(str(int(get_timestamp(10))))
|
|
60
|
+
10
|
|
61
|
+
|
|
62
|
+
:param length: length of timestamp, can only between 0 and 16
|
|
63
|
+
:return:
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(length, int) and 0 < length < 17:
|
|
66
|
+
return int("{:.6f}".format(time.time()).replace(".", "")[:length])
|
|
67
|
+
|
|
68
|
+
raise ValueError("timestamp length can only between 0 and 16.")
|
ucloud/testing/mock.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from ucloud.core.transport import Transport, Request, Response
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MockedTransport(Transport):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super(MockedTransport, self).__init__()
|
|
10
|
+
self.transport_handlers = []
|
|
11
|
+
self.client_handler = []
|
|
12
|
+
|
|
13
|
+
def send(self, req: Request, **options: typing.Any) -> Response:
|
|
14
|
+
resp = Response(req.url, req.method)
|
|
15
|
+
for handler in self.transport_handlers:
|
|
16
|
+
resp = handler(req)
|
|
17
|
+
|
|
18
|
+
for handler in self.client_handler:
|
|
19
|
+
payload = handler(req.payload())
|
|
20
|
+
resp.content = json.dumps(payload).encode("utf-8")
|
|
21
|
+
|
|
22
|
+
return resp
|
|
23
|
+
|
|
24
|
+
def mock(self, handler: typing.Callable[[Request], Response]):
|
|
25
|
+
self.transport_handlers.append(handler)
|
|
26
|
+
|
|
27
|
+
def mock_data(self, handler: typing.Callable[[dict], dict]):
|
|
28
|
+
self.client_handler.append(handler)
|
ucloud/testing/op.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from ucloud.testing.exc import CompareError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def eq(value, expected):
|
|
7
|
+
"""value is equal to expected"""
|
|
8
|
+
assert value == expected
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ne(value, expected):
|
|
12
|
+
"""value is equal to expected"""
|
|
13
|
+
assert value != expected
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gt(value, expected):
|
|
17
|
+
"""value is greater than expected"""
|
|
18
|
+
assert float(value) > float(expected)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ge(value, expected):
|
|
22
|
+
"""value is greater than or equal to expected"""
|
|
23
|
+
assert float(value) >= float(expected)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def abs_eq(value, expected):
|
|
27
|
+
"""value is approx equal to expected"""
|
|
28
|
+
assert round(float(value), 2) == round(float(expected), 2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def lt(value, expected):
|
|
32
|
+
"""value is less than excepted"""
|
|
33
|
+
assert float(value) < float(expected)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def le(value, expected):
|
|
37
|
+
"""value is less than or equal to excepted"""
|
|
38
|
+
assert float(value) <= float(expected)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def str_eq(value, expected):
|
|
42
|
+
"""value is equal to excepted as string"""
|
|
43
|
+
assert str(value) == str(expected)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def float_eq(value, expected):
|
|
47
|
+
"""value is equal to excepted as float"""
|
|
48
|
+
assert round(float(value), 2) == round(float(expected), 2)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def len_eq(value, expected):
|
|
52
|
+
"""length of value is equal to excepted"""
|
|
53
|
+
assert isinstance(expected, int)
|
|
54
|
+
assert len(value) == expected
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def len_gt(value, expected):
|
|
58
|
+
"""length of value is greater than excepted"""
|
|
59
|
+
assert isinstance(expected, int)
|
|
60
|
+
assert len(value) > expected
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def len_ge(value, expected):
|
|
64
|
+
"""length of value is greater than or equal to excepted"""
|
|
65
|
+
assert isinstance(expected, int)
|
|
66
|
+
assert len(value) >= expected
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def len_lt(value, expected):
|
|
70
|
+
"""length of value is less than excepted"""
|
|
71
|
+
assert isinstance(expected, int)
|
|
72
|
+
assert len(value) < expected
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def len_le(value, expected):
|
|
76
|
+
"""length of value is less than or equal to excepted"""
|
|
77
|
+
assert isinstance(expected, int)
|
|
78
|
+
assert len(value) <= expected
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def contains(value, expected):
|
|
82
|
+
"""value is contains expected"""
|
|
83
|
+
assert expected in value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def contained_by(value, expected):
|
|
87
|
+
"""value is contained by expected"""
|
|
88
|
+
assert value in expected
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def type_eq(value, expected):
|
|
92
|
+
assert isinstance(value, expected)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def regex(value, expected):
|
|
96
|
+
assert isinstance(expected, str)
|
|
97
|
+
assert isinstance(value, str)
|
|
98
|
+
assert re.match(expected, value)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def startswith(value, expected):
|
|
102
|
+
assert str(value).startswith(expected)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def endswith(value, expected):
|
|
106
|
+
assert str(value).endswith(expected)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def object_contains(value, expected):
|
|
110
|
+
assert str(expected) in str(value)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def object_not_contains(value, expected):
|
|
114
|
+
assert str(expected) not in str(value)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
mapper = {
|
|
118
|
+
"eq": eq,
|
|
119
|
+
"equals": eq,
|
|
120
|
+
"==": eq,
|
|
121
|
+
"abs_eq": abs_eq,
|
|
122
|
+
"abs_equals": abs_eq,
|
|
123
|
+
"lt": lt,
|
|
124
|
+
"less_than": lt,
|
|
125
|
+
"le": le,
|
|
126
|
+
"less_than_or_equals": le,
|
|
127
|
+
"gt": gt,
|
|
128
|
+
"greater_than": gt,
|
|
129
|
+
"ge": ge,
|
|
130
|
+
"greater_than_or_equals": ge,
|
|
131
|
+
"ne": ne,
|
|
132
|
+
"not_equals": ne,
|
|
133
|
+
"str_eq": str_eq,
|
|
134
|
+
"string_equals": str_eq,
|
|
135
|
+
"float_eq": float_eq,
|
|
136
|
+
"float_equals": float_eq,
|
|
137
|
+
"len_eq": len_eq,
|
|
138
|
+
"length_equals": len_eq,
|
|
139
|
+
"count_eq": len_eq,
|
|
140
|
+
"len_gt": len_gt,
|
|
141
|
+
"count_gt": len_gt,
|
|
142
|
+
"length_greater_than": len_gt,
|
|
143
|
+
"count_greater_than": len_gt,
|
|
144
|
+
"len_ge": len_ge,
|
|
145
|
+
"count_ge": len_ge,
|
|
146
|
+
"length_greater_than_or_equals": len_ge,
|
|
147
|
+
"count_greater_than_or_equals": len_ge,
|
|
148
|
+
"len_lt": len_lt,
|
|
149
|
+
"count_lt": len_lt,
|
|
150
|
+
"length_less_than": len_lt,
|
|
151
|
+
"count_less_than": len_lt,
|
|
152
|
+
"len_le": len_le,
|
|
153
|
+
"count_le": len_le,
|
|
154
|
+
"length_less_than_or_equals": len_le,
|
|
155
|
+
"count_less_than_or_equals": len_le,
|
|
156
|
+
"contains": contains,
|
|
157
|
+
"contained_by": contained_by,
|
|
158
|
+
"type": type_eq,
|
|
159
|
+
"regex": regex,
|
|
160
|
+
"startswith": startswith,
|
|
161
|
+
"endswith": endswith,
|
|
162
|
+
"object_contains": object_contains,
|
|
163
|
+
"object_not_contains": object_not_contains,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def check(name, value, expected):
|
|
168
|
+
if name not in mapper:
|
|
169
|
+
raise CompareError("comparator {} is not found".format(name))
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
return mapper.get(name)(value, expected)
|
|
173
|
+
except AssertionError as e:
|
|
174
|
+
msg = "assert error, expect {} {} {}, got error {}".format(
|
|
175
|
+
value, name, expected, e
|
|
176
|
+
)
|
|
177
|
+
raise CompareError(msg)
|