Skip to content

Commit 471f1ec

Browse files
committed
Add a full test suite.
1 parent 9776c95 commit 471f1ec

7 files changed

Lines changed: 167 additions & 34 deletions

File tree

.github/workflows/ci.yml

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,29 @@ jobs:
3535

3636
- uses: actions/setup-python@v5
3737
with:
38-
python-version: "3.13"
38+
python-version: "3.x"
3939

4040
- name: Build wheels
4141
uses: pypa/cibuildwheel@v3.4.1
42+
with:
43+
output-dir: dist
4244

4345
- uses: actions/upload-artifact@v4
4446
with:
45-
name: wheels-${{ matrix.platform }}
46-
path: ./wheelhouse/*.whl
47+
name: wheels
48+
path: ./dist/*.whl
4749
if-no-files-found: error
4850

4951
build-sdist:
5052
needs: pre-commit
5153
name: Build sdist
52-
runs-on: macos-14
54+
runs-on: macos-latest
5355
steps:
5456
- uses: actions/checkout@v4
5557

5658
- uses: actions/setup-python@v5
5759
with:
58-
python-version: "3.13"
60+
python-version: "3.x"
5961

6062
- name: Build sdist
6163
run: |
@@ -69,32 +71,28 @@ jobs:
6971
if-no-files-found: error
7072

7173
smoke-test:
72-
name: Smoke test
74+
name: Smoke test (Python ${{ matrix.python-version }})
7375
needs: build-wheels
7476
runs-on: macos-latest
77+
strategy:
78+
fail-fast: false
79+
matrix:
80+
python-version: ["py310", "py311", "py312", "py313", "py314", "py315"]
7581
steps:
7682
- uses: actions/checkout@v4
7783

7884
- uses: actions/setup-python@v5
7985
with:
80-
python-version: "3.13"
86+
python-version: "3.x"
8187

8288
- uses: actions/download-artifact@v4
8389
with:
84-
name: wheels-macos
85-
path: dist
90+
name: wheels
91+
path: wheelhouse
8692

87-
- name: Install and smoke-test the wheel
93+
- name: Install tox
8894
run: |
89-
python -m pip install --upgrade pip
90-
python -m pip install --find-links=dist --no-index --pre std-nslog
91-
python -c "
92-
import sys, io, nslog
93-
assert isinstance(sys.stdout, nslog.NSLogWriter)
94-
assert isinstance(sys.stderr, nslog.NSLogWriter)
95-
print('hello from std-nslog smoke test')
96-
print('this is stderr', file=sys.stderr)
97-
# nslog() should accept all log levels.
98-
nslog.nslog('explicit info', level=nslog.OS_LOG_TYPE_INFO)
99-
nslog.nslog('explicit error', level=nslog.OS_LOG_TYPE_ERROR)
100-
"
95+
python -m pip install --group tox
96+
97+
- name: Run smoke test
98+
run: tox -e py

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ lib64/
2020
parts/
2121
sdist/
2222
var/
23+
wheelhouse/
2324
*.egg-info/
2425
.installed.cfg
2526
*.egg
@@ -102,5 +103,3 @@ runtime/
102103

103104
# Ruff linter cache files
104105
.ruff_cache
105-
106-
wheelhouse/

README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
[<img src="http://beeware.org/static/images/defaultlogo.png" width="72px" alt="Generic BeeWare Logo">](https://beeware.org/)
2-
31
# std-nslog
42

5-
[![Python Versions](https://img.shields.io/pypi/pyversions/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog)
6-
[![PyPI Version](https://img.shields.io/pypi/v/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog)
7-
[![Maturity](https://img.shields.io/pypi/status/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog)
8-
[![BSD License](https://img.shields.io/pypi/l/std-nslog.svg)](https://github.com/beeware/std-nslog/blob/main/LICENSE)
9-
[![Discord server](https://img.shields.io/discord/836455665257021440?label=Discord%20Chat&logo=discord&style=plastic)](https://beeware.org/bee/chat/)
3+
[<img src="http://beeware.org/static/images/defaultlogo.png" width="72px" alt="Generic BeeWare Logo">](https://beeware.org/)
4+
5+
[![Python Versions](https://img.shields.io/pypi/pyversions/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog) [![PyPI Version](https://img.shields.io/pypi/v/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog) [![Maturity](https://img.shields.io/pypi/status/std-nslog.svg)](https://pypi.python.org/pypi/std-nslog) [![BSD License](https://img.shields.io/pypi/l/std-nslog.svg)](https://github.com/beeware/std-nslog/blob/main/LICENSE) [![Discord server](https://img.shields.io/discord/836455665257021440?label=Discord%20Chat&logo=discord&style=plastic)](https://beeware.org/bee/chat/)
106

117
std-nslog is a shim that redirects stderr/stdout to the Apple unified logging system (`os_log`, the modern replacement for `NSLog`). This can be useful when deploying Python code as a standalone app on macOS or iOS, where stdout/stderr aren't easily visible for end users.
128

9+
This functionality is baked into Python 3.14+ if you are using an embedded interpreter - set `config.use_system_logger = 1` when configuring the interpreter. The same functionality exists in all iOS CPython builds (where it is enabled by default).
10+
1311
## Usage
1412

15-
std-nslog will generally only be needed in the context of a bundled macOS or iOS application. You shouldn't ever need to install it yourself into a local virtual environment. However, just in case...
13+
std-nslog will generally only be needed in the context of a bundled macOS application. You shouldn't ever need to install it yourself into a local virtual environment. However, just in case...
1614

1715
To install std-nslog:
1816

@@ -22,8 +20,6 @@ Then, in your code, `import nslog`. This will install the shim. The file only ne
2220

2321
Once installed, all output written to stdout and stderr will be redirected to the unified logging system; stdout is logged at `OS_LOG_TYPE_DEFAULT`, and stderr at `OS_LOG_TYPE_ERROR`, so the two streams can be filtered separately in Console.app or `log stream`.
2422

25-
To associate the log entries with a specific subsystem and category (useful for filtering with `log stream --predicate 'subsystem == "..."'`), set the `STD_NSLOG_SUBSYSTEM` and `STD_NSLOG_CATEGORY` environment variables before importing `nslog`. If unset, the default system log is used.
26-
2723
## Why no pun?
2824

2925
While an obscure joke referencing apples and logs might be amusing, it would make no sense when it appeared in a Briefcase requirements file.

pyproject.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ classifiers = [
2727
"Programming Language :: Python :: 3.11",
2828
"Programming Language :: Python :: 3.12",
2929
"Programming Language :: Python :: 3.13",
30+
"Programming Language :: Python :: 3.14",
31+
"Programming Language :: Python :: 3.15",
3032
"Programming Language :: Python :: 3 :: Only",
3133
"Topic :: Software Development",
3234
"Topic :: Utilities",
@@ -38,6 +40,32 @@ Funding = "https://beeware.org/contributing/membership/"
3840
Tracker = "https://github.com/beeware/std-nslog/issues"
3941
Source = "https://github.com/beeware/std-nslog"
4042

43+
[dependency-groups]
44+
# Extras used by developers *of* briefcase are pinned to specific versions to
45+
# ensure environment consistency.
46+
pre-commit = [
47+
"pre-commit == 4.6.0",
48+
]
49+
50+
setuptools-scm = [
51+
"setuptools_scm == 10.0.5",
52+
]
53+
54+
tox-uv = [
55+
"tox-uv == 1.35.2",
56+
]
57+
58+
dev = [
59+
{include-group = "pre-commit"},
60+
{include-group = "setuptools-scm"},
61+
{include-group = "tox-uv"},
62+
]
63+
64+
test = [
65+
"pytest == 9.0.3",
66+
{include-group = "setuptools-scm"},
67+
]
68+
4169
[tool.cibuildwheel]
4270
build-frontend = "build"
4371
archs = "all"

tests/smoke.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
std-nslog smoke test driver.
3+
4+
This file is a *helper* for `test_smoke.py`, not a test itself.
5+
It imports nslog, and outputs some known strings to the console.
6+
"""
7+
8+
import sys
9+
10+
import nslog
11+
12+
13+
def main() -> None:
14+
tag = sys.argv[1]
15+
16+
# Importing nslog should have replaced both standard streams with
17+
# NSLogWriter instances; if it didn't, nothing else here is meaningful.
18+
assert isinstance(sys.stdout, nslog.NSLogWriter), (
19+
f"sys.stdout was not replaced by NSLogWriter (got {type(sys.stdout)!r})"
20+
)
21+
assert isinstance(sys.stderr, nslog.NSLogWriter), (
22+
f"sys.stderr was not replaced by NSLogWriter (got {type(sys.stderr)!r})"
23+
)
24+
25+
# Exercise stdout, stderr, and direct nslog() calls at multiple log
26+
# levels. Each line is tagged so the CI verifier can pick them out of
27+
# the unified log unambiguously.
28+
print(f"{tag} hello from std-nslog smoke test")
29+
print(f"{tag} this is stderr", file=sys.stderr)
30+
nslog.nslog(f"{tag} explicit info", level=nslog.OS_LOG_TYPE_INFO)
31+
nslog.nslog(f"{tag} explicit error", level=nslog.OS_LOG_TYPE_ERROR)
32+
33+
# NSLogWriter buffers partial lines until a newline arrives; flush
34+
# explicitly so nothing is dropped before the unified-log query runs.
35+
sys.stdout.flush()
36+
sys.stderr.flush()
37+
38+
39+
if __name__ == "__main__":
40+
main()

tests/test_smoke.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import sys
5+
import time
6+
import uuid
7+
from pathlib import Path
8+
9+
10+
def test_system_log():
11+
tag = f"std-nslog-smoke-{uuid.uuid4()}"
12+
13+
result = subprocess.run(
14+
[sys.executable, Path(__file__).parent / "smoke.py", tag],
15+
capture_output=True,
16+
text=True,
17+
check=False,
18+
)
19+
20+
# Assert that the smoke test ran without error.
21+
assert result.returncode == 0
22+
23+
# Nothing the script `print()`ed should have reached the inherited
24+
# stdio: NSLogWriter routes every byte to os_log instead. Anything
25+
# showing up here would mean the redirection failed.
26+
assert result.stdout == "", (
27+
f"unexpected bytes on subprocess stdout: {result.stdout!r}"
28+
)
29+
assert result.stderr == "", (
30+
f"unexpected bytes on subprocess stderr: {result.stderr!r}"
31+
)
32+
33+
# Give the unified logging subsystem a moment to commit the writes
34+
# before querying. Two seconds matches the existing CI margin and is
35+
# well within the `log show` window we request below.
36+
time.sleep(2)
37+
38+
log = subprocess.check_output(
39+
["log", "show", "--last", "10s", "--info"],
40+
text=True,
41+
)
42+
43+
missing = [
44+
msg
45+
for msg in [
46+
"hello from std-nslog smoke test",
47+
"this is stderr",
48+
"explicit info",
49+
"explicit error",
50+
]
51+
if f"{tag} {msg}" not in log
52+
]
53+
assert not missing, (
54+
"Expected message(s) not found in `log show` output: "
55+
+ ", ".join(repr(m) for m in missing)
56+
)

tox.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tox]
2+
requires = tox>=4.21
3+
env_list = pre-commit,py3{10,11,12,13}
4+
skip_missing_interpreters = True
5+
6+
[testenv:pre-commit]
7+
skip_install = True
8+
dependency_groups = pre-commit
9+
commands = pre-commit run --all-files --show-diff-on-failure --color=always
10+
11+
[testenv:py{,310,311,312,313}]
12+
skip_install = True
13+
dependency_groups = test
14+
commands =
15+
uv pip install --find-links=wheelhouse --no-index --only-binary :all: --pre std-nslog
16+
pytest {posargs:-v tests}

0 commit comments

Comments
 (0)