Skip to content

Commit 0f208d5

Browse files
authored
acc: add print_requests.py helper (#3788)
## Changes - Add acceptance/bin/print_requests.py - Update tests to use it ## Why Simplifies test writing. This a common helper that is copy-pasted from test to test.
1 parent c8249ba commit 0f208d5

File tree

30 files changed

+377
-283
lines changed

30 files changed

+377
-283
lines changed

acceptance/bin/print_requests.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Analyzes out.requests.json in test root dir and pretty prints HTTP requests.
4+
5+
By default ignores requests with method==GET (unless --get option is passed).
6+
Free standing arguments are substrings matching path.
7+
If argument starts with ! then it's a negation filter.
8+
9+
Examples:
10+
print_requests.py //jobs # Show non-GET requests with /jobs in path
11+
print_requests.py --get //jobs # Show all requests with /jobs in path
12+
print_requests.py --sort '^//import-file/' # Show non-GET requests, exclude /import-file/, sort output
13+
print_requests.py --keep //jobs # Show requests and do not delete out.requests.json afterwards
14+
15+
This replaces custom jq wrappers like:
16+
jq --sort-keys 'select(.method != "GET" and (.path | contains("/jobs")))' < out.requests.txt
17+
18+
>>> test_requests = [
19+
... {"method": "GET", "path": "/api/2.0/clusters/list"},
20+
... {"method": "POST", "path": "/api/2.1/jobs/create", "body": {"name": "test"}},
21+
... {"method": "GET", "path": "/api/2.0/jobs/123"},
22+
... {"method": "PUT", "path": "/api/2.0/jobs/123", "body": {"name": "updated"}},
23+
... {"method": "POST", "path": "/api/2.0/workspace/import-file/test.py"},
24+
... {"method": "DELETE", "path": "/api/2.0/jobs/123"}
25+
... ]
26+
27+
>>> def short_name(x):
28+
... ind = test_requests.index(x)
29+
... return f'R{ind} {x["method"]}'
30+
>>> def test(*args):
31+
... r = filter_requests(*args)
32+
... for x in r:
33+
... print(short_name(x))
34+
35+
>>> test(test_requests, ["//jobs"], False, False)
36+
R1 POST
37+
R3 PUT
38+
R5 DELETE
39+
40+
>>> test(test_requests, ["//jobs"], True, False)
41+
R1 POST
42+
R2 GET
43+
R3 PUT
44+
R5 DELETE
45+
46+
>>> test(test_requests, ["^//import-file/"], False, False)
47+
R1 POST
48+
R3 PUT
49+
R5 DELETE
50+
51+
>>> # Test multiple positive filters (OR logic)
52+
>>> test(test_requests, ["//clusters", "//import-file"], True, False)
53+
R0 GET
54+
R4 POST
55+
56+
>>> # Test positive + negative filters (AND logic)
57+
>>> test(test_requests, ["//api", "^/jobs"], False, False)
58+
R4 POST
59+
"""
60+
61+
import os
62+
import sys
63+
import json
64+
import argparse
65+
from pathlib import Path
66+
67+
68+
# I've originally tried ADD_PREFIX to be empty, so you can just do "print_requests.py /jobs"
69+
# However, that causes test to fail on Windows CI because "/jobs" becomes "C:/Program Files/Git/jobs"
70+
# This behaviour can be disabled with MSYS_NO_PATHCONV=1 but that causes other failures, so we require extra slash here.
71+
ADD_PREFIX = "/"
72+
NEGATE_PREFIX = "^/"
73+
74+
75+
def read_json_many(s):
76+
result = []
77+
78+
try:
79+
dec = json.JSONDecoder()
80+
pos = 0
81+
n = len(s)
82+
while True:
83+
# skip whitespace between objects
84+
while pos < n and s[pos].isspace():
85+
pos += 1
86+
if pos >= n:
87+
break
88+
obj, idx = dec.raw_decode(s, pos)
89+
result.append(obj)
90+
pos = idx
91+
92+
except Exception as ex:
93+
sys.exit(str(ex))
94+
95+
if not result and s:
96+
sys.stderr.write(f"WARNING: could not parse {len(s)} chars: {s!r}\n")
97+
98+
return result
99+
100+
101+
# quick self-test
102+
test = '{"method": "GET"}\n{"method":\n"POST"\n}\n'
103+
result = read_json_many(test)
104+
assert result == [{"method": "GET"}, {"method": "POST"}], result
105+
106+
107+
def filter_requests(requests, path_filters, include_get, should_sort):
108+
"""Filter requests based on method and path filters."""
109+
positive_filters = []
110+
negative_filters = []
111+
112+
for f in path_filters:
113+
if f.startswith(ADD_PREFIX):
114+
positive_filters.append(f.removeprefix(ADD_PREFIX))
115+
elif f.startswith(NEGATE_PREFIX):
116+
negative_filters.append(f.removeprefix(NEGATE_PREFIX))
117+
else:
118+
sys.exit(f"Unrecognized filter: {f!r}")
119+
120+
filtered_requests = []
121+
for req in requests:
122+
# Skip GET requests unless include_get is True
123+
if req.get("method") == "GET" and not include_get:
124+
continue
125+
126+
# Apply path filters
127+
path = req.get("path", "")
128+
should_include = True
129+
130+
# Check positive filters - if any exist, at least one must match (OR logic)
131+
if positive_filters:
132+
has_match = any(f in path for f in positive_filters)
133+
if not has_match:
134+
should_include = False
135+
136+
# Check negative filters - if any match, exclude the request (AND logic with positive)
137+
if should_include and negative_filters:
138+
has_negative_match = any(f in path for f in negative_filters)
139+
if has_negative_match:
140+
should_include = False
141+
142+
if should_include:
143+
filtered_requests.append(req)
144+
145+
if should_sort:
146+
filtered_requests.sort(key=str)
147+
148+
return filtered_requests
149+
150+
151+
def main():
152+
parser = argparse.ArgumentParser()
153+
parser.add_argument("path_filters", nargs="*", help=f"Path substring filters")
154+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable diagnostic messages")
155+
parser.add_argument("--get", action="store_true", help="Include GET requests (excluded by default)")
156+
parser.add_argument("--keep", action="store_true", help="Keep out.requests.json file after processing")
157+
parser.add_argument("--sort", action="store_true", help="Sort requests before output")
158+
parser.add_argument("--fname", default="out.requests.txt")
159+
args = parser.parse_args()
160+
161+
test_tmp_dir = os.environ.get("TEST_TMP_DIR")
162+
if test_tmp_dir:
163+
requests_file = Path(test_tmp_dir) / args.fname
164+
else:
165+
requests_file = Path(args.fname)
166+
167+
if not requests_file.exists():
168+
sys.exit(f"File {requests_file} not found")
169+
170+
with open(requests_file) as fobj:
171+
data = fobj.read()
172+
173+
if not data:
174+
return
175+
176+
requests = read_json_many(data)
177+
filtered_requests = filter_requests(requests, args.path_filters, args.get, args.sort)
178+
if args.verbose:
179+
print(
180+
f"Read {len(data)} chars, {len(requests)} requests, {len(filtered_requests)} after filtering",
181+
file=sys.stderr,
182+
flush=True,
183+
)
184+
185+
for req in filtered_requests:
186+
print(json.dumps(req, indent=2), flush=True)
187+
188+
if not args.keep:
189+
requests_file.unlink()
190+
191+
192+
if __name__ == "__main__":
193+
main()

acceptance/bundle/apps/app_yaml/output.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ All files and directories at the following location will be deleted: /Workspace/
3030
Deleting files...
3131
Destroy complete!
3232

33-
>>> print_requests
33+
>>> print_requests.py //apps
3434
{
3535
"method": "DELETE",
3636
"path": "/api/2.0/apps/myapp"
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
print_requests() {
2-
jq --sort-keys 'select(.method != "GET" and (.path | contains("/apps")))' < out.requests.txt
3-
rm out.requests.txt
4-
}
5-
61
trace $CLI bundle validate
72
trace $CLI bundle plan
83
trace $CLI bundle deploy
94
trace jq 'select(.path | test("app.yml"))' out.requests.txt | sed 's/\\r//g' > out.app.yml.txt
10-
#trace print_requests # currently fails due to TF inserting description=""
5+
#trace print_requests.py //apps # currently fails due to TF inserting description=""
116
rm out.requests.txt
127

138
trace $CLI bundle destroy --auto-approve
14-
trace print_requests
9+
trace print_requests.py //apps

acceptance/bundle/bundle_tag/id/output.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ Deploying resources...
3939
Updating deployment state...
4040
Deployment complete!
4141

42-
>>> print_requests
42+
>>> print_requests.py //jobs
4343
{
44+
"method": "POST",
45+
"path": "/api/2.2/jobs/create",
4446
"body": {
4547
"deployment": {
4648
"kind": "BUNDLE",
@@ -53,9 +55,7 @@ Deployment complete!
5355
"queue": {
5456
"enabled": true
5557
}
56-
},
57-
"method": "POST",
58-
"path": "/api/2.2/jobs/create"
58+
}
5959
}
6060

6161
>>> [CLI] bundle summary
@@ -79,11 +79,11 @@ All files and directories at the following location will be deleted: /Workspace/
7979
Deleting files...
8080
Destroy complete!
8181

82-
>>> print_requests
82+
>>> print_requests.py //jobs
8383
{
84+
"method": "POST",
85+
"path": "/api/2.2/jobs/delete",
8486
"body": {
8587
"job_id": [NUMID]
86-
},
87-
"method": "POST",
88-
"path": "/api/2.2/jobs/delete"
88+
}
8989
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
print_requests() {
2-
jq --sort-keys 'select(.method != "GET" and (.path | contains("/jobs")))' < out.requests.txt
3-
rm out.requests.txt
4-
}
5-
61
trace $CLI bundle validate
72
trace $CLI bundle validate -o json | jq .resources
83
trace $CLI bundle plan
94
trace $CLI bundle deploy
10-
trace print_requests
5+
trace print_requests.py //jobs
116
trace $CLI bundle summary
127
trace $CLI bundle destroy --auto-approve
13-
trace print_requests
8+
trace print_requests.py //jobs

acceptance/bundle/bundle_tag/url/output.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ Deploying resources...
3939
Updating deployment state...
4040
Deployment complete!
4141

42-
>>> print_requests
42+
>>> print_requests.py //jobs
4343
{
44+
"method": "POST",
45+
"path": "/api/2.2/jobs/create",
4446
"body": {
4547
"deployment": {
4648
"kind": "BUNDLE",
@@ -53,9 +55,7 @@ Deployment complete!
5355
"queue": {
5456
"enabled": true
5557
}
56-
},
57-
"method": "POST",
58-
"path": "/api/2.2/jobs/create"
58+
}
5959
}
6060

6161
>>> [CLI] bundle summary
@@ -79,11 +79,11 @@ All files and directories at the following location will be deleted: /Workspace/
7979
Deleting files...
8080
Destroy complete!
8181

82-
>>> print_requests
82+
>>> print_requests.py //jobs
8383
{
84+
"method": "POST",
85+
"path": "/api/2.2/jobs/delete",
8486
"body": {
8587
"job_id": [NUMID]
86-
},
87-
"method": "POST",
88-
"path": "/api/2.2/jobs/delete"
88+
}
8989
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
print_requests() {
2-
jq --sort-keys 'select(.method != "GET" and (.path | contains("/jobs")))' < out.requests.txt
3-
rm out.requests.txt
4-
}
5-
61
trace $CLI bundle validate
72
trace $CLI bundle validate -o json | jq .resources
83
trace $CLI bundle plan
94
trace $CLI bundle deploy
10-
trace print_requests
5+
trace print_requests.py //jobs
116
trace $CLI bundle summary
127
trace $CLI bundle destroy --auto-approve
13-
trace print_requests
8+
trace print_requests.py //jobs

acceptance/bundle/bundle_tag/url_ref/out.deploy-requests.terraform.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2+
"method": "POST",
3+
"path": "/api/2.2/jobs/create",
24
"body": {
35
"deployment": {
46
"kind": "BUNDLE",
@@ -11,11 +13,11 @@
1113
"queue": {
1214
"enabled": true
1315
}
14-
},
15-
"method": "POST",
16-
"path": "/api/2.2/jobs/create"
16+
}
1717
}
1818
{
19+
"method": "POST",
20+
"path": "/api/2.2/jobs/create",
1921
"body": {
2022
"deployment": {
2123
"kind": "BUNDLE",
@@ -28,7 +30,5 @@
2830
"queue": {
2931
"enabled": true
3032
}
31-
},
32-
"method": "POST",
33-
"path": "/api/2.2/jobs/create"
33+
}
3434
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2+
"method": "POST",
3+
"path": "/api/2.2/jobs/delete",
24
"body": {
35
"job_id": [NUMID]
4-
},
5-
"method": "POST",
6-
"path": "/api/2.2/jobs/delete"
6+
}
77
}
88
{
9+
"method": "POST",
10+
"path": "/api/2.2/jobs/delete",
911
"body": {
1012
"job_id": [NUMID]
11-
},
12-
"method": "POST",
13-
"path": "/api/2.2/jobs/delete"
13+
}
1414
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
print_requests() {
2-
jq --sort-keys 'select(.method != "GET" and (.path | contains("/jobs")))' < out.requests.txt
3-
rm out.requests.txt
4-
}
5-
61
trace $CLI bundle validate
72
trace $CLI bundle validate -o json | jq .resources
83
errcode $CLI bundle plan &> out.plan.$DATABRICKS_BUNDLE_ENGINE.txt
94
errcode $CLI bundle deploy &> out.deploy.$DATABRICKS_BUNDLE_ENGINE.txt
10-
print_requests &> out.deploy-requests.$DATABRICKS_BUNDLE_ENGINE.txt
5+
print_requests.py //jobs &> out.deploy-requests.$DATABRICKS_BUNDLE_ENGINE.txt
116
errcode $CLI bundle summary &> out.summary.$DATABRICKS_BUNDLE_ENGINE.txt
127
$CLI bundle destroy --auto-approve &> out.destroy.$DATABRICKS_BUNDLE_ENGINE.txt
13-
print_requests &> out.destroy-requests.$DATABRICKS_BUNDLE_ENGINE.txt
8+
print_requests.py //jobs &> out.destroy-requests.$DATABRICKS_BUNDLE_ENGINE.txt

0 commit comments

Comments
 (0)