Skip to content

Commit 2a8d7c9

Browse files
add optional sample timeout to tox sample runner (Azure#17139)
* add optional sample timeout to tox sample runner * updated sample timeout to install sample specific dependencies reqs * except with FileNotFoundError * made timeout a required param and pass_if_timeout default to True Co-authored-by: Swathi Pillalamarri <swathip@microsoft.com>
1 parent 5243d2e commit 2a8d7c9

File tree

2 files changed

+147
-53
lines changed

2 files changed

+147
-53
lines changed

scripts/devops_tasks/test_run_samples.py

Lines changed: 145 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import logging
1313
from fnmatch import fnmatch
14+
from subprocess import check_call, CalledProcessError, TimeoutExpired
1415
from common_tasks import (
1516
run_check_call,
1617
process_glob_string,
@@ -20,6 +21,56 @@
2021

2122
root_dir = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", ".."))
2223

24+
"""
25+
Some samples may "run forever" or need to be timed out after a period of time. Add them here in the following format:
26+
TIMEOUT_SAMPLES = {
27+
"<package-name>": {
28+
"<sample_file_name.py>": (<timeout (seconds)>, <pass if timeout? (bool, default: True)>)
29+
}
30+
}
31+
"""
32+
TIMEOUT_SAMPLES = {
33+
"azure-eventhub": {
34+
"authenticate_with_sas_token.py": (5),
35+
"receive_batch_with_checkpoint.py": (5),
36+
"recv.py": (5),
37+
"recv_track_last_enqueued_event_prop.py": (5),
38+
"recv_with_checkpoint_by_event_count.py": (5),
39+
"recv_with_checkpoint_by_time_interval.py": (5),
40+
"recv_with_checkpoint_store.py": (5),
41+
"recv_with_custom_starting_position.py": (5),
42+
"sample_code_eventhub.py": (10),
43+
"authenticate_with_sas_token_async.py": (5),
44+
"receive_batch_with_checkpoint_async.py": (5),
45+
"recv_async.py": (5),
46+
"recv_track_last_enqueued_event_prop_async.py": (5),
47+
"recv_with_checkpoint_by_event_count_async.py": (5),
48+
"recv_with_checkpoint_by_time_interval_async.py": (5),
49+
"recv_with_checkpoint_store_async.py": (5),
50+
"recv_with_custom_starting_position_async.py": (5),
51+
"sample_code_eventhub_async.py": (10)
52+
},
53+
"azure-eventhub-checkpointstoreblob": {
54+
"receive_events_using_checkpoint_store.py": (5),
55+
"receive_events_using_checkpoint_store_storage_api_version.py": (5)
56+
},
57+
"azure-eventhub-checkpointstoreblob-aio": {
58+
"receive_events_using_checkpoint_store_async.py": (5),
59+
"receive_events_using_checkpoint_store_storage_api_version_async.py": (5)
60+
},
61+
"azure-servicebus": {
62+
"failure_and_recovery.py": (5),
63+
"receive_iterator_queue.py": (5),
64+
"sample_code_servicebus.py": (30),
65+
"session_pool_receive.py": (20),
66+
"receive_iterator_queue_async.py": (5),
67+
"sample_code_servicebus_async.py": (30),
68+
"session_pool_receive_async.py": (20)
69+
}
70+
}
71+
72+
73+
# Add your library + sample file if you do not want a particular sample to be run
2374
IGNORED_SAMPLES = {
2475
"azure-eventgrid": [
2576
"__init__.py",
@@ -29,56 +80,25 @@
2980
"sample_publish_events_to_a_topic_using_sas_credential.py",
3081
"sample_publish_events_to_a_topic_using_sas_credential_async.py"],
3182
"azure-eventhub": [
32-
"authenticate_with_sas_token.py",
3383
"connection_to_custom_endpoint_address.py",
3484
"proxy.py",
35-
"receive_batch_with_checkpoint.py",
36-
"recv.py",
37-
"recv_track_last_enqueued_event_prop.py",
38-
"recv_with_checkpoint_by_event_count.py",
39-
"recv_with_checkpoint_by_time_interval.py",
40-
"recv_with_checkpoint_store.py",
41-
"recv_with_custom_starting_position.py",
42-
"sample_code_eventhub.py",
43-
"authenticate_with_sas_token_async.py",
4485
"connection_to_custom_endpoint_address_async.py",
4586
"iot_hub_connection_string_receive_async.py",
46-
"proxy_async.py",
47-
"receive_batch_with_checkpoint_async.py",
48-
"recv_async.py",
49-
"recv_track_last_enqueued_event_prop_async.py",
50-
"recv_with_checkpoint_by_event_count_async.py",
51-
"recv_with_checkpoint_by_time_interval_async.py",
52-
"recv_with_checkpoint_store_async.py",
53-
"recv_with_custom_starting_position_async.py",
54-
"sample_code_eventhub_async.py"
55-
],
56-
"azure-eventhub-checkpointstoreblob": [
57-
"receive_events_using_checkpoint_store.py",
58-
"receive_events_using_checkpoint_store_storage_api_version.py"
59-
],
60-
"azure-eventhub-checkpointstoreblob-aio": [
61-
"receive_events_using_checkpoint_store_async.py",
62-
"receive_events_using_checkpoint_store_storage_api_version_async.py"
87+
"proxy_async.py"
6388
],
6489
"azure-servicebus": [
65-
"failure_and_recovery.py",
6690
"mgmt_queue.py",
6791
"mgmt_rule.py",
6892
"mgmt_subscription.py",
6993
"mgmt_topic.py",
7094
"proxy.py",
7195
"receive_deferred_message_queue.py",
72-
"receive_iterator_queue.py",
73-
"session_pool_receive.py",
7496
"mgmt_queue_async.py",
7597
"mgmt_rule_async.py",
7698
"mgmt_subscription_async.py",
7799
"mgmt_topic_async.py",
78100
"proxy_async.py",
79-
"receive_deferred_message_queue_async.py",
80-
"receive_iterator_queue_async.py",
81-
"session_pool_receive_async.py"
101+
"receive_deferred_message_queue_async.py"
82102
],
83103
"azure-ai-formrecognizer": [
84104
"sample_recognize_receipts_from_url.py",
@@ -87,45 +107,117 @@
87107
}
88108

89109

110+
def run_check_call_with_timeout(
111+
command_array,
112+
working_directory,
113+
timeout,
114+
pass_if_timeout,
115+
acceptable_return_codes=[],
116+
always_exit=False
117+
):
118+
"""This is copied from common_tasks.py with some additions.
119+
Don't want to break anyone that's using the original code.
120+
"""
121+
try:
122+
logging.info(
123+
"Command Array: {0}, Target Working Directory: {1}".format(
124+
command_array, working_directory
125+
)
126+
)
127+
check_call(command_array, cwd=working_directory, timeout=timeout)
128+
except CalledProcessError as err:
129+
if err.returncode not in acceptable_return_codes:
130+
logging.error(err) # , file = sys.stderr
131+
if always_exit:
132+
exit(1)
133+
else:
134+
return err
135+
except TimeoutExpired as err:
136+
if pass_if_timeout:
137+
logging.info(
138+
"Sample timed out successfully"
139+
)
140+
else:
141+
logging.info(
142+
"Fail: Sample timed out"
143+
)
144+
return err
145+
146+
147+
def execute_sample(sample, samples_errors, timed):
148+
if isinstance(sample, tuple):
149+
sample, timeout, pass_if_timeout = sample
150+
151+
if sys.version_info < (3, 5) and sample.endswith("_async.py"):
152+
return
153+
154+
logging.info(
155+
"Testing {}".format(sample)
156+
)
157+
command_array = [sys.executable, sample]
158+
159+
if not timed:
160+
errors = run_check_call(command_array, root_dir)
161+
else:
162+
errors = run_check_call_with_timeout(
163+
command_array, root_dir, timeout, pass_if_timeout
164+
)
165+
166+
sample_name = os.path.basename(sample)
167+
if errors:
168+
samples_errors.append(sample_name)
169+
logging.info(
170+
"ERROR: {}".format(sample_name)
171+
)
172+
else:
173+
logging.info(
174+
"SUCCESS: {}.".format(sample_name)
175+
)
176+
177+
90178
def run_samples(targeted_package):
91179
logging.info("running samples for {}".format(targeted_package))
92180

93181
samples_errors = []
94182
sample_paths = []
183+
timed_sample_paths = []
184+
95185
samples_dir_path = os.path.abspath(os.path.join(targeted_package, "samples"))
96186
package_name = os.path.basename(targeted_package)
187+
samples_need_timeout = TIMEOUT_SAMPLES.get(package_name, {})
188+
189+
# install extra dependencies for samples if needed
190+
try:
191+
with open(samples_dir_path + "/sample_dev_requirements.txt") as sample_dev_reqs:
192+
for dep in sample_dev_reqs.readlines():
193+
check_call([sys.executable, '-m', 'pip', 'install', dep])
194+
except FileNotFoundError:
195+
pass
97196

98197
for path, subdirs, files in os.walk(samples_dir_path):
99198
for name in files:
100-
if fnmatch(name, "*.py") and name not in IGNORED_SAMPLES.get(package_name, []):
199+
if fnmatch(name, "*.py") and name in samples_need_timeout:
200+
timeout = samples_need_timeout[name]
201+
# timeout, pass_if_timeout is True by default if nothing passed in
202+
if isinstance(timeout, tuple):
203+
timeout, pass_if_timeout = timeout
204+
else:
205+
pass_if_timeout = True
206+
timed_sample_paths.append((os.path.abspath(os.path.join(path, name)), timeout, pass_if_timeout))
207+
elif fnmatch(name, "*.py") and name not in IGNORED_SAMPLES.get(package_name, []):
101208
sample_paths.append(os.path.abspath(os.path.join(path, name)))
102209

103-
if not sample_paths:
210+
if not sample_paths and not timed_sample_paths:
104211
logging.info(
105212
"No samples found in {}".format(targeted_package)
106213
)
107214
exit(0)
108215

109216
for sample in sample_paths:
110-
if sys.version_info < (3, 5) and sample.endswith("_async.py"):
111-
continue
112-
113-
logging.info(
114-
"Testing {}".format(sample)
115-
)
116-
command_array = [sys.executable, sample]
117-
errors = run_check_call(command_array, root_dir, always_exit=False)
217+
execute_sample(sample, samples_errors, timed=False)
118218

119-
sample_name = os.path.basename(sample)
120-
if errors:
121-
samples_errors.append(sample_name)
122-
logging.info(
123-
"ERROR: {}".format(sample_name)
124-
)
125-
else:
126-
logging.info(
127-
"SUCCESS: {}.".format(sample_name)
128-
)
219+
for sample in timed_sample_paths:
220+
execute_sample(sample, samples_errors, timed=True)
129221

130222
if samples_errors:
131223
logging.error("Sample(s) that ran with errors: {}".format(samples_errors))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
azure-eventhub-checkpointstoreblob
2+
azure-eventhub-checkpointstoreblob-aio

0 commit comments

Comments
 (0)