boumenot: Sync'ed offline.  LGTM.
diff --git a/azurelinuxagent/common/utils/processutil.py b/azurelinuxagent/common/utils/processutil.py
index fe9dd4a5..9c0eb24b 100644
--- a/azurelinuxagent/common/utils/processutil.py
+++ b/azurelinuxagent/common/utils/processutil.py
@@ -16,7 +16,7 @@
 #
 # Requires Python 2.6+ and Openssl 1.0+
 #
-
+import multiprocessing
 import subprocess
 import sys
 import os
@@ -100,75 +100,57 @@ def _destroy_process(process, signal_to_send=signal.SIGKILL):
         pass    # If the process is already gone, that's fine
 
 
-def capture_from_process_modern(process, cmd, timeout):
-    try:
-        stdout, stderr = process.communicate(timeout=timeout)
-    except subprocess.TimeoutExpired:
-        # Just kill the process. The .communicate method will gather stdout/stderr, close those pipes, and reap
-        # the zombie process. That is, .communicate() does all the other stuff that _destroy_process does.
+def capture_from_process_poll(process, cmd, timeout):
+    """
+    If the process forks, we cannot capture anything we block until the process tree completes
+    """
+    retry = timeout
+    while retry > 0 and process.poll() is None:
+        time.sleep(1)
+        retry -= 1
+
+    # process did not fork, timeout expired
+    if retry == 0:
         os.killpg(os.getpgid(process.pid), signal.SIGKILL)
         stdout, stderr = process.communicate()
         msg = format_stdout_stderr(sanitize(stdout), sanitize(stderr))
         raise ExtensionError("Timeout({0}): {1}\n{2}".format(timeout, cmd, msg))
-    except OSError as e:
-        _destroy_process(process, signal.SIGKILL)
-        raise ExtensionError("Error while running '{0}': {1}".format(cmd, e.strerror))
-    except ValueError:
-        _destroy_process(process, signal.SIGKILL)
-        raise ExtensionError("Invalid timeout ({0}) specified for '{1}'".format(timeout, cmd))
-    except Exception as e:
-        _destroy_process(process, signal.SIGKILL)
-        raise ExtensionError("Exception while running '{0}': {1}".format(cmd, e))
 
-    return stdout, stderr
+    # process completed or forked
+    return_code = process.wait()
+    if return_code != 0:
+        raise ExtensionError("Non-zero exit code: {0}, {1}".format(return_code, cmd))
 
+    stderr = b''
+    stdout = b'cannot collect stdout'
 
-def capture_from_process_pre_33(process, cmd, timeout):
-    """
-    Can't use process.communicate(timeout=), so do it the hard way.
-    """
-    watcher_process_exited = 0
-    watcher_process_timed_out = 1
-
-    def kill_on_timeout(pid, watcher_timeout):
-        """
-        Check for the continued existence of pid once per second. If pid no longer exists, exit with code 0.
-        If timeout (in seconds) elapses, kill pid and exit with code 1.
-        """
-        for iteration in range(watcher_timeout):
-            time.sleep(1)
-            try:
-                os.kill(pid, 0)
-            except OSError as ex:
-                if ESRCH == ex.errno:   # Process no longer exists
-                    exit(watcher_process_exited)
-        os.killpg(os.getpgid(pid), signal.SIGKILL)
-        exit(watcher_process_timed_out)
-
-    watcher = Process(target=kill_on_timeout, args=(process.pid, timeout))
-    watcher.start()
+    # attempt non-blocking process communication to capture output
+    def proc_comm(_process, _return):
+        try:
+            _stdout, _stderr = _process.communicate()
+            _return[0] = _stdout
+            _return[1] = _stderr
+        except Exception:
+            pass
 
     try:
-        # Now, block "forever" waiting on process. If the timeout-limited Event wait in the watcher pops,
-        # it will kill the process and Popen.communicate() will return
-        stdout, stderr = process.communicate()
-    except OSError as e:
-        _destroy_process(process, signal.SIGKILL)
-        raise ExtensionError("Error while running '{0}': {1}".format(cmd, e.strerror))
-    except Exception as e:
-        _destroy_process(process, signal.SIGKILL)
-        raise ExtensionError("Exception while running '{0}': {1}".format(cmd, e))
+        mgr = multiprocessing.Manager()
+        ret_dict = mgr.dict()
 
-    timeout_happened = False
-    watcher.join(1)
-    if watcher.is_alive():
-        watcher.terminate()
-    else:
-        timeout_happened = (watcher.exitcode == watcher_process_timed_out)
+        cproc = Process(target=proc_comm, args=(process, ret_dict))
+        cproc.start()
 
