Skip to content
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

Run unit tests with pytest on Python >= 3.10 #3081

Merged
merged 5 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/ci_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,14 @@ jobs:

pylint $PYLINT_OPTIONS $PYLINT_FILES

- name: Test with nosetests
if: contains(fromJSON('["3.10", "3.11"]'), matrix.python-version) == false && (success() || (failure() && steps.install-dependencies.outcome == 'success'))
- name: Execute Unit Tests
if: success() || (failure() && steps.install-dependencies.outcome == 'success')
run: |
./ci/nosetests.sh
exit $?
if [[ "${{ matrix.python-version }}" =~ ^3\.[1-9][0-9]+$ ]]; then
./ci/pytest.sh
else
./ci/nosetests.sh
fi

- name: Compile Coverage
if: matrix.python-version == '3.9'
Expand Down
8 changes: 4 additions & 4 deletions ci/nosetests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ set -u
EXIT_CODE=0

echo "========================================="
echo "nosetests -a '!requires_sudo' output"
echo "**** nosetests non-sudo tests ****"
echo "========================================="
nosetests -a '!requires_sudo' tests $NOSEOPTS || EXIT_CODE=$(($EXIT_CODE || $?))
nosetests --ignore-files test_cgroupconfigurator_sudo.py tests $NOSEOPTS || EXIT_CODE=$(($EXIT_CODE || $?))
echo EXIT_CODE no_sudo nosetests = $EXIT_CODE

[[ -f .coverage ]] && \
sudo mv .coverage coverage.$(uuidgen).no_sudo.data

echo "========================================="
echo "nosetests -a 'requires_sudo' output"
echo "**** nosetests sudo tests ****"
echo "========================================="
sudo env "PATH=$PATH" nosetests -a 'requires_sudo' tests $NOSEOPTS || EXIT_CODE=$(($EXIT_CODE || $?))
sudo env "PATH=$PATH" nosetests tests/ga/test_cgroupconfigurator_sudo.py $NOSEOPTS || EXIT_CODE=$(($EXIT_CODE || $?))
echo EXIT_CODE with_sudo nosetests = $EXIT_CODE

[[ -f .coverage ]] && \
Expand Down
19 changes: 19 additions & 0 deletions ci/pytest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

set -u

EXIT_CODE=0

echo "========================================="
echo "**** pytest *** non-sudo tests ****"
echo "========================================="
pytest --ignore-glob '*/test_cgroupconfigurator_sudo.py' --verbose tests || EXIT_CODE=$(($EXIT_CODE || $?))
echo EXIT_CODE pytests non-sudo = $EXIT_CODE

echo "========================================="
echo "**** pytest *** sudo tests ****"
echo "========================================="
sudo env "PATH=$PATH" pytest --verbose tests/ga/test_cgroupconfigurator_sudo.py || EXIT_CODE=$(($EXIT_CODE || $?))
echo EXIT_CODE pytests sudo = $EXIT_CODE

exit "$EXIT_CODE"
5 changes: 3 additions & 2 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ mock==2.0.0; python_version == '2.6'
mock==3.0.5; python_version >= '2.7' and python_version <= '3.5'
mock==4.0.2; python_version >= '3.6'
distro; python_version >= '3.8'
nose
nose-timer; python_version >= '2.7'
nose; python_version <= '3.9'
nose-timer; python_version >= '2.7' and python_version <= '3.9'
pytest; python_version >= '3.10'

# Pinning the wrapt requirement to 1.12.0 due to the bug - https://github.com/GrahamDumpleton/wrapt/issues/188
wrapt==1.12.0; python_version > '2.6' and python_version < '3.6'
Expand Down
2 changes: 1 addition & 1 deletion tests/common/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# Requires Python 2.6+ and Openssl 1.0+
#

import json # pylint: disable=unused-import
import os
import tempfile
from datetime import datetime, timedelta
Expand Down Expand Up @@ -49,6 +48,7 @@ def tearDown(self):
AgentTestCase.tearDown(self)
logger.reset_periodic()
logger.DEFAULT_LOGGER.appenders *= 0
logger.set_prefix(None)
fileutil.rm_dirs(self.event_dir)

@patch('azurelinuxagent.common.logger.Logger.verbose')
Expand Down
30 changes: 15 additions & 15 deletions tests/common/test_telemetryevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@
from tests.lib.tools import AgentTestCase


