Skip to content

Commit d316b24

Browse files
authored
Merge branch '5.0' into bolt-handshake-v2
2 parents 98a3c2e + 0b1e872 commit d316b24

File tree

8 files changed

+246
-20
lines changed

8 files changed

+246
-20
lines changed

requirements-dev.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ pytest-asyncio>=0.16.0
2323
pytest-benchmark>=3.4.1
2424
pytest-cov>=3.0.0
2525
pytest-mock>=3.6.1
26-
teamcity-messages>=1.29
2726
tox>=4.0.0
2827
teamcity-messages>=1.32
2928

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def change_project_name(new_name):
6969
fd.seek(0)
7070
pyproject = tomlkit.parse(fd.read())
7171
old_name = pyproject["project"]["name"]
72+
if old_name == new_name:
73+
return None
7274
pyproject["project"]["name"] = new_name
7375
fd.seek(0)
7476
fd.truncate()
@@ -82,7 +84,8 @@ def changed_package_name(new_name):
8284
try:
8385
yield
8486
finally:
85-
change_project_name(old_name)
87+
if old_name is not None:
88+
change_project_name(old_name)
8689

8790

8891
with changed_package_name(package):

src/neo4j/_async/auth_management.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ async def auth_provider():
273273
# assume we know our tokens expire every 60 seconds
274274
expires_in = 60
275275
276-
return ExpiringAuth(
277-
auth=neo4j.bearer_auth(sso_token),
278-
# Include a little buffer so that we fetch a new token
279-
# *before* the old one expires
280-
expires_in=expires_in - 10
281-
)
276+
# Include a little buffer so that we fetch a new token
277+
# *before* the old one expires
278+
expires_in -= 10
279+
280+
auth = neo4j.bearer_auth(sso_token)
281+
return ExpiringAuth(auth=auth).expires_in(expires_in)
282282
283283
284284
with neo4j.GraphDatabase.driver(

src/neo4j/_sync/auth_management.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ def auth_provider():
273273
# assume we know our tokens expire every 60 seconds
274274
expires_in = 60
275275
276-
return ExpiringAuth(
277-
auth=neo4j.bearer_auth(sso_token),
278-
# Include a little buffer so that we fetch a new token
279-
# *before* the old one expires
280-
expires_in=expires_in - 10
281-
)
276+
# Include a little buffer so that we fetch a new token
277+
# *before* the old one expires
278+
expires_in -= 10
279+
280+
auth = neo4j.bearer_auth(sso_token)
281+
return ExpiringAuth(auth=auth).expires_in(expires_in)
282282
283283
284284
with neo4j.GraphDatabase.driver(

testkit/unittests.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,14 @@
1818

1919

2020
if __name__ == "__main__":
21-
run_python(["-m", "tox", "-vv", "-p", "-f", "unit"])
21+
run_python(
22+
[
23+
"-m",
24+
"tox",
25+
"-vv",
26+
"--parallel",
27+
"--parallel-no-spinner",
28+
"-f",
29+
"unit",
30+
]
31+
)

tests/_teamcity.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from __future__ import annotations
18+
19+
import os
20+
import re
21+
import time
22+
import typing as t
23+
24+
25+
if t.TYPE_CHECKING:
26+
import pytest
27+
28+
29+
__all__ = [
30+
"pytest_collection_finish",
31+
"pytest_runtest_logreport",
32+
"pytest_sessionstart",
33+
"pytest_unconfigure",
34+
]
35+
36+
37+
_ENABLED = os.environ.get("TEST_IN_TEAMCITY", "").upper() in {
38+
"TRUE",
39+
"1",
40+
"Y",
41+
"YES",
42+
"ON",
43+
}
44+
45+
46+
_SUITE_NAME = os.environ.get("TEST_SUITE_NAME")
47+
48+
49+
def _escape(s: object) -> str:
50+
s = str(s)
51+
s = s.replace("|", "||")
52+
s = s.replace("\n", "|n")
53+
s = s.replace("\r", "|r")
54+
s = s.replace("'", "|'")
55+
s = s.replace("[", "|[")
56+
s = s.replace("]", "|]")
57+
return s # noqa: RET504 - subjectively easier to read this way
58+
59+
60+
def _message(title: str, **entries: object) -> None:
61+
if "timestamp" not in entries:
62+
now = time.time()
63+
now_s, now_sub_s = divmod(now, 1)
64+
now_tuple = time.localtime(now_s)
65+
entries["timestamp"] = (
66+
time.strftime("%Y-%m-%dT%H:%M:%S", now_tuple)
67+
+ f".{int(now_sub_s * 1000):03}"
68+
)
69+
str_entries = " ".join(f"{k}='{_escape(v)}'" for k, v in entries.items())
70+
if str_entries:
71+
str_entries = " " + str_entries
72+
73+
print(f"\n##teamcity[{title}{str_entries}]", flush=True) # noqa: T201
74+
# [noqa] to allow print as that's the whole purpose of this
75+
# make-shift pytest plugin
76+
77+
78+
def pytest_sessionstart(session: pytest.Session) -> None:
79+
if not (_ENABLED and _SUITE_NAME):
80+
return
81+
_message("testSuiteStarted", name=_SUITE_NAME)
82+
83+
84+
def pytest_unconfigure(config: pytest.Config) -> None:
85+
if not (_ENABLED and _SUITE_NAME):
86+
return
87+
_message("testSuiteFinished", name=_SUITE_NAME)
88+
89+
90+
def pytest_collection_finish(session: pytest.Session) -> None:
91+
if not _ENABLED:
92+
return
93+
_message("testCount", count=len(session.items))
94+
95+
96+
# function taken from teamcity-messages package
97+
# Copyright JetBrains, licensed under Apache 2.0
98+
# changes applied:
99+
# - non-functional changes (e.g., formatting, removed dead code)
100+
# - removed support for pep8-check and pylint
101+
def format_test_id(nodeid: str) -> str:
102+
test_id = nodeid
103+
104+
if test_id:
105+
if test_id.find("::") < 0:
106+
test_id += "::top_level"
107+
else:
108+
test_id = "top_level"
109+
110+
first_bracket = test_id.find("[")
111+
if first_bracket > 0:
112+
# [] -> (), make it look like nose parameterized tests
113+
params = "(" + test_id[first_bracket + 1 :]
114+
if params.endswith("]"):
115+
params = params[:-1] + ")"
116+
test_id = test_id[:first_bracket]
117+
if test_id.endswith("::"):
118+
test_id = test_id[:-2]
119+
else:
120+
params = ""
121+
122+
test_id = test_id.replace("::()::", "::")
123+
test_id = re.sub(r"\.pyc?::", r"::", test_id)
124+
test_id = test_id.replace(".", "_")
125+
test_id = test_id.replace(os.sep, ".")
126+
test_id = test_id.replace("/", ".")
127+
test_id = test_id.replace("::", ".")
128+
129+
if params:
130+
params = params.replace(".", "_")
131+
test_id += params
132+
133+
return test_id
134+
135+
136+
def _report_output(test_id: str, stdout: str, stderr: str) -> None:
137+
block_name = None
138+
if stdout or stderr:
139+
block_name = f"{test_id} output"
140+
_message("blockOpened", name=block_name)
141+
if stdout:
142+
_message("testStdOut", name=test_id, out=stdout)
143+
if stderr:
144+
_message("testStdErr", name=test_id, out=stderr)
145+
if block_name:
146+
_message("blockClosed", name=block_name)
147+
148+
149+
def _skip_reason(report: pytest.TestReport) -> str | None:
150+
if isinstance(report.longrepr, tuple):
151+
return report.longrepr[2]
152+
if isinstance(report.longrepr, str):
153+
return report.longrepr
154+
return None
155+
156+
157+
def _report_skip(test_id: str, reason: str | None) -> None:
158+
if reason is None:
159+
_message("testIgnored", name=test_id)
160+
else:
161+
_message("testIgnored", name=test_id, message=reason)
162+
163+
164+
def pytest_runtest_logreport(report: pytest.TestReport) -> None:
165+
if not _ENABLED:
166+
return
167+
168+
test_id = format_test_id(report.nodeid)
169+
170+
test_stdouts = []
171+
test_stderrs = []
172+
for section_name, section_data in report.sections:
173+
if not section_data:
174+
continue
175+
if "stdout" in section_name:
176+
test_stdouts.append(
177+
f"===== [{section_name}] =====\n{section_data}"
178+
)
179+
if "stderr" in section_name:
180+
test_stderrs.append(
181+
f"===== [{section_name}] =====\n{section_data}"
182+
)
183+
test_stdout = "\n".join(test_stdouts)
184+
test_stderr = "\n".join(test_stderrs)
185+
186+
if report.when == "teardown":
187+
_report_output(test_id, test_stdout, test_stderr)
188+
test_duration_ms = int(report.duration * 1000)
189+
_message("testFinished", name=test_id, duration=test_duration_ms)
190+
if report.outcome == "skipped":
191+
# a little late to skip the test, eh?
192+
test_stage_id = f"{test_id}___teardown"
193+
_report_skip(test_stage_id, _skip_reason(report))
194+
195+
if report.when in {"setup", "teardown"} and report.outcome == "failed":
196+
test_stage_id = f"{test_id}__{report.when}"
197+
_message("testStarted", name=test_stage_id)
198+
_report_output(test_stage_id, test_stdout, test_stderr)
199+
_message(
200+
"testFailed",
201+
name=test_stage_id,
202+
message=f"{report.when.capitalize()} failed",
203+
details=report.longreprtext,
204+
)
205+
_message("testFinished", name=test_stage_id)
206+
207+
if report.when == "setup":
208+
_message("testStarted", name=test_id)
209+
if report.outcome == "skipped":
210+
_report_skip(test_id, _skip_reason(report))
211+
212+
if report.when == "call":
213+
if report.outcome == "failed":
214+
_message("testFailed", name=test_id, message=report.longreprtext)
215+
elif report.outcome == "skipped":
216+
_report_skip(test_id, _skip_reason(report))

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from neo4j.debug import watch
3131

3232
from . import env
33+
from ._teamcity import * # noqa - needed for pytest to pick up the hooks
3334

3435

3536
# from neo4j.debug import watch

tox.ini

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ envlist = py{37,38,39,310,311,312,313}-{unit,integration,performance}
44
requires = virtualenv<20.22.0
55

66
[testenv]
7-
passenv =
8-
TEST_NEO4J_*
9-
TEAMCITY_VERSION
10-
deps = -r requirements-dev.txt
7+
passenv = TEST_*
118
setenv =
129
COVERAGE_FILE={envdir}/.coverage
13-
; unit,performance,integration: PYTHONTRACEMALLOC = 5
10+
TEST_SUITE_NAME={envname}
1411
usedevelop = true
1512
warnargs =
1613
py{37,38,39,310,311,312}: -W error -W ignore::pytest.PytestUnraisableExceptionWarning

0 commit comments

Comments
 (0)