diff --git a/rbinstall/errors.py b/rbinstall/errors.py
index 5cff053af13788885db4ccf32ebf6eb838720a36..305365ec2ace9d755d30487e6a5f01dcccbcbdca 100644
--- a/rbinstall/errors.py
+++ b/rbinstall/errors.py
@@ -7,7 +7,11 @@ Version Added:
 # NOTE: This file must be syntactically compatible with Python 3.7+.
 from __future__ import annotations
 
-from typing import List
+from typing import List, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from rbinstall.install_methods import InstallMethodType
+    from rbinstall.state import InstallState
 
 
 class InstallerError(Exception):
@@ -58,3 +62,57 @@ class RunCommandError(InstallerError):
         super().__init__(
             f'Error executing `{command_str}`: exit code {exit_code}'
         )
+
+
+class InstallPackageError(InstallerError):
+    """An error installing a package.
+
+    Version Added:
+        1.0
+    """
+
+    def __init__(
+        self,
+        *,
+        error: RunCommandError,
+        install_state: InstallState,
+        install_method: InstallMethodType,
+        packages: List[str] = [],
+        msg: str = '',
+    ) -> None:
+        """Initialize the error.
+
+        Args:
+            error (RunCommandError):
+                The command error that triggered this error.
+
+            install_state (rbinstall.state.InstallState):
+                The state for the installation.
+
+            install_method (rbinstall.install_methods.InstallMethodType):
+                The installation method that failed.
+
+            packages (list of str, optional):
+                The packages that were being installed.
+
+            msg (str, optional):
+                A custom message to return.
+
+                This may contain ``%(package)s``, ``%(command)s``, and
+                ``%(detail)s`` format strings.
+        """
+        self.install_method = install_method
+        self.install_state = install_state
+        self.packages = packages
+
+        super().__init__(
+            (msg or (
+                'There was an error installing one or more packages '
+                '(%(packages)s). The command that failed was: `%(command)s`. '
+                'The error was: %(detail)s'
+            )) % {
+                'command': ' '.join(error.command),
+                'detail': str(error),
+                'packages': ' '.join(packages),
+            }
+        )
diff --git a/rbinstall/install_methods.py b/rbinstall/install_methods.py
index fc7e06b1241c613c7b3623b5bd86467e2c3faa8a..3ad538042abc608eae5f21a4673ced5658614ab1 100644
--- a/rbinstall/install_methods.py
+++ b/rbinstall/install_methods.py
@@ -7,7 +7,32 @@ Version Added:
 # NOTE: This file must be syntactically compatible with Python 3.7+.
 from __future__ import annotations
 
+import os
+import tempfile
+import urllib.request
 from enum import Enum
+from typing import Dict, List, Set, TYPE_CHECKING, TypeVar
+
+from rbinstall.errors import RunCommandError, InstallPackageError
+from rbinstall.process import run
+
+if TYPE_CHECKING:
+    from typing_extensions import Protocol
+
+    from rbinstall.process import RunKwargs
+    from rbinstall.state import InstallState
+
+    _T = TypeVar('_T')
+
+    class _InstallMethod(Protocol):
+        def __call__(
+            self,
+            install_state: InstallState,
+            packages: List[str],
+            *,
+            run_kwargs: RunKwargs,
+        ) -> None:
+            ...
 
 
 class InstallMethodType(str, Enum):
@@ -49,3 +74,446 @@ class InstallMethodType(str, Enum):
 
     #: An indicator of the default system package tool for a target system.
     SYSTEM_DEFAULT = 'system-default'
