Skip to content

Commit d0ece3d

Browse files
committed
Add a testbed app to verify the support library is complete and correct.
1 parent a135313 commit d0ece3d

30 files changed

+369
-1
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ diff/*
88
dist/*
99
.envrc
1010
.vscode/
11-
local/*
11+
local/*
12+
*.dist-info
13+
__pycache__
14+
tests/testbed/macOS
15+
tests/testbed/iOS

tests/testbed/README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Python Apple Support Testbed
2+
============================
3+
4+
This is a testbed application that can be used to do basic verification checks
5+
of the Python Apple Support builds.
6+
7+
The app can be deployed with Briefcase. When executed, (using `briefcase run
8+
macOS Xcode` or `briefcase run iOS`) the app will generate output on the console
9+
log that is similar to a unit test suite. If it returns 0 test failures, you can
10+
have some confidence that the support build is functioning as expected.
11+
12+
The default configuration assumes that you have already run `make` in the root
13+
directory of this repository.

tests/testbed/pyproject.toml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[tool.briefcase]
2+
project_name = "Testbed"
3+
bundle = "org.beeware"
4+
version = "0.0.1"
5+
url = "https://beeware.org"
6+
license = "BSD license"
7+
author = 'Russell Keith-Magee'
8+
author_email = "[email protected]"
9+
10+
[tool.briefcase.app.testbed]
11+
formal_name = "Testbed"
12+
description = "A testbed for the Apple Support packages."
13+
icon = "src/testbed/resources/testbed"
14+
sources = ['src/testbed']
15+
requires = [
16+
]
17+
18+
19+
[tool.briefcase.app.testbed.macOS]
20+
requires = [
21+
"rubicon-objc",
22+
"std-nslog",
23+
]
24+
template_branch = "m1-compile"
25+
support_package = "../../dist/Python-3.10-macOS-support.custom.tar.gz"
26+
27+
[tool.briefcase.app.testbed.linux]
28+
requires = []
29+
system_requires = []
30+
31+
[tool.briefcase.app.testbed.windows]
32+
requires = []
33+
34+
# Mobile deployments
35+
[tool.briefcase.app.testbed.iOS]
36+
requires = [
37+
"rubicon-objc",
38+
]
39+
template = "../../../../templates/briefcase-iOS-Xcode-template"
40+
# template_branch = "m1-compile"
41+
support_package = "../../dist/Python-3.10-iOS-support.custom.tar.gz"
42+
43+
[tool.briefcase.app.testbed.android]
44+
requires = []

tests/testbed/src/testbed/__init__.py

Whitespace-only changes.

tests/testbed/src/testbed/__main__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from testbed.app import main
2+
3+
if __name__ == "__main__":
4+
main()

tests/testbed/src/testbed/app.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
A testbed for the Apple Support packages.
3+
"""
4+
import platform
5+
import sys
6+
import traceback
7+
8+
from . import common
9+
from . import macos
10+
11+
12+
def discover_tests(module):
13+
"Discover all the test methods in the given module"
14+
return [
15+
(getattr(module, "__name__").split(".")[-1], getattr(module, name))
16+
for name in dir(module)
17+
if name.startswith("test_")
18+
]
19+
20+
21+
def main():
22+
# This should start and launch your app!
23+
print("=" * 80)
24+
print(f"Python {platform.python_version()} Apple Support verification suite")
25+
print(f"Running on {platform.platform()}")
26+
print("=" * 80)
27+
# Discover the suite
28+
suite = discover_tests(common)
29+
if sys.platform == "darwin":
30+
suite.extend(discover_tests(macos))
31+
32+
# Run the suite
33+
failures = 0
34+
tests = 0
35+
for sys_platform, test in suite:
36+
try:
37+
tests += 1
38+
# If the test has a docstring, use that text;
39+
# otherwise, use the test name
40+
if test.__doc__:
41+
print(f"{sys_platform}: {test.__doc__}", end="...")
42+
else:
43+
print(f"{sys_platform}: {test.__name__}", end="...")
44+
test()
45+
print(" ok")
46+
except Exception as e:
47+
failures += 1
48+
print(" FAILED!")
49+
print("-" * 80)
50+
traceback.print_exception(e)
51+
print("-" * 80)
52+
53+
print("=" * 80)
54+
print(f"Tests complete; {tests} tests, {failures} failures.")
55+
sys.exit(int(failures != 0))

tests/testbed/src/testbed/common.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
###########################################################################
2+
# Common tests
3+
###########################################################################
4+
from .utils import assert_
5+
6+
7+
def test_bzip2():
8+
"BZip2 compression with the bz2 module works"
9+
import bz2
10+
11+
data = bz2.compress(b"Hello world")
12+
assert_(
13+
data == b"BZh91AY&SY\x059\x88u\x00\x00\x00\x95\x80@\x00\x00@\x06\x04"
14+
b'\x90\x80 \x00"\x06\x9bHC\x02\x1a|\n\xa1<]\xc9\x14\xe1B@\x14\xe6!\xd4'
15+
)
16+
17+
18+
def test_ctypes():
19+
"The FFI module has been compiled, and ctypes works on ObjC objects"
20+
from rubicon.objc import ObjCClass
21+
22+
NSURL = ObjCClass("NSURL")
23+
24+
base = NSURL.URLWithString("https://beeware.org/")
25+
full = NSURL.URLWithString("contributing", relativeToURL=base)
26+
absolute = full.absoluteURL
27+
assert_(absolute.description == "https://beeware.org/contributing")
28+
29+
30+
def test_decimal():
31+
"The decimal module works"
32+
from decimal import Decimal, getcontext
33+
34+
getcontext().prec = 28
35+
assert str(Decimal(1) / Decimal(7)) == "0.1428571428571428571428571429"
36+
37+
38+
def test_hashlib():
39+
"Hashlib can compute hashes"
40+
import hashlib
41+
42+
algorithms = {
43+
"md5": "3e25960a79dbc69b674cd4ec67a72c62",
44+
"sha1": "7b502c3a1f48c8609ae212cdfb639dee39673f5e",
45+
"sha224": "ac230f15fcae7f77d8f76e99adf45864a1c6f800655da78dea956112",
46+
"sha256": ("64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c"),
47+
"sha384": (
48+
"9203b0c4439fd1e6ae5878866337b7c532acd6d9260150c80318e8ab8c27ce33"
49+
"0189f8df94fb890df1d298ff360627e1"
50+
),
51+
"sha512": (
52+
"b7f783baed8297f0db917462184ff4f08e69c2d5e5f79a942600f9725f58ce1f"
53+
"29c18139bf80b06c0fff2bdd34738452ecf40c488c22a7e3d80cdf6f9c1c0d47"
54+
),
55+
"blake2b": (
56+
"6ff843ba685842aa82031d3f53c48b66326df7639a63d128974c5c14f31a0f33"
57+
"343a8c65551134ed1ae0f2b0dd2bb495dc81039e3eeb0aa1bb0388bbeac29183"
58+
),
59+
"blake2s": "619a15b0f4dd21ef4bd626a9146af64561caf1325b21bccf755e4d7fbc31a65f",
60+
"sha3_224": "3b8570ec1335c461747d016460ff91cb41fad08051911c50dd8e1995",
61+
"sha3_256": (
62+
"369183d3786773cef4e56c7b849e7ef5f742867510b676d6b38f8e38a222d8a2"
63+
),
64+
"sha3_384": (
65+
"ff3917192427ea1aa7f3ad47ac10152d179af30126c52835ee8dc7e6ea12aed9"
66+
"1ad91b316e15c3b250469ef17a03e529"
67+
),
68+
"sha3_512": (
69+
"e2e1c9e522efb2495a178434c8bb8f11000ca23f1fd679058b7d7e141f0cf343"
70+
"3f94fc427ec0b9bebb12f327a3240021053db6091196576d5e6d9bd8fac71c0c"
71+
),
72+
"shake_128": (
73+
40,
74+
"c1301df86b1dc67ce3b5a067dc9b47affca8caa08f41d1efa614cea56f526897"
75+
"d61ded8ab01421f1",
76+
),
77+
"shake_256": (
78+
40,
79+
"20740b4c7a7997765e9cc254b44a1589e60849be0fe70b68a6fb732415edaa13"
80+
"3bb6eb7825ffa531",
81+
),
82+
}
83+
for algorithm, details in algorithms.items():
84+
try:
85+
length, expected = details
86+
digest_args = {"length": length}
87+
except ValueError:
88+
expected = details
89+
digest_args = {}
90+
msg = getattr(hashlib, algorithm)()
91+
msg.update(b"Hello world")
92+
assert_(
93+
msg.hexdigest(**digest_args) == expected,
94+
msg=f"{algorithm} digest was {msg.hexdigest(**digest_args)}",
95+
)
96+
97+
98+
def test_sqlite3():
99+
"The sqlite3 module works"
100+
import sqlite3
101+
102+
conn = sqlite3.connect(":memory:")
103+
try:
104+
cursor = conn.cursor()
105+
106+
cursor.execute(
107+
"CREATE TABLE stonks (date text, symbol text, qty real, price real)"
108+
)
109+
cursor.execute("INSERT INTO stonks VALUES ('2022-05-04', 'JEDI', 10, 2.50)")
110+
cursor.execute("INSERT INTO stonks VALUES ('2022-05-04', 'SITH', 2, 6.66)")
111+
conn.commit()
112+
113+
assert_(
114+
list(cursor.execute("SELECT * FROM stonks ORDER BY symbol DESC"))
115+
== [("2022-05-04", "SITH", 2.0, 6.66), ("2022-05-04", "JEDI", 10.0, 2.5)]
116+
)
117+
finally:
118+
conn.close()
119+
120+
121+
def test_ssl():
122+
"The SSL modules has been compiled"
123+
import ssl
124+
125+
# Result doesn't really matter; we just need to be able to invoke
126+
# a method whose implementation is in the C module
127+
ssl.get_default_verify_paths()
128+
129+
130+
XML_DOCUMENT = """<?xml version="1.0"?>
131+
<data>
132+
<device name="iPhone">
133+
<os>iOS</os>
134+
<type>phone</type>
135+
</device>
136+
<device name="macBook">
137+
<os>macOS</os>
138+
<type>laptop</type>
139+
</device>
140+
</data>
141+
"""
142+
143+
144+
def test_xml_elementtree():
145+
"The elementtree XML parser works"
146+
import xml.etree.ElementTree as ET
147+
148+
root = ET.fromstring(XML_DOCUMENT)
149+
assert_(
150+
[(child.tag, child.attrib["name"]) for child in root]
151+
== [("device", "iPhone"), ("device", "macBook")]
152+
)
153+
154+
155+
def test_xml_expat():
156+
"The expat XML parser works"
157+
from xml.parsers.expat import ParserCreate
158+
159+
starts = []
160+
161+
def start_element(name, attrs):
162+
starts.append(name)
163+
164+
parser = ParserCreate()
165+
parser.StartElementHandler = start_element
166+
parser.Parse(XML_DOCUMENT)
167+
168+
assert_(starts == ["data", "device", "os", "type", "device", "os", "type"])
169+
170+
171+
def test_xz():
172+
"XZ compression with the lzma module works"
173+
import lzma
174+
175+
data = lzma.compress(b"Hello world")
176+
assert_(
177+
data == b"\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/"
178+
b"\xe5\xa3\x01\x00\nHello world\x00\x00\xbfVw\xd4\xb9\xf2\xa5\xf4\x00"
179+
b"\x01#\x0b\xc2\x1b\xfd\t\x1f\xb6\xf3}\x01\x00\x00\x00\x00\x04YZ"
180+
)
181+
182+
183+
def test_zoneinfo():
184+
"Zoneinfo database is available"
185+
from zoneinfo import ZoneInfo
186+
from datetime import datetime
187+
188+
dt = datetime(2022, 5, 4, 13, 40, 42, tzinfo=ZoneInfo("Australia/Perth"))
189+
assert_(str(dt) == "2022-05-04 13:40:42+08:00")

tests/testbed/src/testbed/macos.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
###########################################################################
2+
# macOS specific tests
3+
###########################################################################
4+
from .utils import assert_
5+
6+
7+
def test_scproxy():
8+
"The _scproxy module has been compiled"
9+
import _scproxy
10+
11+
_scproxy._get_proxy_settings()
12+
13+
14+
def test_curses():
15+
"The curses module has been compiled"
16+
import curses
17+
18+
try:
19+
curses.can_change_color()
20+
except curses.error:
21+
# We can't invoke curses methods without raising a curses error;
22+
# but if we get the error, the module works.
23+
pass
24+
25+
26+
def test_posix_shmem():
27+
"POSIX shared memory works"
28+
from multiprocessing import shared_memory
29+
30+
# FIXME: For now, we can't actually test multiprocessing
31+
# because it involves invoking a subprocess, and the macOS app
32+
# shim doesn't support process duplication. The import is
33+
# enough to test that the _posixshmem C module exists.
34+
# try:
35+
# obj = shared_memory.ShareableList(
36+
# ["howdy", b"HoWdY", -273.154, 100, None, True, 42]
37+
# )
38+
#
39+
# assert obj[3] == 100
40+
# finally:
41+
# obj.shm.close()
42+
# obj.shm.unlink()
43+
# del obj
44+
45+
46+
def test_posix_subprocess():
47+
"Subprocesses can be invoked"
48+
import subprocess
49+
50+
result = subprocess.run(["uname", "-s"], capture_output=True)
51+
assert_(result.stdout == b"Darwin\n")

tests/testbed/src/testbed/resources/__init__.py

Whitespace-only changes.
194 KB
Loading

0 commit comments

Comments
 (0)