-    if timeout_happened:
-        msg = format_stdout_stderr(sanitize(stdout), sanitize(stderr))
-        raise ExtensionError("Timeout({0}): {1}\n{2}".format(timeout, cmd, msg))
+        # allow 1s to capture output
+        cproc.join(1)
+
+        if cproc.is_alive():
+            cproc.terminate()
+
+        stdout = ret_dict[0]
+        stderr = ret_dict[1]
+
+    except Exception:
+        pass
 
     return stdout, stderr
 
@@ -204,10 +186,7 @@ def capture_from_process_raw(process, cmd, timeout):
             _destroy_process(process, signal.SIGKILL)
             raise ExtensionError("Subprocess was not root of its own process group")
 
-        if sys.version_info < (3, 3):
-            stdout, stderr = capture_from_process_pre_33(process, cmd, timeout)
-        else:
-            stdout, stderr = capture_from_process_modern(process, cmd, timeout)
+        stdout, stderr = capture_from_process_poll(process, cmd, timeout)
 
     return stdout, stderr
 
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index 650eaf0f..4d10b024 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -113,7 +113,7 @@ def get_distro():
 
 AGENT_NAME = "WALinuxAgent"
 AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.29'
+AGENT_VERSION = '2.2.30'
 AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
 AGENT_DESCRIPTION = """
 The Azure Linux Agent supports the provisioning and running of Linux
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index 21cc2ef2..f46447ab 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -1002,12 +1002,12 @@ class ExtHandlerInstance(object):
                 CGroups.add_to_extension_cgroup(self.ext_handler.name)
 
             process = subprocess.Popen(full_path,
-                                  shell=True,
-                                  cwd=base_dir,
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE,
-                                  env=os.environ,
-                                  preexec_fn=pre_exec_function)
+                                       shell=True,
+                                       cwd=base_dir,
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.PIPE,
+                                       env=os.environ,
+                                       preexec_fn=pre_exec_function)
         except OSError as e:
             raise ExtensionError("Failed to launch '{0}': {1}".format(full_path, e.strerror))
 
@@ -1016,7 +1016,9 @@ class ExtHandlerInstance(object):
         msg = capture_from_process(process, cmd, timeout)
 
         ret = process.poll()
-        if ret is None or ret != 0:
+        if ret is None:
+            raise ExtensionError("Process {0} was not terminated: {1}\n{2}".format(process.pid, cmd, msg))
+        if ret != 0:
             raise ExtensionError("Non-zero exit code: {0}, {1}\n{2}".format(ret, cmd, msg))
 
         duration = elapsed_milliseconds(begin_utc)

Agent Prematurely Terminates Long Running Extensions
A user opened an issue against [Azure/custom-script-extension-linux #139](https://github.com/Azure/custom-script-extension-linux/issues/139) about the extension prematurely timing out.  I have verified this issue against 2.2.29.

```sh
az vm extension set -g my-resource-group --vm-name vm1 --publisher Microsoft.Azure.Extensions --name CustomScript --version 2.0 --settings "{ \"commandToExecute\": \"sleep 360; ls\" }"
```

The agent will kill the process after five minutes, which is different from previous behavior.  The logs for the agent claim that it moved the extension PID into a cGroup, but the PID it referenced in the logs is the PID *agent ext-handler process* not the extension.
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index a3aa7ae9..af5dcbba 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -1000,8 +1000,8 @@ class TestUpdate(UpdateTestCase):
         self.assertTrue(2 < len(self.update_handler.agents))
 
         # Purge every other agent
-        kept_agents = self.update_handler.agents[1::2]
-        purged_agents = self.update_handler.agents[::2]
+        kept_agents = self.update_handler.agents[::2]
+        purged_agents = self.update_handler.agents[1::2]
 
         # Reload and assert only the kept agents remain on disk
         self.update_handler.agents = kept_agents
diff --git a/tests/utils/test_process_util.py b/tests/utils/test_process_util.py
index 85abdb5a..a950e556 100644
--- a/tests/utils/test_process_util.py
+++ b/tests/utils/test_process_util.py
@@ -14,12 +14,12 @@
 #
 # Requires Python 2.6+ and Openssl 1.0+
 #
-
+import datetime
 import subprocess
 
 from azurelinuxagent.common.exception import ExtensionError
 from azurelinuxagent.common.utils.processutil \
-    import format_stdout_stderr, capture_from_process, capture_from_process_raw
+    import format_stdout_stderr, capture_from_process
 from tests.tools import *
 import sys
 
@@ -121,7 +121,14 @@ class TestProcessUtils(AgentTestCase):
         actual = capture_from_process(process, cmd)
         self.assertEqual(expected, actual)
 
-    def test_process_timeout(self):
+    def test_process_timeout_non_forked(self):
+        """
+        non-forked process runs for 20 seconds, timeout is 10 seconds
+        we expect:
+            - test to run in just over 10 seconds
+            - exception should be thrown
+            - output should be collected
+        """
         cmd = "{0} -t 20".format(process_target)
         process = subprocess.Popen(cmd,
                                    shell=True,
@@ -130,19 +137,93 @@ class TestProcessUtils(AgentTestCase):
                                    env=os.environ,
                                    preexec_fn=os.setsid)
 
-        if sys.version_info < (2, 7):
-            self.assertRaises(ExtensionError, capture_from_process_raw, process, cmd, 10)
-        else:
-            with self.assertRaises(ExtensionError) as ee:
-                capture_from_process_raw(process, cmd, 10)
+        try:
+            capture_from_process(process, 'sleep 20', 10)
+            self.fail('Timeout exception was expected')
+        except ExtensionError as e:
+            body = str(e)
+            self.assertTrue('Timeout(10)' in body)
+            self.assertTrue('Iteration 9' in body)
+            self.assertFalse('Iteration 11' in body)
+        except Exception as gen_ex:
+            self.fail('Unexpected exception: {0}'.format(gen_ex))
 
-            body = str(ee.exception)
-            if sys.version_info >= (3, 2):
-                self.assertNotRegex(body, "Iteration 12")
-                self.assertRegex(body, "Iteration 8")
-            else:
-                self.assertNotRegexpMatches(body, "Iteration 12")
-                self.assertRegexpMatches(body, "Iteration 8")
+    def test_process_timeout_forked(self):
+        """
+        forked process runs for 20 seconds, timeout is 10 seconds
+        we expect:
+            - test to run in less than 3 seconds
+            - no exception should be thrown
+            - no output is collected
+        """
+        cmd = "{0} -t 20 &".format(process_target)
+        process = subprocess.Popen(cmd,
+                                   shell=True,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   env=os.environ,
+                                   preexec_fn=os.setsid)
+
+        start = datetime.datetime.utcnow()
+        try:
+            cap = capture_from_process(process, 'sleep 20 &', 10)
+        except Exception as e:
+            self.fail('No exception should be thrown for a long running process which forks: {0}'.format(e))
+        duration = datetime.datetime.utcnow() - start
+
+        self.assertTrue(duration < datetime.timedelta(seconds=3))
+        self.assertEqual('[stdout]\ncannot collect stdout\n\n[stderr]\n', cap)
+
+    def test_process_behaved_non_forked(self):
+        """
+        non-forked process runs for 10 seconds, timeout is 20 seconds
+        we expect:
+            - test to run in just over 10 seconds
+            - no exception should be thrown
+            - output should be collected
+        """
+        cmd = "{0} -t 10".format(process_target)
+        process = subprocess.Popen(cmd,
+                                   shell=True,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   env=os.environ,
+                                   preexec_fn=os.setsid)
+
+        try:
+            body = capture_from_process(process, 'sleep 10', 20)
+        except Exception as gen_ex:
+            self.fail('Unexpected exception: {0}'.format(gen_ex))
+
+        self.assertFalse('Timeout' in body)
+        self.assertTrue('Iteration 9' in body)
+        self.assertTrue('Iteration 10' in body)
+
+    def test_process_behaved_forked(self):
+        """
+        forked process runs for 10 seconds, timeout is 20 seconds
+        we expect:
+            - test to run in under 3 seconds
+            - no exception should be thrown
+            - output is not collected
+        """
+        cmd = "{0} -t 10 &".format(process_target)
+        process = subprocess.Popen(cmd,
+                                   shell=True,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   env=os.environ,
+                                   preexec_fn=os.setsid)
+
+        start = datetime.datetime.utcnow()
+        try:
+            body = capture_from_process(process, 'sleep 10 &', 20)
+        except Exception as e:
+            self.fail('No exception should be thrown for a well behaved process which forks: {0}'.format(e))
+        duration = datetime.datetime.utcnow() - start
+
+        self.assertTrue(duration < datetime.timedelta(seconds=3))
+        self.assertEqual('[stdout]\ncannot collect stdout\n\n[stderr]\n', body)
 
     def test_process_bad_pgid(self):
         """

