mirror of
https://github.com/python-poetry/install.python-poetry.org.git
synced 2024-11-25 15:30:56 -05:00
feat: log stderr
This commit is contained in:
parent
89f4049e20
commit
8db565cd0f
4 changed files with 212 additions and 8 deletions
21
.github/workflows/installer.yml
vendored
21
.github/workflows/installer.yml
vendored
|
@ -19,6 +19,27 @@ concurrency:
|
|||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
name: ${{ matrix.python-version }}
|
||||
runs-on: Ubuntu
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install pytest
|
||||
run: python -m pip install pytest
|
||||
|
||||
- name: Run PyTest
|
||||
run: python -m pytest ./tests
|
||||
|
||||
default:
|
||||
name: ${{ matrix.os }} / ${{ matrix.python-version }} / install-poetry.py ${{ matrix.args }}
|
||||
runs-on: ${{ matrix.image }}
|
||||
|
|
10
README.md
10
README.md
|
@ -83,6 +83,16 @@ You can also install Poetry for a `git` repository by using the `--git` option:
|
|||
curl -sSL https://install.python-poetry.org | python3 - --git https://github.com/python-poetry/poetry.git@master
|
||||
````
|
||||
|
||||
If you need Poetry to write errors to stderr, you can use `--stderr` option or the `$POETRY_LOG_STDERR`
|
||||
environment variable:
|
||||
|
||||
> _Note: In CI environments, this will be enabled automatically._
|
||||
|
||||
```bash
|
||||
curl -sSL https://install.python-poetry.org | python3 - --stderr
|
||||
curl -sSL https://install.python-poetry.org | POETRY_LOG_STDERR=1 python3 -
|
||||
````
|
||||
|
||||
> **Note**: The installer does not support Python < 3.6.
|
||||
|
||||
## Known Issues
|
||||
|
|
|
@ -898,6 +898,15 @@ def main():
|
|||
"of Poetry available online."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stderr",
|
||||
dest="stderr",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Log installation errors to stderr instead of a log file."
|
||||
),
|
||||
default=False
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -912,6 +921,15 @@ def main():
|
|||
git=args.git,
|
||||
)
|
||||
|
||||
disable_log_file = args.stderr or string_to_bool(os.getenv("POETRY_LOG_STDERR", "0"))
|
||||
|
||||
if not disable_log_file and string_to_bool(os.getenv("CI", "0")):
|
||||
installer._write(
|
||||
colorize("info", "CI environment detected. Writing logs to stderr.")
|
||||
)
|
||||
disable_log_file = True
|
||||
|
||||
|
||||
if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")):
|
||||
return installer.uninstall()
|
||||
|
||||
|
@ -923,6 +941,14 @@ def main():
|
|||
if e.log is not None:
|
||||
import traceback
|
||||
|
||||
error = (
|
||||
f"{e.log}\n"
|
||||
f"Traceback:\n\n{''.join(traceback.format_tb(e.__traceback__))}"
|
||||
)
|
||||
|
||||
if disable_log_file:
|
||||
installer._write(colorize("error", error))
|
||||
else:
|
||||
_, path = tempfile.mkstemp(
|
||||
suffix=".log",
|
||||
prefix="poetry-installer-error-",
|
||||
|
@ -930,8 +956,7 @@ def main():
|
|||
text=True,
|
||||
)
|
||||
installer._write(colorize("error", f"See {path} for error logs."))
|
||||
text = f"{e.log}\nTraceback:\n\n{''.join(traceback.format_tb(e.__traceback__))}"
|
||||
Path(path).write_text(text)
|
||||
Path(path).write_text(error)
|
||||
|
||||
return e.return_code
|
||||
|
||||
|
|
148
tests/test_install_poetry.py
Normal file
148
tests/test_install_poetry.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
import re
|
||||
import typing
|
||||
import importlib
|
||||
import unittest
|
||||
|
||||
|
||||
module = importlib.import_module("install-poetry")
|
||||
|
||||
|
||||
class InstallPoetryTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.__patchers = []
|
||||
|
||||
self.__patchers.append(patch("install-poetry.Installer"))
|
||||
self.__mock_installer_cls = self.__patchers[-1].start()
|
||||
self.__installer = MagicMock()
|
||||
self.__mock_installer_cls.return_value = self.__installer
|
||||
|
||||
self.__patchers.append(patch("install-poetry.argparse.ArgumentParser"))
|
||||
self.__mock_argument_parser_cls = self.__patchers[-1].start()
|
||||
self.__parser = MagicMock()
|
||||
self.__args = MagicMock()
|
||||
self.__args.version = None
|
||||
self.__args.preview = False
|
||||
self.__args.force = False
|
||||
self.__args.accept_all = False
|
||||
self.__args.path = None
|
||||
self.__args.git = None
|
||||
self.__args.stderr = False
|
||||
self.__args.uninstall = False
|
||||
self.__mock_argument_parser_cls.return_value = self.__parser
|
||||
self.__parser.parse_args.return_value = self.__args
|
||||
|
||||
self.__patchers.append(patch("install-poetry.os"))
|
||||
self.__mock_os = self.__patchers[-1].start()
|
||||
self.__mock_os.getenv.side_effect = self.__getenv
|
||||
self.__env = {}
|
||||
|
||||
self.__patchers.append(patch("install-poetry.colorize"))
|
||||
self.__mock_colorize = self.__patchers[-1].start()
|
||||
self.__mock_colorize.side_effect = self.__colorize
|
||||
self.__messages = []
|
||||
|
||||
self.__patchers.append(patch("install-poetry.tempfile"))
|
||||
self.__mock_tempfile = self.__patchers[-1].start()
|
||||
self.__mock_tempfile.mkstemp.side_effect = self.__mkstemp
|
||||
self.__tmp_file = None
|
||||
|
||||
self.__patchers.append(patch("install-poetry.Path"))
|
||||
self.__mock_path_cls = self.__patchers[-1].start()
|
||||
|
||||
def tearDown(self):
|
||||
for patcher in self.__patchers:
|
||||
patcher.stop()
|
||||
|
||||
def test_install_poetry_main__happy(self):
|
||||
self.__installer.run.return_value = 0
|
||||
|
||||
return_code = module.main()
|
||||
|
||||
self.__mock_installer_cls.assert_called_with(
|
||||
version=None,
|
||||
preview=False,
|
||||
force=False,
|
||||
accept_all=True,
|
||||
path=None,
|
||||
git=None
|
||||
)
|
||||
|
||||
self.__installer.uninstall.assert_not_called()
|
||||
|
||||
self.assertEqual(return_code, 0)
|
||||
|
||||
def test_install_poetry_main__default_install_error(self):
|
||||
self.__installer.run.side_effect = [
|
||||
module.PoetryInstallationError(1, "a fake poetry installation error")
|
||||
]
|
||||
|
||||
return_code = module.main()
|
||||
|
||||
self.__assert_no_matching_message("error", re.compile("a fake poetry installation error"))
|
||||
self.__assert_any_matching_message("error", re.compile(f"See {self.__tmp_file} for error logs"))
|
||||
|
||||
self.assertEqual(return_code, 1)
|
||||
self.__mock_path_cls(self.__tmp_file).write_text.assert_called_once()
|
||||
|
||||
def test_install_poetry_main__stderr_arg(self):
|
||||
self.__args.stderr = True
|
||||
self.__installer.run.side_effect = [
|
||||
module.PoetryInstallationError(1, "a fake poetry installation error")
|
||||
]
|
||||
|
||||
return_code = module.main()
|
||||
|
||||
self.__assert_no_matching_message("info", re.compile("CI environment detected"))
|
||||
self.__assert_any_matching_message("error", re.compile("a fake poetry installation error"))
|
||||
|
||||
self.assertEqual(return_code, 1)
|
||||
self.__mock_path_cls.assert_not_called()
|
||||
|
||||
def test_install_poetry_main__log_stderr_var(self):
|
||||
self.__env["POETRY_LOG_STDERR"] = "1"
|
||||
self.__installer.run.side_effect = [
|
||||
module.PoetryInstallationError(1, "a fake poetry installation error")
|
||||
]
|
||||
|
||||
return_code = module.main()
|
||||
|
||||
self.__assert_no_matching_message("info", re.compile("CI environment detected"))
|
||||
self.__assert_any_matching_message("error", re.compile("a fake poetry installation error"))
|
||||
|
||||
self.assertEqual(return_code, 1)
|
||||
self.__mock_path_cls.assert_not_called()
|
||||
|
||||
def test_install_poetry_main__ci(self):
|
||||
self.__env["CI"] = "1"
|
||||
self.__installer.run.side_effect = [
|
||||
module.PoetryInstallationError(1, "a fake poetry installation error")
|
||||
]
|
||||
|
||||
return_code = module.main()
|
||||
|
||||
self.__assert_any_matching_message("info", re.compile("CI environment detected"))
|
||||
self.__assert_any_matching_message("error", re.compile("a fake poetry installation error"))
|
||||
|
||||
self.assertEqual(return_code, 1)
|
||||
self.__mock_path_cls.assert_not_called()
|
||||
|
||||
def __colorize(self, severity: str, message: str) -> str:
|
||||
self.__messages.append((severity, message))
|
||||
return f"{severity}:{message}"
|
||||
|
||||
def __getenv(self, key: str, default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
return self.__env.get(key, default)
|
||||
|
||||
def __mkstemp(self, suffix="suffix", prefix="prefix", dir=None, text=None):
|
||||
self.__tmp_file = f"{prefix}unittest{suffix}"
|
||||
return None, self.__tmp_file
|
||||
|
||||
def __assert_any_matching_message(self, severity: str, pattern: re.Pattern):
|
||||
self.assertGreater(self.__count_matching_message(severity, pattern), 0)
|
||||
|
||||
def __assert_no_matching_message(self, severity: str, pattern: re.Pattern):
|
||||
self.assertEqual(self.__count_matching_message(severity, pattern), 0)
|
||||
|
||||
def __count_matching_message(self, severity: str, pattern: re.Pattern):
|
||||
return len([message for message in self.__messages if severity == message[0] and pattern.search(message[1])])
|
Loading…
Reference in a new issue