-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add retry to check_sudo_status and unit test (#278)
* add unit test for check_sudo_status() * add unit case for insufficient outputline and unexpected output * fix mock_insufficient_run_command_output * fix mock_insufficient_run_command_output * reomve retry_attempt constant * refactor func description * remove eol in test_coremain.py * mock env_layer instead * move mock check_sudo_check logic to new bootstrapper test file * remove the import adodbapi * refactor Bootstrapper * move check_sudo_status into a retry method * undo check_sudo_status logic and add attempt checks in check_sudo_status_with_retry * change inner logerr to logdebug and move sudo status check log to retry * fix rety try return * modify the sudo failed log and add line to eol and add data type to check_sudo_status * add check for None and return false retry when attempts exhausted * add logic for handling sudo_status is false or none * raise exception when sudo_status is None or false * set retry to return none and update signature
- Loading branch information
Showing
4 changed files
with
171 additions
and
14 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# Copyright 2020 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. | ||
# | ||
# Requires Python 2.7+ | ||
import unittest | ||
|
||
from core.src.bootstrap.Constants import Constants | ||
from core.tests.library.ArgumentComposer import ArgumentComposer | ||
from core.tests.library.RuntimeCompositor import RuntimeCompositor | ||
|
||
|
||
class TestBootstrapper(unittest.TestCase): | ||
def setUp(self): | ||
self.sudo_check_status_attempts = 0 | ||
Constants.SET_CHECK_SUDO_STATUS_TRUE = False | ||
argument_composer = ArgumentComposer() | ||
argument_composer.operation = Constants.ASSESSMENT | ||
self.argv = argument_composer.get_composed_arguments() | ||
self.runtime = RuntimeCompositor(self.argv, legacy_mode=True, package_manager_name=Constants.APT) | ||
|
||
def tearDown(self): | ||
self.sudo_check_status_attempts = 0 | ||
Constants.SET_CHECK_SUDO_STATUS_TRUE = True | ||
self.runtime.stop() | ||
|
||
# regions mock | ||
def mock_false_run_command_output(self, command, no_output=False, chk_err=True): | ||
"""Mock a failed sudo check status command output to test retry logic.""" | ||
# Mock failure to trigger retry logic in check_sudo_status | ||
return (1, "[sudo] password for user:\nFalse") | ||
|
||
def mock_insufficient_run_command_output(self, command, no_output=False, chk_err=True): | ||
"""Mock an insufficient output line in sudo check status command output.""" | ||
# Mock failure to trigger retry logic in check_sudo_status | ||
return (1, "[sudo] password for user:") | ||
|
||
def mock_unexpected_output_run_command_output(self, command, no_output=False, chk_err=True): | ||
"""Mock an unexpected output line in sudo check status command output.""" | ||
# Mock failure to trigger retry logic in check_sudo_status | ||
return (1, "[sudo] password for user:\nUnexpectedOutput") | ||
|
||
def mock_retry_run_command_output(self, command, no_output=False, chk_err=True): | ||
"""Mock 3 failed sudo check status attempts followed by a success on the 4th attempt.""" | ||
self.sudo_check_status_attempts += 1 | ||
|
||
# Mock failure on the first two attempts | ||
if self.sudo_check_status_attempts <= 2: | ||
return (1, "[sudo] password for user:\nFalse") | ||
|
||
# Mock success (True) on the 3rd attempt | ||
elif self.sudo_check_status_attempts == 3: | ||
return (0, "uid=0(root) gid=0(root) groups=0(root)\nTrue") | ||
# end regions mock | ||
|
||
def test_check_sudo_status_all_attempts_failed(self): | ||
# Set raise_if_not_sudo=False to test the `return False` all attempts failed | ||
self.runtime.env_layer.run_command_output = self.mock_false_run_command_output | ||
|
||
result = self.runtime.bootstrapper.check_sudo_status_with_retry(raise_if_not_sudo=False) | ||
|
||
# Verify check_sudo_status_with_retry is False | ||
self.assertEqual(result, None, "Expected check_sudo_status retry to return None after all attempts failed") | ||
|
||
def test_check_sudo_status_throw_exception(self): | ||
# Set raise_if_not_sudo=True to throw exception) after all retries | ||
self.runtime.env_layer.run_command_output = self.mock_false_run_command_output | ||
with self.assertRaises(Exception) as context: | ||
self.runtime.bootstrapper.check_sudo_status_with_retry(raise_if_not_sudo=True) | ||
|
||
# Verify exception msg contains the expected failure text | ||
self.assertTrue("Unable to invoke sudo successfully" in str(context.exception)) | ||
|
||
def test_check_sudo_status_insufficient_output_lines(self): | ||
# Test insufficient output lines to raise exception after all retries | ||
self.runtime.env_layer.run_command_output = self.mock_insufficient_run_command_output | ||
|
||
with self.assertRaises(Exception) as context: | ||
self.runtime.bootstrapper.check_sudo_status_with_retry() | ||
|
||
# Verify exception msg contains the expected failure text | ||
self.assertTrue("Unexpected sudo check result" in str(context.exception)) | ||
|
||
def test_check_sudo_status_unexpected_output_lines(self): | ||
# Test unexpected output with neither false or true to raise exception after all retries | ||
self.runtime.env_layer.run_command_output = self.mock_unexpected_output_run_command_output | ||
|
||
with self.assertRaises(Exception) as context: | ||
self.runtime.bootstrapper.check_sudo_status_with_retry() | ||
|
||
# Verify exception msg contains the expected failure text | ||
self.assertTrue("Unexpected sudo check result" in str(context.exception)) | ||
|
||
def test_check_sudo_status_succeeds_on_third_attempt(self): | ||
# Test retry logic in check sudo status after 2 failed attempts followed by success (true) | ||
self.runtime.env_layer.run_command_output = self.mock_retry_run_command_output | ||
|
||
# Attempt to check sudo status, succeed (true) on the 3rd attempt | ||
result = self.runtime.bootstrapper.check_sudo_status_with_retry(raise_if_not_sudo=True) | ||
|
||
# Verify the result is success (True) | ||
self.assertTrue(result, "Expected check_sudo_status to succeed on the 3rd attempts") | ||
|
||
# Verify 3 attempts were made | ||
self.assertEqual(self.sudo_check_status_attempts, 3, "Expected exactly 3 attempts in check_sudo_status") | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
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