-
Notifications
You must be signed in to change notification settings - Fork 375
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
Add sample remote tests #2888
Add sample remote tests #2888
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
name: "Fail" | ||
tests: | ||
- "fail_test.py" | ||
- "error_test.py" | ||
images: "ubuntu_1804" | ||
- "samples/fail_test.py" | ||
- "samples/fail_remote_test.py" | ||
- "samples/error_test.py" | ||
- "samples/error_remote_test.py" | ||
images: "ubuntu_2004" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fail & pass are many times used together... matching image in order to save 1 test VM |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
name: "Pass" | ||
tests: | ||
- "pass_test.py" | ||
- "samples/pass_test.py" | ||
- "samples/pass_remote_test.py" | ||
images: "ubuntu_2004" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,10 +20,14 @@ | |
import sys | ||
|
||
from abc import ABC, abstractmethod | ||
from assertpy import fail | ||
from typing import Any, Dict, List | ||
|
||
from tests_e2e.tests.lib.agent_test_context import AgentTestContext | ||
from tests_e2e.tests.lib.logging import log | ||
from tests_e2e.tests.lib.remote_test import FAIL_EXIT_CODE | ||
from tests_e2e.tests.lib.shell import CommandError | ||
from tests_e2e.tests.lib.ssh_client import ATTEMPTS, ATTEMPT_DELAY, SshClient | ||
|
||
|
||
class TestSkipped(Exception): | ||
|
@@ -33,6 +37,12 @@ class TestSkipped(Exception): | |
""" | ||
|
||
|
||
class RemoteTestError(CommandError): | ||
""" | ||
Raised when a remote test fails with an unexpected error. | ||
""" | ||
|
||
|
||
class AgentTest(ABC): | ||
""" | ||
Defines the interface for agent tests, which are simply constructed from an AgentTestContext and expose a single method, | ||
|
@@ -59,8 +69,28 @@ def run_from_command_line(cls): | |
cls(AgentTestContext.from_args()).run() | ||
except SystemExit: # Bad arguments | ||
pass | ||
except AssertionError as e: | ||
log.error("%s", e) | ||
sys.exit(1) | ||
except: # pylint: disable=bare-except | ||
log.exception("Test failed") | ||
sys.exit(1) | ||
|
||
sys.exit(0) | ||
|
||
def _run_remote_test(self, command: str, use_sudo: bool = False, attempts: int = ATTEMPTS, attempt_delay: int = ATTEMPT_DELAY) -> None: | ||
""" | ||
Derived classes can use this method to execute a remote test (a test that runs over SSH). | ||
""" | ||
try: | ||
ssh_client: SshClient = self._context.create_ssh_client() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about initializing this to AgentTest instead every remote run in test_suite? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry, i don't understand the question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems we initialize ssh_client on every remote run, can we optimize this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. initialization of the SSH client is very cheap (just 4 assigments). it was designed this way so that we can create instances at will, without worrying about it. |
||
output = ssh_client.run_command(command=command, use_sudo=use_sudo, attempts=attempts, attempt_delay=attempt_delay) | ||
log.info("*** PASSED: [%s]\n%s", command, self._indent(output)) | ||
except CommandError as error: | ||
if error.exit_code == FAIL_EXIT_CODE: | ||
fail(f"[{command}] {error.stderr}{self._indent(error.stdout)}") | ||
raise RemoteTestError(command=error.command, exit_code=error.exit_code, stdout=self._indent(error.stdout), stderr=error.stderr) | ||
|
||
@staticmethod | ||
def _indent(text: str, indent: str = " " * 8): | ||
return "\n".join(f"{indent}{line}" for line in text.splitlines()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#!/usr/bin/env python3 | ||
nagworld9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
import sys | ||
|
||
from typing import Callable | ||
|
||
from tests_e2e.tests.lib.logging import log | ||
|
||
SUCCESS_EXIT_CODE = 0 | ||
FAIL_EXIT_CODE = 100 | ||
ERROR_EXIT_CODE = 200 | ||
|
||
|
||
nagworld9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def run_remote_test(test_method: Callable[[], int]) -> None: | ||
try: | ||
test_method() | ||
log.error("*** PASSED") | ||
nagworld9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
except AssertionError as e: | ||
print(f"{e}", file=sys.stderr) | ||
nagworld9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
log.error("%s", e) | ||
sys.exit(FAIL_EXIT_CODE) | ||
except Exception as e: | ||
log.exception("*** UNEXPECTED ERROR") | ||
sys.exit(ERROR_EXIT_CODE) | ||
|
||
sys.exit(SUCCESS_EXIT_CODE) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from tests_e2e.tests.lib.agent_test import AgentTest | ||
|
||
|
||
class ErrorRemoteTest(AgentTest): | ||
""" | ||
A trivial remote test that fails | ||
""" | ||
def run(self): | ||
self._run_remote_test("samples-error_remote_test.py") | ||
|
||
|
||
if __name__ == "__main__": | ||
ErrorRemoteTest.run_from_command_line() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from tests_e2e.tests.lib.agent_test import AgentTest | ||
|
||
|
||
class FailRemoteTest(AgentTest): | ||
""" | ||
A trivial remote test that fails | ||
""" | ||
def run(self): | ||
self._run_remote_test("samples-fail_remote_test.py") | ||
|
||
|
||
if __name__ == "__main__": | ||
FailRemoteTest.run_from_command_line() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from tests_e2e.tests.lib.agent_test import AgentTest | ||
|
||
|
||
class PassRemoteTest(AgentTest): | ||
""" | ||
A trivial remote test that succeeds | ||
""" | ||
def run(self): | ||
self._run_remote_test("samples-pass_remote_test.py") | ||
|
||
|
||
if __name__ == "__main__": | ||
PassRemoteTest.run_from_command_line() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/usr/bin/env pypy3 | ||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
# | ||
# A sample remote test that simulates an unexpected error | ||
# | ||
|
||
from tests_e2e.tests.lib.logging import log | ||
from tests_e2e.tests.lib.remote_test import run_remote_test | ||
|
||
|
||
def main(): | ||
log.info("Setting up test") | ||
log.info("Doing some operation") | ||
log.warning("Something went wrong, but the test can continue") | ||
log.info("Doing some other operation") | ||
raise Exception("Something went wrong") # simulate an unexpected error | ||
|
||
|
||
run_remote_test(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#!/usr/bin/env pypy3 | ||
|
||
# Microsoft Azure Linux Agent | ||
# | ||
# Copyright 2018 Microsoft Corporation | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
# | ||
# A sample remote test that fails | ||
# | ||
|
||
from assertpy import fail | ||
from tests_e2e.tests.lib.logging import log | ||
from tests_e2e.tests.lib.remote_test import run_remote_test | ||
|
||
|
||
def main(): | ||
log.info("Setting up test") | ||
log.info("Doing some operation") | ||
log.warning("Something went wrong, but the test can continue") | ||
log.info("Doing some other operation") | ||
fail("Verification of the operation failed") | ||
|
||
|
||
run_remote_test(main) |
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.
Changed this from "UNHANDLED EXCEPTION" to "UNEXPECTED ERROR" since the former gives the impression that the tests should handle those exceptions, but tests can actually just let those exceptions bubble up when doing test setup, etc.
Note that some unexpected errors should actually be handled by the tests, though