-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
{testsdk} Move azure-devtools
's code to azure-cli-testsdk
#20601
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1650110
copy scenario_tests
jiasli f1a54fe
Replace with azure.cli.testsdk
jiasli f50d0b1
more
jiasli df6b52f
Fix tests
jiasli f5d4e8a
update doc
jiasli 23e3bd7
add comment
jiasli d1b18cc
update setup.py
jiasli f6d67ca
Merge remote-tracking branch 'azure/dev' into azure-devtools
jiasli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
src/azure-cli-testsdk/azure/cli/testsdk/scenario_tests/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
""" | ||
This module is vendored from | ||
https://github.com/Azure/azure-python-devtools/tree/1.2.0/src/azure_devtools/scenario_tests | ||
|
||
More info: https://github.com/Azure/azure-cli/pull/20601 | ||
""" | ||
|
||
from .base import IntegrationTestBase, ReplayableTest, LiveTest | ||
from .exceptions import AzureTestError | ||
from .decorators import live_only, record_only, AllowLargeResponse | ||
from .patches import mock_in_unit_test, patch_time_sleep_api, patch_long_run_operation_delay | ||
from .preparers import AbstractPreparer, SingleValueReplacer | ||
from .recording_processors import ( | ||
RecordingProcessor, SubscriptionRecordingProcessor, | ||
LargeRequestBodyProcessor, LargeResponseBodyProcessor, LargeResponseBodyReplacer, | ||
OAuthRequestResponsesFilter, DeploymentNameReplacer, GeneralNameReplacer, AccessTokenReplacer, RequestUrlNormalizer, | ||
) | ||
from .utilities import create_random_name, get_sha1_hash | ||
|
||
__all__ = ['IntegrationTestBase', 'ReplayableTest', 'LiveTest', | ||
'AzureTestError', | ||
'mock_in_unit_test', 'patch_time_sleep_api', 'patch_long_run_operation_delay', | ||
'AbstractPreparer', 'SingleValueReplacer', 'AllowLargeResponse', | ||
'RecordingProcessor', 'SubscriptionRecordingProcessor', | ||
'LargeRequestBodyProcessor', 'LargeResponseBodyProcessor', 'LargeResponseBodyReplacer', | ||
'OAuthRequestResponsesFilter', 'DeploymentNameReplacer', 'GeneralNameReplacer', | ||
'AccessTokenReplacer', 'RequestUrlNormalizer', | ||
'live_only', 'record_only', | ||
'create_random_name', 'get_sha1_hash'] | ||
__version__ = '0.5.2' |
219 changes: 219 additions & 0 deletions
219
src/azure-cli-testsdk/azure/cli/testsdk/scenario_tests/base.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from __future__ import print_function | ||
import unittest | ||
import os | ||
import inspect | ||
import tempfile | ||
import shutil | ||
import logging | ||
import threading | ||
import six | ||
import vcr | ||
|
||
from .config import TestConfig | ||
from .const import ENV_TEST_DIAGNOSE | ||
from .utilities import create_random_name | ||
from .decorators import live_only | ||
|
||
|
||
class IntegrationTestBase(unittest.TestCase): | ||
def __init__(self, method_name): | ||
super(IntegrationTestBase, self).__init__(method_name) | ||
self.diagnose = os.environ.get(ENV_TEST_DIAGNOSE, None) == 'True' | ||
self.logger = logging.getLogger('azure.cli.testsdk.scenario_tests') | ||
|
||
def create_random_name(self, prefix, length): # pylint: disable=no-self-use | ||
return create_random_name(prefix=prefix, length=length) | ||
|
||
def create_temp_file(self, size_kb, full_random=False): | ||
""" | ||
Create a temporary file for testing. The test harness will delete the file during tearing down. | ||
:param float size_kb: specify the generated file size in kb. | ||
""" | ||
fd, path = tempfile.mkstemp() | ||
os.close(fd) | ||
self.addCleanup(lambda: os.remove(path)) | ||
|
||
with open(path, mode='r+b') as f: | ||
if full_random: | ||
chunk = os.urandom(1024) | ||
else: | ||
chunk = bytearray([0] * 1024) | ||
for _ in range(int(size_kb)): | ||
f.write(chunk) | ||
chunk = os.urandom(int(1024 * (size_kb % 1))) | ||
f.write(chunk) | ||
|
||
return path | ||
|
||
def create_temp_dir(self): | ||
""" | ||
Create a temporary directory for testing. The test harness will delete the directory during tearing down. | ||
""" | ||
temp_dir = tempfile.mkdtemp() | ||
self.addCleanup(lambda: shutil.rmtree(temp_dir, ignore_errors=True)) | ||
|
||
return temp_dir | ||
|
||
@classmethod | ||
def set_env(cls, key, val): | ||
os.environ[key] = val | ||
|
||
@classmethod | ||
def pop_env(cls, key): | ||
return os.environ.pop(key, None) | ||
|
||
|
||
@live_only() | ||
class LiveTest(IntegrationTestBase): | ||
pass | ||
|
||
|
||
class ReplayableTest(IntegrationTestBase): # pylint: disable=too-many-instance-attributes | ||
FILTER_HEADERS = [ | ||
'authorization', | ||
'client-request-id', | ||
'retry-after', | ||
'x-ms-client-request-id', | ||
'x-ms-correlation-request-id', | ||
'x-ms-ratelimit-remaining-subscription-reads', | ||
'x-ms-request-id', | ||
'x-ms-routing-request-id', | ||
'x-ms-gateway-service-instanceid', | ||
'x-ms-ratelimit-remaining-tenant-reads', | ||
'x-ms-served-by', | ||
'x-ms-authorization-auxiliary' | ||
] | ||
|
||
def __init__(self, # pylint: disable=too-many-arguments | ||
method_name, config_file=None, recording_dir=None, recording_name=None, recording_processors=None, | ||
replay_processors=None, recording_patches=None, replay_patches=None): | ||
super(ReplayableTest, self).__init__(method_name) | ||
|
||
self.recording_processors = recording_processors or [] | ||
self.replay_processors = replay_processors or [] | ||
|
||
self.recording_patches = recording_patches or [] | ||
self.replay_patches = replay_patches or [] | ||
|
||
self.config = TestConfig(config_file=config_file) | ||
|
||
self.disable_recording = False | ||
|
||
test_file_path = inspect.getfile(self.__class__) | ||
recording_dir = recording_dir or os.path.join(os.path.dirname(test_file_path), 'recordings') | ||
self.is_live = self.config.record_mode | ||
|
||
self.vcr = vcr.VCR( | ||
cassette_library_dir=recording_dir, | ||
before_record_request=self._process_request_recording, | ||
before_record_response=self._process_response_recording, | ||
decode_compressed_response=True, | ||
record_mode='once' if not self.is_live else 'all', | ||
filter_headers=self.FILTER_HEADERS | ||
) | ||
self.vcr.register_matcher('query', self._custom_request_query_matcher) | ||
|
||
self.recording_file = os.path.join( | ||
recording_dir, | ||
'{}.yaml'.format(recording_name or method_name) | ||
) | ||
if self.is_live and os.path.exists(self.recording_file): | ||
os.remove(self.recording_file) | ||
|
||
self.in_recording = self.is_live or not os.path.exists(self.recording_file) | ||
self.test_resources_count = 0 | ||
self.original_env = os.environ.copy() | ||
|
||
def setUp(self): | ||
super(ReplayableTest, self).setUp() | ||
|
||
# set up cassette | ||
cm = self.vcr.use_cassette(self.recording_file) | ||
self.cassette = cm.__enter__() | ||
self.addCleanup(cm.__exit__) | ||
|
||
# set up mock patches | ||
if self.in_recording: | ||
for patch in self.recording_patches: | ||
patch(self) | ||
else: | ||
for patch in self.replay_patches: | ||
patch(self) | ||
|
||
def tearDown(self): | ||
os.environ = self.original_env | ||
# Autorest.Python 2.x | ||
assert not [t for t in threading.enumerate() if t.name.startswith("AzureOperationPoller")], \ | ||
"You need to call 'result' or 'wait' on all AzureOperationPoller you have created" | ||
# Autorest.Python 3.x | ||
assert not [t for t in threading.enumerate() if t.name.startswith("LROPoller")], \ | ||
"You need to call 'result' or 'wait' on all LROPoller you have created" | ||
|
||
def _process_request_recording(self, request): | ||
if self.disable_recording: | ||
return None | ||
|
||
if self.in_recording: | ||
for processor in self.recording_processors: | ||
request = processor.process_request(request) | ||
if not request: | ||
break | ||
else: | ||
for processor in self.replay_processors: | ||
request = processor.process_request(request) | ||
if not request: | ||
break | ||
|
||
return request | ||
|
||
def _process_response_recording(self, response): | ||
from .utilities import is_text_payload | ||
if self.in_recording: | ||
# make header name lower case and filter unwanted headers | ||
headers = {} | ||
for key in response['headers']: | ||
if key.lower() not in self.FILTER_HEADERS: | ||
headers[key.lower()] = response['headers'][key] | ||
response['headers'] = headers | ||
|
||
body = response['body']['string'] | ||
if is_text_payload(response) and body and not isinstance(body, six.string_types): | ||
response['body']['string'] = body.decode('utf-8') | ||
|
||
for processor in self.recording_processors: | ||
response = processor.process_response(response) | ||
if not response: | ||
break | ||
else: | ||
for processor in self.replay_processors: | ||
response = processor.process_response(response) | ||
if not response: | ||
break | ||
|
||
return response | ||
|
||
@classmethod | ||
def _custom_request_query_matcher(cls, r1, r2): | ||
""" Ensure method, path, and query parameters match. """ | ||
from six.moves.urllib_parse import urlparse, parse_qs # pylint: disable=import-error, relative-import | ||
|
||
url1 = urlparse(r1.uri) | ||
url2 = urlparse(r2.uri) | ||
|
||
q1 = parse_qs(url1.query) | ||
q2 = parse_qs(url2.query) | ||
shared_keys = set(q1.keys()).intersection(set(q2.keys())) | ||
|
||
if len(shared_keys) != len(q1) or len(shared_keys) != len(q2): | ||
return False | ||
|
||
for key in shared_keys: | ||
if q1[key][0].lower() != q2[key][0].lower(): | ||
return False | ||
|
||
return True |
28 changes: 28 additions & 0 deletions
28
src/azure-cli-testsdk/azure/cli/testsdk/scenario_tests/config.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
import configargparse | ||
|
||
from .const import ENV_LIVE_TEST | ||
|
||
|
||
class TestConfig(object): # pylint: disable=too-few-public-methods | ||
def __init__(self, parent_parsers=None, config_file=None): | ||
parent_parsers = parent_parsers or [] | ||
self.parser = configargparse.ArgumentParser(parents=parent_parsers) | ||
self.parser.add_argument( | ||
'-c', '--config', is_config_file=True, default=config_file, | ||
help='Path to a configuration file in YAML format.' | ||
) | ||
self.parser.add_argument( | ||
'-l', '--live-mode', action='store_true', dest='live_mode', | ||
env_var=ENV_LIVE_TEST, | ||
help='Activate "live" recording mode for tests.' | ||
) | ||
self.args = self.parser.parse_args([]) | ||
|
||
@property | ||
def record_mode(self): | ||
return self.args.live_mode |
14 changes: 14 additions & 0 deletions
14
src/azure-cli-testsdk/azure/cli/testsdk/scenario_tests/const.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
# Replaced mock values | ||
MOCKED_SUBSCRIPTION_ID = '00000000-0000-0000-0000-000000000000' | ||
MOCKED_TENANT_ID = '00000000-0000-0000-0000-000000000000' | ||
|
||
# Configuration environment variable | ||
ENV_COMMAND_COVERAGE = 'AZURE_TEST_COMMAND_COVERAGE' | ||
ENV_LIVE_TEST = 'AZURE_TEST_RUN_LIVE' | ||
ENV_SKIP_ASSERT = 'AZURE_TEST_SKIP_ASSERT' | ||
ENV_TEST_DIAGNOSE = 'AZURE_TEST_DIAGNOSE' |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comments for where this module comes from.