pip install -e .
parse_log_pytest
pytest
nose pytest
apt-get update apt-get install -y gcc
3.4
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
nose==1.3.7
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
-e git+https://github.com/Azure/WALinuxAgent.git@066d711c4dd3f5a166a19da1910ee92b35cd3cbb#egg=WALinuxAgent
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: WALinuxAgent
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - nose==1.3.7
prefix: /opt/conda/envs/WALinuxAgent

tests/ga/test_update.py::TestUpdate::test_purge_agents tests/utils/test_process_util.py::TestProcessUtils::test_process_behaved_forked tests/utils/test_process_util.py::TestProcessUtils::test_process_timeout_forked
tests/ga/test_update.py::TestGuestAgentError::test_clear tests/ga/test_update.py::TestGuestAgentError::test_creation tests/ga/test_update.py::TestGuestAgentError::test_mark_failure tests/ga/test_update.py::TestGuestAgentError::test_mark_failure_permanent tests/ga/test_update.py::TestGuestAgentError::test_save tests/ga/test_update.py::TestGuestAgentError::test_str tests/ga/test_update.py::TestGuestAgent::test_clear_error tests/ga/test_update.py::TestGuestAgent::test_creation tests/ga/test_update.py::TestGuestAgent::test_download tests/ga/test_update.py::TestGuestAgent::test_download_fail tests/ga/test_update.py::TestGuestAgent::test_download_fallback tests/ga/test_update.py::TestGuestAgent::test_ensure_download_skips_blacklisted tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_download_fails tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_load_manifest_fails tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_unpack_fails tests/ga/test_update.py::TestGuestAgent::test_ioerror_not_blacklisted tests/ga/test_update.py::TestGuestAgent::test_is_available tests/ga/test_update.py::TestGuestAgent::test_is_blacklisted tests/ga/test_update.py::TestGuestAgent::test_is_downloaded tests/ga/test_update.py::TestGuestAgent::test_load_error tests/ga/test_update.py::TestGuestAgent::test_load_manifest tests/ga/test_update.py::TestGuestAgent::test_load_manifest_is_empty tests/ga/test_update.py::TestGuestAgent::test_load_manifest_is_malformed tests/ga/test_update.py::TestGuestAgent::test_load_manifest_missing tests/ga/test_update.py::TestGuestAgent::test_mark_failure tests/ga/test_update.py::TestGuestAgent::test_resource_gone_error_not_blacklisted tests/ga/test_update.py::TestGuestAgent::test_unpack tests/ga/test_update.py::TestGuestAgent::test_unpack_fail tests/ga/test_update.py::TestUpdate::test_creation tests/ga/test_update.py::TestUpdate::test_emit_restart_event_emits_event_if_not_clean_start tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_ignores_exceptions tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_kills_after_interval tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_skips_if_no_orphans tests/ga/test_update.py::TestUpdate::test_ensure_partition_assigned tests/ga/test_update.py::TestUpdate::test_ensure_readonly_leaves_unmodified tests/ga/test_update.py::TestUpdate::test_ensure_readonly_sets_readonly tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_ignores_installed_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_raises_exception_for_restarting_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_resets_with_new_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_will_not_raise_exception_for_long_restarts tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_will_not_raise_exception_too_few_restarts tests/ga/test_update.py::TestUpdate::test_filter_blacklisted_agents tests/ga/test_update.py::TestUpdate::test_find_agents tests/ga/test_update.py::TestUpdate::test_find_agents_does_reload tests/ga/test_update.py::TestUpdate::test_find_agents_sorts tests/ga/test_update.py::TestUpdate::test_get_host_plugin_returns_host_for_wireserver tests/ga/test_update.py::TestUpdate::test_get_host_plugin_returns_none_otherwise tests/ga/test_update.py::TestUpdate::test_get_latest_agent tests/ga/test_update.py::TestUpdate::test_get_latest_agent_excluded tests/ga/test_update.py::TestUpdate::test_get_latest_agent_no_updates tests/ga/test_update.py::TestUpdate::test_get_latest_agent_skip_updates tests/ga/test_update.py::TestUpdate::test_get_latest_agent_skips_unavailable tests/ga/test_update.py::TestUpdate::test_get_pid_files tests/ga/test_update.py::TestUpdate::test_get_pid_files_returns_previous tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_false_for_exceptions tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_false_when_sentinel_exists tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_true_when_no_sentinel tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_false_if_parent_exists tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_true_if_parent_does_not_exist tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_true_if_parent_is_init tests/ga/test_update.py::TestUpdate::test_is_version_available tests/ga/test_update.py::TestUpdate::test_is_version_available_accepts_current tests/ga/test_update.py::TestUpdate::test_is_version_available_rejects tests/ga/test_update.py::TestUpdate::test_is_version_available_rejects_by_default tests/ga/test_update.py::TestUpdate::test_package_filter_for_agent_manifest tests/ga/test_update.py::TestUpdate::test_run tests/ga/test_update.py::TestUpdate::test_run_clears_sentinel_on_successful_exit tests/ga/test_update.py::TestUpdate::test_run_emits_restart_event tests/ga/test_update.py::TestUpdate::test_run_keeps_running tests/ga/test_update.py::TestUpdate::test_run_latest tests/ga/test_update.py::TestUpdate::test_run_latest_captures_signals tests/ga/test_update.py::TestUpdate::test_run_latest_creates_only_one_signal_handler tests/ga/test_update.py::TestUpdate::test_run_latest_defaults_to_current tests/ga/test_update.py::TestUpdate::test_run_latest_exception_blacklists tests/ga/test_update.py::TestUpdate::test_run_latest_exception_does_not_blacklist_if_terminating tests/ga/test_update.py::TestUpdate::test_run_latest_forwards_output tests/ga/test_update.py::TestUpdate::test_run_latest_nonzero_code_marks_failures tests/ga/test_update.py::TestUpdate::test_run_latest_passes_child_args tests/ga/test_update.py::TestUpdate::test_run_latest_polling_stops_at_failure tests/ga/test_update.py::TestUpdate::test_run_latest_polling_stops_at_success tests/ga/test_update.py::TestUpdate::test_run_latest_polls_and_waits_for_success tests/ga/test_update.py::TestUpdate::test_run_latest_polls_frequently_if_installed_is_latest tests/ga/test_update.py::TestUpdate::test_run_latest_polls_moderately_if_installed_not_latest tests/ga/test_update.py::TestUpdate::test_run_leaves_sentinel_on_unsuccessful_exit tests/ga/test_update.py::TestUpdate::test_run_stops_if_orphaned tests/ga/test_update.py::TestUpdate::test_run_stops_if_update_available tests/ga/test_update.py::TestUpdate::test_set_agents_sets_agents tests/ga/test_update.py::TestUpdate::test_set_agents_sorts_agents tests/ga/test_update.py::TestUpdate::test_set_sentinel tests/ga/test_update.py::TestUpdate::test_set_sentinel_writes_current_agent tests/ga/test_update.py::TestUpdate::test_shutdown tests/ga/test_update.py::TestUpdate::test_shutdown_ignores_exceptions tests/ga/test_update.py::TestUpdate::test_shutdown_ignores_missing_sentinel_file tests/ga/test_update.py::TestUpdate::test_update_available_returns_true_if_current_gets_blacklisted tests/ga/test_update.py::TestUpdate::test_upgrade_available_handles_missing_family tests/ga/test_update.py::TestUpdate::test_upgrade_available_includes_old_agents tests/ga/test_update.py::TestUpdate::test_upgrade_available_purges_old_agents tests/ga/test_update.py::TestUpdate::test_upgrade_available_returns_true_on_first_use tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_if_too_frequent tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_if_when_no_new_versions tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_when_no_versions tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_when_updates_are_disabled tests/ga/test_update.py::TestUpdate::test_upgrade_available_sorts tests/ga/test_update.py::TestUpdate::test_upgrade_available_will_refresh_goal_state tests/ga/test_update.py::TestUpdate::test_write_pid_file tests/ga/test_update.py::TestUpdate::test_write_pid_file_ignores_exceptions tests/ga/test_update.py::MonitorThreadTest::test_check_if_env_thread_is_alive tests/ga/test_update.py::MonitorThreadTest::test_check_if_monitor_thread_is_alive tests/ga/test_update.py::MonitorThreadTest::test_restart_env_thread tests/ga/test_update.py::MonitorThreadTest::test_restart_env_thread_if_not_alive tests/ga/test_update.py::MonitorThreadTest::test_restart_monitor_thread tests/ga/test_update.py::MonitorThreadTest::test_restart_monitor_thread_if_not_alive tests/ga/test_update.py::MonitorThreadTest::test_start_threads tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr00 tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr01 tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr02 tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr03 tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr04 tests/utils/test_process_util.py::TestProcessUtils::test_format_stdout_stderr05 tests/utils/test_process_util.py::TestProcessUtils::test_process_bad_pgid tests/utils/test_process_util.py::TestProcessUtils::test_process_behaved_non_forked tests/utils/test_process_util.py::TestProcessUtils::test_process_stdout_stderr tests/utils/test_process_util.py::TestProcessUtils::test_process_timeout_non_forked
azurelinuxagent/common/version.py azurelinuxagent/common/utils/processutil.py azurelinuxagent/ga/exthandlers.py
azurelinuxagent/common/version.py azurelinuxagent/common/utils/processutil.py azurelinuxagent/ga/exthandlers.py