Skip to content

Commit 14b5e88

Browse files
committed
Version 1.0.1: restore backward compatibility; full test coverage
1 parent 191f34b commit 14b5e88

File tree

9 files changed

+99
-47
lines changed

9 files changed

+99
-47
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ After installing the OSLog wrappers (via `python -m pip install pyobjc-framework
120120
python -m unittest
121121
```
122122

123-
Please note that if Console.app is live-streaming messages, some tests may fail.
124-
See [`test_logging.py`](https://github.com/simonrob/pyoslog/blob/main/tests/test_logging.py#L93) for discussion about why this is the case.
123+
All of pyoslog's code is covered by tests, but please note that if Console.app is live-streaming messages, some tests may fail.
124+
See [`test_logging.py`](https://github.com/simonrob/pyoslog/blob/main/tests/test_logging.py#L96) for discussion about why this is the case.
125125

126126

127127
## Alternatives

build.sh

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#!/bin/bash
2+
set -Eeuo pipefail
3+
14
# this build script is quite forceful about setup - make sure not to mess up the system python
25
PYTHON_VENV=$(python -c "import sys; sys.stdout.write('1') if hasattr(sys, 'real_prefix') or sys.base_prefix != sys.prefix else sys.stdout.write('0')")
36
if [ "$PYTHON_VENV" == 0 ]; then
@@ -8,34 +11,60 @@ fi
811
module=${PWD##*/} # get module name - relies on directory name == module name
912
module=${module:-/}
1013

11-
printf "\nPreparing environment for %s…\n" "$module"
12-
python -m pip install --quiet --upgrade pip
13-
python -m pip install --quiet setuptools wheel twine mypy sphinx # we don't have a requirements.txt, so easiest just to be sure build dependencies exist every time
14-
rm -rf dist
1514
rm -rf build
15+
rm -rf dist
16+
17+
# check and upload built files with whatever python binary is globally set; use custom pyenv pyoslog versions below for building
18+
python -m pip install --quiet --upgrade pip
19+
python -m pip install --quiet twine
20+
21+
if [ "${1:-0}" == 'all' ]; then
22+
versions=(36 37 38 39 310) # run as "./build.sh all" to build with all reasonable python versions
23+
else
24+
versions=(default)
25+
fi
26+
for i in "${versions[@]}"; do
27+
printf "\nBuilding %s with Python %s\n" "$module" "$i"
28+
if [ "$i" == 'default' ]; then
29+
python_binary='python'
30+
else
31+
python_binary="${PYENV_ROOT}/versions/pyoslog$i/bin/python"
32+
fi
33+
34+
printf "\nPreparing environment for %s…\n" "$module"
35+
$python_binary -m pip install --quiet --upgrade pip
36+
$python_binary -m pip install --quiet setuptools wheel coverage mypy sphinx # we don't have a requirements.txt, so easiest just to be sure build dependencies exist every time
1637

17-
python -m pip install --quiet --force-reinstall . # install pyoslog itself
38+
$python_binary -m pip install --quiet --force-reinstall . # install pyoslog itself (for tests)
1839

19-
printf '\nType checking %s…\n' "$module"
20-
python -m mypy pyoslog
40+
printf '\nType checking %s…\n' "$module"
41+
$python_binary -m mypy pyoslog
2142

22-
printf '\nRunning tests for %s…\n' "$module"
23-
python -m pip install --quiet pyobjc-framework-OSLog # separated from other installations because it will fail on unsupported platforms
24-
(cd tests && python -m unittest) # we don't fail on failed tests because they need a macOS version above our minimum
43+
# note: for 100% test coverage, building must take place on macOS 12 or later, and debug logging must be enabled for all log objects:
44+
# > sudo log config --mode 'level:debug' --subsystem 'ac.robinson.pyoslog'
45+
# > sudo log config --mode 'level:debug'
46+
# (the system mode can be restored to default afterwards via: `sudo log config --mode 'level:default'`)
47+
printf '\nRunning tests and checking coverage for %s…\n' "$module"
48+
$python_binary -m pip install --quiet pyobjc-framework-OSLog # separated from other installations because it will fail on unsupported platforms
49+
(cd tests && $python_binary -m coverage run -m unittest) # note re: test output - we selectively skip tests where they need a macOS version above our minimum
50+
set +e # in environments we can't test (e.g., no OSLog framework) we don't care if coverage fails
51+
(cd tests && $python_binary -m coverage html --include '*/pyoslog/*' --omit '*test*')
52+
set -e
2553

26-
printf '\nBuilding documentation for %s…\n' "$module"
27-
python -m pip install --quiet -r docs/requirements.txt
28-
export SPHINXOPTS=-q
29-
(cd docs && make clean html)
54+
printf '\nBuilding documentation for %s…\n' "$module"
55+
$python_binary -m pip install --quiet -r docs/requirements.txt
56+
export SPHINXOPTS=-q
57+
(cd docs && make clean html)
3058

31-
printf '\nBuilding source and wheel (universal) distributions for %s ("can'\''t clean" messages can be ignored)…\n' "$module"
32-
python setup.py -q clean --all sdist bdist_wheel --universal
59+
printf '\nBuilding source and wheel (universal) distributions for %s ("can'\''t clean" messages can be ignored)…\n' "$module"
60+
$python_binary setup.py -q clean --all sdist bdist_wheel --universal
61+
done
3362

3463
printf '\nValidating %s packages…\n' "$module"
3564
python -m twine check dist/*
3665

37-
if [ -z "$1" ]; then # exit unless a parameter is provided (we use 'deploy' but don't actually care what it is)
38-
printf '\nExiting build script - run "./build.sh deploy" to also upload to PyPi / Read the Docs (please git commit first)\n\n'
66+
if [ "${1:-0}" != 'deploy' ]; then
67+
printf '\nBuild complete - exiting script. Run "./build.sh deploy" to also upload to PyPi / Read the Docs (please git commit first)\n\n'
3968
exit 0
4069
fi
4170

@@ -45,7 +74,7 @@ read -r answer
4574
if [ "$answer" != "${answer#[Yy]}" ]; then
4675
PYPI_TEST_TOKEN=$(<versions/pypi-test.token)
4776
if python -m twine upload -u __token__ -p "$PYPI_TEST_TOKEN" --repository testpypi dist/*; then
48-
echo "Upload of $module completed – install via: python -m pip install --force-reinstall --index-url https://test.pypi.org/simple/ $module"
77+
echo "Upload of $module completed – install via: python -m pip install --force-reinstall --index-url https://test.pypi.org/simple/ $module (forcing version via $module==X.X.X if needed)"
4978
else
5079
echo "Error uploading $module; exiting"
5180
exit 1
@@ -68,6 +97,7 @@ if [ "$answer" != "${answer#[Yy]}" ]; then
6897
READTHEDOCS_TOKEN=$(<versions/readthedocs.token)
6998
echo "Triggering a Read the Docs build for $module – view at https://$module.readthedocs.io"
7099
curl -X POST -H "Authorization: Token $READTHEDOCS_TOKEN" "https://readthedocs.org/api/v3/projects/$module/versions/latest/builds/"
100+
echo ''
71101
else
72102
echo "Error: $module upload failed"
73103
exit 1

pyoslog/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__title__ = 'pyoslog'
2-
__version__ = '1.0.0'
2+
__version__ = '1.0.1'
33
__description__ = 'Send messages to the macOS unified logging system (os_log)'
44
__author__ = 'Simon Robinson'
55
__author_email__ = '[email protected]'

pyoslog/compatibility.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import sys
44

55

6-
def is_supported() -> bool:
6+
def is_supported():
77
"""Unified logging is only present in macOS 10.12 and later, but it is nicer to not have to check OS type or version
88
strings when installing or importing the module - use this method at runtime to check whether it is supported. It is
99
important to note that if is_supported() is `False` then none of the module's other methods/constants will exist."""
1010
supported = sys.platform == 'darwin' and sys.version_info >= (3, 0,) and float(
1111
'.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.12
12-
if not supported and os.environ.get('PYOSLOG_OVERRIDE_IS_SUPPORTED', ''):
13-
print('Warning: overriding is_supported() on an unsupported platform (use to build documentation only)')
12+
if os.environ.get('PYOSLOG_OVERRIDE_IS_SUPPORTED', ''):
13+
print('Warning: overriding pyoslog.is_supported() to return True in all cases - '
14+
'use only to build documentation and/or run tests')
1415
return True
1516
return supported
17+
18+
19+
is_supported.__annotations__ = {'return': bool}

pyoslog/core.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
try:
55
import _pyoslog # type: ignore
6-
except ImportError:
6+
except ImportError: # pragma: no cover
77
# noinspection PyPep8Naming
88
class _pyoslog: # type: ignore
9-
print('Warning: mocking _pyoslog class on an unsupported platform (use to build documentation only)')
9+
print('Warning: mocking _pyoslog class on an unsupported platform - use to build documentation only')
1010
OS_LOG_TYPE_DEFAULT = 0
1111
OS_LOG_TYPE_INFO = 0
1212
OS_LOG_TYPE_DEBUG = 0
@@ -40,11 +40,11 @@ def __repr__(self) -> str:
4040
OS_LOG_DISABLED = os_log_t(None, None, None) # type: ignore
4141
OS_LOG_DISABLED._description = '<os_log_t (OS_LOG_DISABLED)>'
4242

43-
OS_LOG_TYPE_DEFAULT: int = _pyoslog.OS_LOG_TYPE_DEFAULT
44-
OS_LOG_TYPE_INFO: int = _pyoslog.OS_LOG_TYPE_INFO
45-
OS_LOG_TYPE_DEBUG: int = _pyoslog.OS_LOG_TYPE_DEBUG
46-
OS_LOG_TYPE_ERROR: int = _pyoslog.OS_LOG_TYPE_ERROR
47-
OS_LOG_TYPE_FAULT: int = _pyoslog.OS_LOG_TYPE_FAULT
43+
OS_LOG_TYPE_DEFAULT = _pyoslog.OS_LOG_TYPE_DEFAULT # type: int
44+
OS_LOG_TYPE_INFO = _pyoslog.OS_LOG_TYPE_INFO # type: int
45+
OS_LOG_TYPE_DEBUG = _pyoslog.OS_LOG_TYPE_DEBUG # type: int
46+
OS_LOG_TYPE_ERROR = _pyoslog.OS_LOG_TYPE_ERROR # type: int
47+
OS_LOG_TYPE_FAULT = _pyoslog.OS_LOG_TYPE_FAULT # type: int
4848

4949

5050
def os_log_create(subsystem: str, category: str) -> os_log_t:

setup.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23

34
import setuptools
45

@@ -15,10 +16,13 @@
1516
# see compatibility.py - allow installation on older versions (or other OSs) but provide is_supported() at runtime
1617
compatibility_module_name = 'compatibility'
1718
compatibility_module_path = os.path.join(working_directory, '%s.py' % compatibility_module_name)
19+
is_old_python_version = sys.version_info < (3, 5,)
1820
try:
1921
# noinspection PyUnresolvedReferences
2022
import importlib.util
2123

24+
if is_old_python_version: # importlib.util.module_from_spec wasn't always present
25+
raise ImportError('importlib.util.module_from_spec is not available')
2226
spec = importlib.util.spec_from_file_location(compatibility_module_name, compatibility_module_path)
2327
compatibility = importlib.util.module_from_spec(spec)
2428
spec.loader.exec_module(compatibility)
@@ -48,13 +52,13 @@
4852
'Source Code': about['__url__'],
4953
},
5054

51-
# 3.6+ for core.py constants inline type hints, but not enabled because we support installation on earlier versions
52-
# python_requires='>=3.6',
53-
5455
platforms=['darwin'],
5556
packages=[NAME],
5657
ext_modules=ext_modules,
5758

59+
# could do, e.g., 'typing;python_version<"3.5"' but some old versions don't support that syntax...
60+
install_requires=['typing'] if is_old_python_version else [],
61+
5862
package_data={'': ['LICENSE']},
5963
include_package_data=True,
6064

tests/test_handler.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import platform
3-
import sys
43
import unittest
54

65
import pkg_resources
@@ -14,17 +13,20 @@
1413

1514
class TestHandler(unittest.TestCase):
1615
def setUp(self):
16+
tests_supported_macos = float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 12
1717
try:
1818
import OSLog
19+
if not tests_supported_macos:
20+
raise ImportError('unsupported macOS version for testing')
1921
except ImportError:
20-
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
22+
if pyoslog.is_supported() and tests_supported_macos:
2123
skip_reason = 'Warning: cannot import pyobjc\'s OSLog; unable to run tests (run `pip install ' \
2224
'pyobjc-framework-OSLog`)'
2325
print(skip_reason)
2426
raise unittest.SkipTest(skip_reason)
2527
else:
26-
skip_reason = 'Warning: pyobjc\'s OSLog is not supported on this platform (requires macOS 10.15+); ' \
27-
'unable to test logging Handler'
28+
skip_reason = 'Warning: pyobjc\'s OSLog is not fully supported on this platform (requires macOS ' \
29+
'12+); unable to test logging Handler'
2830
print(skip_reason)
2931
raise unittest.SkipTest(skip_reason)
3032

tests/test_logging.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import platform
2-
import sys
32
import unittest
43

54
import pkg_resources
@@ -13,17 +12,20 @@
1312

1413
class TestLogging(unittest.TestCase):
1514
def setUp(self):
15+
supported_macos = float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 12
1616
try:
1717
import OSLog
18+
if not supported_macos:
19+
raise ImportError('unsupported macOS version for testing')
1820
except ImportError:
19-
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
21+
if pyoslog.is_supported() and supported_macos:
2022
skip_reason = 'Warning: cannot import pyobjc\'s OSLog; unable to run tests (run `pip install ' \
2123
'pyobjc-framework-OSLog`)'
2224
print(skip_reason)
2325
raise unittest.SkipTest(skip_reason)
2426
else:
25-
skip_reason = 'Warning: pyobjc\'s OSLog is not supported on this platform (requires macOS 10.15+); ' \
26-
'unable to test os_log output'
27+
skip_reason = 'Warning: pyobjc\'s OSLog is not fully supported on this platform (requires macOS ' \
28+
'12+); unable to test os_log output'
2729
print(skip_reason)
2830
raise unittest.SkipTest(skip_reason)
2931

@@ -62,11 +64,15 @@ def test_os_log_type_enabled(self):
6264

6365
def test_os_log_info_enabled(self):
6466
# note that os_log_info_enabled() just calls os_log_type_enabled - more thorough testing can be found there
65-
self.test_os_log_type_enabled()
67+
expected_value = pyoslog.os_log_type_enabled(pyoslog.OS_LOG_DEFAULT,
68+
pyoslog_test_globals.TestLogTypes.OS_LOG_TYPE_INFO.value)
69+
self.assertEqual(pyoslog.os_log_info_enabled(pyoslog.OS_LOG_DEFAULT), expected_value)
6670

6771
def test_os_log_debug_enabled(self):
6872
# note that os_log_debug_enabled() just calls os_log_type_enabled - more thorough testing can be found there
69-
self.test_os_log_type_enabled()
73+
expected_value = pyoslog.os_log_type_enabled(pyoslog.OS_LOG_DEFAULT,
74+
pyoslog_test_globals.TestLogTypes.OS_LOG_TYPE_DEBUG.value)
75+
self.assertEqual(pyoslog.os_log_debug_enabled(pyoslog.OS_LOG_DEFAULT), expected_value)
7076

7177
def test_os_log_with_type(self):
7278
# PyArg_ParseTuple in _pyoslog.c handles type validation - just ensure objects are required and test boundaries
@@ -90,7 +96,7 @@ def test_os_log_with_type(self):
9096
pyoslog.os_log_with_type(pyoslog.OS_LOG_DEFAULT, log_type.value, sent_message)
9197
received_message = pyoslog_test_globals.get_latest_log_message(self.log_store)
9298

93-
# only test types that are enabled - note that starting streaming in Console.app seems to enable all levels,
99+
# only test types that are enabled - note: starting streaming in Console.app appears to enable all levels,
94100
# but actually starts "STREAM_LIVE" mode (see `sudo log config --status`) which makes os_log_type_enabled
95101
# return True for all levels, but doesn't let OSLog retrieve them - as a result, some tests will fail when
96102
# Console.app is live-streaming logs

tests/test_setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import platform
23
import sys
34
import unittest
@@ -29,7 +30,12 @@ def test_is_supported(self):
2930
# a little pointless since our platform check and that from is_supported() are identical, but no other option
3031
matching_platform = sys.platform == 'darwin' and sys.version_info >= (3, 0,) and float(
3132
'.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.12
32-
self.assertEqual(matching_platform, pyoslog.is_supported())
33+
self.assertEqual(pyoslog.is_supported(), matching_platform)
34+
35+
# a little contrived, but might as well test the documentation building special case
36+
os.environ['PYOSLOG_OVERRIDE_IS_SUPPORTED'] = '1'
37+
self.assertEqual(pyoslog.is_supported(), True)
38+
del os.environ['PYOSLOG_OVERRIDE_IS_SUPPORTED']
3339

3440
def test_os_log_create(self):
3541
log = pyoslog.os_log_create(pyoslog_test_globals.LOG_SUBSYSTEM, pyoslog_test_globals.LOG_CATEGORY)

0 commit comments

Comments
 (0)