def get_test_event(name="DummyExtension", op="Unknown", is_success=True, duration=0, version="foo", evt_type="", is_internal=False,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nose was identifying this as a test; renamed it and moved it to the TestTelemetryEvent class

message="DummyMessage", eventId=1):
event = TelemetryEvent(eventId, "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Name, name))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Version, str(version)))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.IsInternal, is_internal))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Operation, op))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.OperationSuccess, is_success))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Message, message))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Duration, duration))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.ExtensionType, evt_type))
return event


class TestTelemetryEvent(AgentTestCase):
@staticmethod
def _get_test_event(name="DummyExtension", op="Unknown", is_success=True, duration=0, version="foo", evt_type="", is_internal=False,
message="DummyMessage", eventId=1):
event = TelemetryEvent(eventId, "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Name, name))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Version, str(version)))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.IsInternal, is_internal))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Operation, op))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.OperationSuccess, is_success))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Message, message))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.Duration, duration))
event.parameters.append(TelemetryEventParam(GuestAgentExtensionEventsSchema.ExtensionType, evt_type))
return event

def test_contains_works_for_TelemetryEvent(self):
test_event = get_test_event(message="Dummy Event")
test_event = TestTelemetryEvent._get_test_event(message="Dummy Event")

self.assertTrue(GuestAgentExtensionEventsSchema.Name in test_event)
self.assertTrue(GuestAgentExtensionEventsSchema.Version in test_event)
Expand Down
115 changes: 3 additions & 112 deletions tests/ga/test_cgroupconfigurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,17 @@
import time
import threading

from nose.plugins.attrib import attr

from azurelinuxagent.common import conf
from azurelinuxagent.ga.cgroup import AGENT_NAME_TELEMETRY, MetricsCounter, MetricValue, MetricsCategory, CpuCgroup
from azurelinuxagent.ga.cgroupconfigurator import CGroupConfigurator, DisableCgroups
from azurelinuxagent.ga.cgroupstelemetry import CGroupsTelemetry
from azurelinuxagent.common.event import WALAEventOperation
from azurelinuxagent.common.exception import CGroupsException, ExtensionError, ExtensionErrorCodes, \
AgentMemoryExceededException
from azurelinuxagent.common.exception import CGroupsException, AgentMemoryExceededException
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.utils import shellutil, fileutil
from tests.lib.mock_environment import MockCommand
from tests.lib.mock_cgroup_environment import mock_cgroup_environment, UnitFilePaths
from tests.lib.tools import AgentTestCase, patch, mock_sleep, i_am_root, data_dir, is_python_version_26_or_34, skip_if_predicate_true
from tests.lib.tools import AgentTestCase, patch, mock_sleep, data_dir, is_python_version_26_or_34, skip_if_predicate_true
from tests.lib.miscellaneous_tools import format_processes, wait_for


Expand Down Expand Up @@ -526,112 +523,6 @@ def test_start_extension_command_should_disable_cgroups_and_invoke_the_command_d

self.assertEqual(len(CGroupsTelemetry._tracked), 0, "No cgroups should have been created")

@skip_if_predicate_true(is_python_version_26_or_34, "Disabled on Python 2.6 and 3.4, they run on containers where the OS commands needed by the test are not present.")
@attr('requires_sudo')
@patch('time.sleep', side_effect=lambda _: mock_sleep())
def test_start_extension_command_should_not_use_fallback_option_if_extension_fails(self, *args):
self.assertTrue(i_am_root(), "Test does not run when non-root")

with self._get_cgroup_configurator() as configurator:
pass # release the mocks used to create the test CGroupConfigurator so that they do not conflict the mock Popen below

command = "ls folder_does_not_exist"

with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stdout:
with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stderr:
with patch("azurelinuxagent.ga.cgroupapi.subprocess.Popen", wraps=subprocess.Popen) as popen_patch:
with self.assertRaises(ExtensionError) as context_manager:
configurator.start_extension_command(
extension_name="Microsoft.Compute.TestExtension-1.2.3",
command=command,
cmd_name="test",
timeout=300,
shell=True,
cwd=self.tmp_dir,
env={},
stdout=stdout,
stderr=stderr)

extension_calls = [args[0] for (args, _) in popen_patch.call_args_list if command in args[0]]

self.assertEqual(1, len(extension_calls), "The extension should have been invoked exactly once")
self.assertIn("systemd-run", extension_calls[0],
"The first call to the extension should have used systemd")

self.assertEqual(context_manager.exception.code, ExtensionErrorCodes.PluginUnknownFailure)
self.assertIn("Non-zero exit code", ustr(context_manager.exception))
# The scope name should appear in the process output since systemd-run was invoked and stderr
# wasn't truncated.
self.assertIn("Running scope as unit", ustr(context_manager.exception))

@skip_if_predicate_true(is_python_version_26_or_34, "Disabled on Python 2.6 and 3.4, they run on containers where the OS commands needed by the test are not present.")
@attr('requires_sudo')
@patch('time.sleep', side_effect=lambda _: mock_sleep())
@patch("azurelinuxagent.ga.extensionprocessutil.TELEMETRY_MESSAGE_MAX_LEN", 5)
def test_start_extension_command_should_not_use_fallback_option_if_extension_fails_with_long_output(self, *args):
self.assertTrue(i_am_root(), "Test does not run when non-root")

with self._get_cgroup_configurator() as configurator:
pass # release the mocks used to create the test CGroupConfigurator so that they do not conflict the mock Popen below

long_output = "a"*20 # large enough to ensure both stdout and stderr are truncated
long_stdout_stderr_command = "echo {0} && echo {0} >&2 && ls folder_does_not_exist".format(long_output)

with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stdout:
with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stderr:
with patch("azurelinuxagent.ga.cgroupapi.subprocess.Popen", wraps=subprocess.Popen) as popen_patch:
with self.assertRaises(ExtensionError) as context_manager:
configurator.start_extension_command(
extension_name="Microsoft.Compute.TestExtension-1.2.3",
command=long_stdout_stderr_command,
cmd_name="test",
timeout=300,
shell=True,
cwd=self.tmp_dir,
env={},
stdout=stdout,
stderr=stderr)

extension_calls = [args[0] for (args, _) in popen_patch.call_args_list if long_stdout_stderr_command in args[0]]

self.assertEqual(1, len(extension_calls), "The extension should have been invoked exactly once")
self.assertIn("systemd-run", extension_calls[0],
"The first call to the extension should have used systemd")

self.assertEqual(context_manager.exception.code, ExtensionErrorCodes.PluginUnknownFailure)
self.assertIn("Non-zero exit code", ustr(context_manager.exception))
# stdout and stderr should have been truncated, so the scope name doesn't appear in stderr
# even though systemd-run ran
self.assertNotIn("Running scope as unit", ustr(context_manager.exception))

@attr('requires_sudo')
def test_start_extension_command_should_not_use_fallback_option_if_extension_times_out(self, *args): # pylint: disable=unused-argument
self.assertTrue(i_am_root(), "Test does not run when non-root")

with self._get_cgroup_configurator() as configurator:
pass # release the mocks used to create the test CGroupConfigurator so that they do not conflict the mock Popen below

with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stdout:
with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stderr:
with patch("azurelinuxagent.ga.extensionprocessutil.wait_for_process_completion_or_timeout",
return_value=[True, None, 0]):
with patch("azurelinuxagent.ga.cgroupapi.SystemdCgroupsApi._is_systemd_failure",
return_value=False):
with self.assertRaises(ExtensionError) as context_manager:
configurator.start_extension_command(
extension_name="Microsoft.Compute.TestExtension-1.2.3",
command="date",
cmd_name="test",
timeout=300,
shell=True,
cwd=self.tmp_dir,
env={},
stdout=stdout,
stderr=stderr)

self.assertEqual(context_manager.exception.code, ExtensionErrorCodes.PluginHandlerScriptTimedout)
self.assertIn("Timeout", ustr(context_manager.exception))

@skip_if_predicate_true(is_python_version_26_or_34, "Disabled on Python 2.6 and 3.4, they run on containers where the OS commands needed by the test are not present.")
@patch('time.sleep', side_effect=lambda _: mock_sleep())
def test_start_extension_command_should_capture_only_the_last_subprocess_output(self, _):
Expand Down Expand Up @@ -1024,4 +915,4 @@ def test_check_agent_memory_usage_should_raise_a_cgroups_exception_when_the_limi
tracked_metrics.return_value = metrics
configurator.check_agent_memory_usage()

self.assertIn("The agent memory limit {0} bytes exceeded".format(conf.get_agent_memory_quota()), ustr(context_manager.exception), "An incorrect exception was raised")
self.assertIn("The agent memory limit {0} bytes exceeded".format(conf.get_agent_memory_quota()), ustr(context_manager.exception), "An incorrect exception was raised")
Loading
Loading