+
+
+def _run_apt_install(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`apt-get install` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(['apt-get', 'install', '-y'] + packages,
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.APT,
+                                  packages=packages,
+                                  error=e)
+
+
+def _run_apt_build_dep(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`apt-get build-dep` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install build-time dependencies for.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(['apt-get', 'build-dep', '-y'] + packages,
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(
+            install_state=install_state,
+            install_method=InstallMethodType.APT_BUILD_DEP,
+            packages=packages,
+            error=e)
+
+
+def _run_brew_install(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`brew` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(['brew', 'install'] + packages,
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.BREW,
+                                  packages=packages,
+                                  error=e)
+
+
+def _run_pacman_install(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`pacman -S` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(
+            [
+                'pacman',
+                '-S',
+                '--noconfirm',
+                *packages,
+            ],
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.PACMAN,
+                                  packages=packages,
+                                  error=e)
+
+
+def _run_pip_install(
+    install_state: InstallState,
+    packages: List[str],
+    extra_args: List[str] = [],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`pip install` command.
+
+    This will run the version in the target virtual environment.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+        extra_args (list of str, optional):
+            Extra arguments to pass when running :command:`pip`.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(
+            [
+                install_state['venv_pip_exe'],
+                'install',
+                '--disable-pip-version-check',
+                '--no-python-version-warning',
+                *extra_args,
+                *packages,
+            ],
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.PIP,
+                                  packages=packages,
+                                  error=e)
+
+
+def _run_remote_pyscript(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Download and run a remote Python script.
+
+    This will download the Python script to a temp file and run it.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of Python scripts to download and run.
+
+            This may only contain a single entry.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    assert len(packages) == 1
+
+    python_exe = install_state['venv_python_exe']
+    script_url = packages[0]
+
+    displayed_command = ['curl', script_url, '|', python_exe]
+
+    if run_kwargs.get('dry_run'):
+        run(displayed_command, **run_kwargs)
+    else:
+        fd, script_file = tempfile.mkstemp(suffix='.py')
+
+        try:
+            with urllib.request.urlopen(script_url) as fp:
+                os.write(fd, fp.read())
+
+            os.close(fd)
+
+            try:
+                run([python_exe, script_file],
+                    displayed_command=displayed_command,
+                    **run_kwargs)
+            except RunCommandError as e:
+                raise InstallPackageError(
+                    install_state=install_state,
+                    install_method=InstallMethodType.REMOTE_PYSCRIPT,
+                    packages=packages,
+                    error=e)
+        finally:
+            os.unlink(script_file)
+
+
+def _run_reviewboard_extra(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Install a Review Board extras package.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of Review Board extra package names.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        _run_pip_install(
+            install_state,
+            [
+                f'ReviewBoard[{extra}]'
+                for extra in packages
+            ],
+            run_kwargs=run_kwargs)
+    except InstallPackageError as e:
+        e.install_method = InstallMethodType.REVIEWBOARD_EXTRA
+        raise
+
+
+def _run_shell(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run a shell command to install a package.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The full list of command line arguments.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(packages, **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(
+            install_state=install_state,
+            install_method=InstallMethodType.SHELL,
+            error=e,
+            msg=(
+                'There was an error executing the command `%(command)s`. '
+                'The error was: %(detail)s'
+            ))
+
+
+def _run_yum_install(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`yum` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(['yum', 'install', '-y'] + packages,
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.YUM,
+                                  packages=packages,
+                                  error=e)
+
+
+def _run_zypper_install(
+    install_state: InstallState,
+    packages: List[str],
+    *,
+    run_kwargs: RunKwargs,
+) -> None:
+    """Run the :command:`zypper install` command.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_state (rbinstall.state.InstallState):
+            The state for the installation.
+
+        packages (list of str):
+            The list of packages to install.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            The package failed to install.
+    """
+    try:
+        run(
+            [
+                'zypper',
+                'install',
+                '-y',
+                *packages,
+            ],
+            **run_kwargs)
+    except RunCommandError as e:
+        raise InstallPackageError(install_state=install_state,
+                                  install_method=InstallMethodType.ZYPPER,
+                                  packages=packages,
+                                  error=e)
+
+
+def run_install_method(
+    *,
+    install_method: InstallMethodType,
+    install_state: InstallState,
+    args: List[str],
+    run_kwargs: RunKwargs = {},
+) -> None:
+    """Run an install method with the provided arguments.
+
+    Version Added:
+        1.0
+
+    Args:
+        install_method (InstallMethodType):
+            The install method type to run.
+
+        install_state (rbinstall.state.InstallState):
+            The install state for process execution.
+
+        *args (tuple):
+            The arguments to pass.
+
+    Raises:
+        rbinstall.errors.InstallPackageError:
+            There was an error installing the package.
+    """
+    if install_state['dry_run']:
+        run_kwargs['dry_run'] = True
+
+    # Allow errors to bubble up.
+    return INSTALL_METHODS[install_method](install_state, args,
+                                           run_kwargs=run_kwargs)
+
+
+#: A mapping of all install methods to handlers.
+#:
+#: Version Added:
+#:     1.0
+INSTALL_METHODS: Dict[str, _InstallMethod] = {
+    InstallMethodType.APT: _run_apt_install,
+    InstallMethodType.APT_BUILD_DEP: _run_apt_build_dep,
+    InstallMethodType.BREW: _run_brew_install,
+    InstallMethodType.PACMAN: _run_pacman_install,
+    InstallMethodType.PIP: _run_pip_install,
+    InstallMethodType.REMOTE_PYSCRIPT: _run_remote_pyscript,
+    InstallMethodType.REVIEWBOARD_EXTRA: _run_reviewboard_extra,
+    InstallMethodType.SHELL: _run_shell,
+    InstallMethodType.YUM: _run_yum_install,
+    InstallMethodType.ZYPPER: _run_zypper_install,
+}
+
+
+#: All the installer methods available on every target system.
+#:
+#: Version Added:
+#:     1.0
+COMMON_INSTALL_METHODS: Set[InstallMethodType] = {
+    InstallMethodType.PIP,
+    InstallMethodType.REMOTE_PYSCRIPT,
+    InstallMethodType.REVIEWBOARD_EXTRA,
+    InstallMethodType.SHELL,
+}
diff --git a/rbinstall/tests/test_install_methods.py b/rbinstall/tests/test_install_methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bc15626c66ae8acf23c01bd02a4c758f3ff7ef6
--- /dev/null
+++ b/rbinstall/tests/test_install_methods.py
@@ -0,0 +1,481 @@
+"""Unit tests for rbinstall.install_methods.
+
+Version Added:
+    1.0
+"""
+
+from __future__ import annotations
+
+import re
+from io import BytesIO
+from unittest import TestCase
+from urllib.request import urlopen
+
+import kgb
+
+from rbinstall.errors import InstallPackageError, RunCommandError
+from rbinstall.install_methods import InstallMethodType, run_install_method
+from rbinstall.process import run
+from rbinstall.state import InstallState
+
+
+class RunInstallMethodTests(kgb.SpyAgency, TestCase):
+    """Unit tests for run_install_method().
+
+    Version Added:
+        1.0
+    """
+
+    INSTALL_STATE: InstallState = {
+        'create_sitedir': False,
+        'dry_run': False,
+        'install_reviewbot_extension': True,
+        'install_reviewbot_worker': True,
+        'install_powerpack': True,
+        'powerpack_version_info': {
+            'is_latest': True,
+            'is_requested': True,
+            'latest_version': '5.2.2',
+            'package_name': 'ReviewBoardPowerPack',
+            'requires_python': '>=3.7',
+            'version': '5.2.2',
+        },
+        'reviewboard_version_info': {
+            'is_latest': True,
+            'is_requested': True,
+            'latest_version': '6.0',
+            'package_name': 'ReviewBoard',
+            'requires_python': '>=3.8',
+            'version': '6.0',
+        },
+        'reviewbot_extension_version_info': {
+            'is_latest': True,
+            'is_requested': True,
+            'latest_version': '4.0',
+            'package_name': 'reviewbot-extension',
+            'requires_python': '>=3.8',
+            'version': '4.0',
+        },
+        'reviewbot_worker_version_info': {
+            'is_latest': True,
+            'is_requested': True,
+            'latest_version': '4.0',
+            'package_name': 'reviewbot-worker',
+            'requires_python': '>=3.8',
+            'version': '4.0',
+        },
+        'sitedir_path': '/var/www/reviewboard',
+        'steps': [],
+        'system_info': {
+            'arch': 'amd64',
+            'bootstrap_python_exe': '/path/to/bootstrap/python',
+            'paths': {},
+            'system_install_method': InstallMethodType.APT,
+            'system': 'Linux',
+            'system_python_exe': '/usr/bin/python',
+            'system_python_version': (3, 11, 0, '', 0),
+            'version': '1.2.3',
+        },
+        'unattended_install': False,
+        'venv_path': '/path/to/venv',
+        'venv_pip_exe': '/path/to/venv/bin/pip',
+        'venv_python_exe': '/path/to/venv/bin/python',
+    }
+
+    def test_with_apt(self) -> None:
+        """Testing run_install_method with APT"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.APT,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['apt-get', 'install', '-y', 'package1', 'package2'])
+
+    def test_with_apt_and_error(self) -> None:
+        """Testing run_install_method with APT and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `apt-get install -y '
+            'package1 package2`. The error was: Error executing `apt-get '
+            'install -y package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.APT,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.APT)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_apt_build_dep(self) -> None:
+        """Testing run_install_method with APT_BUILD_DEP"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.APT_BUILD_DEP,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['apt-get', 'build-dep', '-y', 'package1', 'package2'])
+
+    def test_with_apt_build_dep_and_error(self) -> None:
+        """Testing run_install_method with APT_BUILD_DEP and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `apt-get build-dep -y '
+            'package1 package2`. The error was: Error executing `apt-get '
+            'build-dep -y package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.APT_BUILD_DEP,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.APT_BUILD_DEP)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_brew(self) -> None:
+        """Testing run_install_method with BREW"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.BREW,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['brew', 'install', 'package1', 'package2'])
+
+    def test_with_brew_and_error(self) -> None:
+        """Testing run_install_method with BREW and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `brew install package1 '
+            'package2`. The error was: Error executing `brew install '
+            'package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.BREW,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.BREW)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_pacman(self) -> None:
+        """Testing run_install_method with PACMAN"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.PACMAN,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['pacman', '-S', '--noconfirm', 'package1', 'package2'])
+
+    def test_with_pacman_and_error(self) -> None:
+        """Testing run_install_method with PACMAN and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `pacman -S --noconfirm '
+            'package1 package2`. The error was: Error executing `pacman -S '
+            '--noconfirm package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.PACMAN,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.PACMAN)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_pip(self) -> None:
+        """Testing run_install_method with PIP"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.PIP,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            [
+                '/path/to/venv/bin/pip', 'install',
+                '--disable-pip-version-check',
+                '--no-python-version-warning',
+                'package1', 'package2',
+            ])
+
+    def test_with_pip_and_error(self) -> None:
+        """Testing run_install_method with PIP and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `/path/to/venv/bin/pip '
+            'install --disable-pip-version-check --no-python-version-warning '
+            'package1 package2`. The error was: Error executing '
+            '`/path/to/venv/bin/pip install --disable-pip-version-check '
+            '--no-python-version-warning package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.PIP,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.PIP)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_remote_pyscript(self) -> None:
+        """Testing run_install_method with REMOTE_PYSCRIPT"""
+        self.spy_on(run, call_original=False)
+        self.spy_on(
+            urlopen,
+            op=kgb.SpyOpReturn(BytesIO(
+                b'import sys\n'
+                b'sys.exit(0)\n'
+            )))
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.REMOTE_PYSCRIPT,
+                           args=['https://install.example.com'])
+
+        self.assertSpyCalled(urlopen)
+        self.assertSpyCalled(run)
+
+    def test_with_remote_pyscript_and_error(self) -> None:
+        """Testing run_install_method with REMOTE_PYSCRIPT and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        self.spy_on(
+            urlopen,
+            op=kgb.SpyOpReturn(BytesIO(
+                b'import sys\n'
+                b'sys.exit(1)\n'
+            )))
+
+        message = (
+            r'There was an error installing one or more packages '
+            r'\(https://install\.example\.com\). The command that failed was: '
+            r'`/path/to/venv/bin/python .+`. The error was: Error executing '
+            r'`/path/to/venv/bin/python .*`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(
+                install_state=self.INSTALL_STATE,
+                install_method=InstallMethodType.REMOTE_PYSCRIPT,
+                args=['https://install.example.com'])
+
+        self.assertSpyCalled(urlopen)
+        self.assertSpyCalled(run)
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.REMOTE_PYSCRIPT)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['https://install.example.com'])
+
+    def test_with_reviewboard_extra(self) -> None:
+        """Testing run_install_method with REVIEWBOARD_EXTRA"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.REVIEWBOARD_EXTRA,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            [
+                '/path/to/venv/bin/pip', 'install',
+                '--disable-pip-version-check',
+                '--no-python-version-warning',
+                'ReviewBoard[package1]', 'ReviewBoard[package2]',
+            ])
+
+    def test_with_reviewboard_extra_and_error(self) -> None:
+        """Testing run_install_method with REVIEWBOARD_EXTRA and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages '
+            '(ReviewBoard[package1] ReviewBoard[package2]). The command that '
+            'failed was: `/path/to/venv/bin/pip install '
+            '--disable-pip-version-check --no-python-version-warning '
+            'ReviewBoard[package1] ReviewBoard[package2]`. The error was: '
+            'Error executing `/path/to/venv/bin/pip install '
+            '--disable-pip-version-check --no-python-version-warning '
+            'ReviewBoard[package1] ReviewBoard[package2]`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(
+                install_state=self.INSTALL_STATE,
+                install_method=InstallMethodType.REVIEWBOARD_EXTRA,
+                args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.REVIEWBOARD_EXTRA)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages,
+                         ['ReviewBoard[package1]', 'ReviewBoard[package2]'])
+
+    def test_with_shell(self) -> None:
+        """Testing run_install_method with SHELL"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.SHELL,
+                           args=['some-command', '--arg1', '--arg2'])
+
+        self.assertSpyCalledWith(
+            run,
+            [
+                'some-command',
+                '--arg1',
+                '--arg2',
+            ])
+
+    def test_with_shell_and_error(self) -> None:
+        """Testing run_install_method with SHELL and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error executing the command `some-command --arg1 '
+            '--arg2`. The error was: Error executing `some-command --arg1 '
+            '--arg2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(
+                install_state=self.INSTALL_STATE,
+                install_method=InstallMethodType.SHELL,
+                args=['some-command', '--arg1', '--arg2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.SHELL)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, [])
+
+    def test_with_yum(self) -> None:
+        """Testing run_install_method with YUM"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.YUM,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['yum', 'install', '-y', 'package1', 'package2'])
+
+    def test_with_yum_and_error(self) -> None:
+        """Testing run_install_method with YUM and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `yum install -y '
+            'package1 package2`. The error was: Error executing '
+            '`yum install -y package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.YUM,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.YUM)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
+
+    def test_with_zypper(self) -> None:
+        """Testing run_install_method with ZYPPER"""
+        self.spy_on(run, call_original=False)
+
+        run_install_method(install_state=self.INSTALL_STATE,
+                           install_method=InstallMethodType.ZYPPER,
+                           args=['package1', 'package2'])
+
+        self.assertSpyCalledWith(
+            run,
+            ['zypper', 'install', '-y', 'package1', 'package2'])
+
+    def test_with_zypper_and_error(self) -> None:
+        """Testing run_install_method with ZYPPER and error"""
+        @self.spy_for(run)
+        def _run(command, **kwargs):
+            raise RunCommandError(command=command,
+                                  exit_code=1)
+
+        message = re.escape(
+            'There was an error installing one or more packages (package1 '
+            'package2). The command that failed was: `zypper install -y '
+            'package1 package2`. The error was: Error executing '
+            '`zypper install -y package1 package2`: exit code 1'
+        )
+
+        with self.assertRaisesRegex(InstallPackageError, message) as ctx:
+            run_install_method(install_state=self.INSTALL_STATE,
+                               install_method=InstallMethodType.ZYPPER,
+                               args=['package1', 'package2'])
+
+        e = ctx.exception
+        self.assertEqual(e.install_method, InstallMethodType.ZYPPER)
+        self.assertEqual(e.install_state, self.INSTALL_STATE)
+        self.assertEqual(e.packages, ['package1', 'package2'])
