Merge branch 'master' of github.com:mongodb/mongo-python-driver
This commit is contained in:
commit
b830ef02d3
@ -151,6 +151,7 @@ functions:
|
||||
- VERSION
|
||||
- IS_WIN32
|
||||
- REQUIRE_FIPS
|
||||
- TEST_MIN_DEPS
|
||||
type: test
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,5 @@
|
||||
buildvariants:
|
||||
# Alternative hosts tests
|
||||
- name: openssl-1.0.2-rhel7-v5.0-python3.9
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
display_name: OpenSSL 1.0.2 RHEL7 v5.0 Python3.9
|
||||
run_on:
|
||||
- rhel79-small
|
||||
batchtime: 1440
|
||||
expansions:
|
||||
VERSION: "5.0"
|
||||
PYTHON_VERSION: "3.9"
|
||||
PYTHON_BINARY: /opt/python/3.9/bin/python3
|
||||
- name: other-hosts-rhel9-fips-latest
|
||||
tasks:
|
||||
- name: .test-no-toolchain
|
||||
@ -153,17 +142,16 @@ buildvariants:
|
||||
- rhel87-small
|
||||
|
||||
# Disable test commands tests
|
||||
- name: disable-test-commands-rhel8-python3.9
|
||||
- name: disable-test-commands-rhel8
|
||||
tasks:
|
||||
- name: .test-standard .server-latest
|
||||
display_name: Disable test commands RHEL8 Python3.9
|
||||
display_name: Disable test commands RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
expansions:
|
||||
AUTH: auth
|
||||
SSL: ssl
|
||||
DISABLE_TEST_COMMANDS: "1"
|
||||
PYTHON_BINARY: /opt/python/3.9/bin/python3
|
||||
|
||||
# Doctests tests
|
||||
- name: doctests-rhel8
|
||||
@ -179,6 +167,7 @@ buildvariants:
|
||||
- name: encryption-rhel8
|
||||
tasks:
|
||||
- name: .test-non-standard
|
||||
- name: .test-min-deps
|
||||
display_name: Encryption RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
@ -209,6 +198,7 @@ buildvariants:
|
||||
- name: encryption-crypt_shared-rhel8
|
||||
tasks:
|
||||
- name: .test-non-standard
|
||||
- name: .test-min-deps
|
||||
display_name: Encryption crypt_shared RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
@ -500,14 +490,14 @@ buildvariants:
|
||||
SUB_TEST_NAME: pyopenssl
|
||||
|
||||
# Search index tests
|
||||
- name: search-index-helpers-rhel8-python3.9
|
||||
- name: search-index-helpers-rhel8-python3.10
|
||||
tasks:
|
||||
- name: .search_index
|
||||
display_name: Search Index Helpers RHEL8 Python3.9
|
||||
display_name: Search Index Helpers RHEL8 Python3.10
|
||||
run_on:
|
||||
- rhel87-small
|
||||
expansions:
|
||||
PYTHON_BINARY: /opt/python/3.9/bin/python3
|
||||
PYTHON_BINARY: /opt/python/3.10/bin/python3
|
||||
|
||||
# Server version tests
|
||||
- name: mongodb-v4.2
|
||||
|
||||
@ -20,8 +20,13 @@ fi
|
||||
set -o xtrace
|
||||
|
||||
# Install python with pip.
|
||||
PYTHON_VER="python3.9"
|
||||
PYTHON_VER="python3.10"
|
||||
apt-get -qq update < /dev/null > /dev/null
|
||||
apt-get -q install -y software-properties-common
|
||||
# Use openpgp to avoid gpg key timeout.
|
||||
mkdir -p $HOME/.gnupg
|
||||
echo "keyserver keys.openpgp.org" >> $HOME/.gnupg/gpg.conf
|
||||
add-apt-repository -y 'ppa:deadsnakes/ppa'
|
||||
apt-get -qq install $PYTHON_VER $PYTHON_VER-venv build-essential $PYTHON_VER-dev -y < /dev/null > /dev/null
|
||||
|
||||
export PYTHON_BINARY=$PYTHON_VER
|
||||
|
||||
@ -6,7 +6,8 @@ SCRIPT_DIR=$(dirname ${BASH_SOURCE:-$0})
|
||||
SCRIPT_DIR="$( cd -- "$SCRIPT_DIR" > /dev/null 2>&1 && pwd )"
|
||||
ROOT_DIR="$(dirname $SCRIPT_DIR)"
|
||||
|
||||
pushd $ROOT_DIR
|
||||
PREV_DIR=$(pwd)
|
||||
cd $ROOT_DIR
|
||||
|
||||
# Try to source the env file.
|
||||
if [ -f $SCRIPT_DIR/scripts/env.sh ]; then
|
||||
@ -25,7 +26,17 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start the test runner.
|
||||
uv run ${UV_ARGS} --reinstall .evergreen/scripts/run_tests.py "$@"
|
||||
cleanup_tests() {
|
||||
# Avoid leaving the lock file in a changed state when we change the resolution type.
|
||||
if [ -n "${TEST_MIN_DEPS:-}" ]; then
|
||||
git checkout uv.lock || true
|
||||
fi
|
||||
cd $PREV_DIR
|
||||
}
|
||||
|
||||
popd
|
||||
trap "cleanup_tests" SIGINT ERR
|
||||
|
||||
# Start the test runner.
|
||||
uv run ${UV_ARGS} --reinstall-package pymongo .evergreen/scripts/run_tests.py "$@"
|
||||
|
||||
cleanup_tests
|
||||
|
||||
@ -143,7 +143,7 @@ def create_encryption_variants() -> list[BuildVariant]:
|
||||
):
|
||||
expansions = get_encryption_expansions(encryption)
|
||||
display_name = get_variant_name(encryption, host, **expansions)
|
||||
tasks = [".test-non-standard"]
|
||||
tasks = [".test-non-standard", ".test-min-deps"]
|
||||
if host != "rhel8":
|
||||
tasks = [".test-non-standard !.pypy"]
|
||||
variant = create_variant(
|
||||
@ -330,10 +330,9 @@ def create_mod_wsgi_variants():
|
||||
def create_disable_test_commands_variants():
|
||||
host = DEFAULT_HOST
|
||||
expansions = dict(AUTH="auth", SSL="ssl", DISABLE_TEST_COMMANDS="1")
|
||||
python = CPYTHONS[0]
|
||||
display_name = get_variant_name("Disable test commands", host, python=python)
|
||||
display_name = get_variant_name("Disable test commands", host)
|
||||
tasks = [".test-standard .server-latest"]
|
||||
return [create_variant(tasks, display_name, host=host, python=python, expansions=expansions)]
|
||||
return [create_variant(tasks, display_name, host=host, expansions=expansions)]
|
||||
|
||||
|
||||
def create_oidc_auth_variants():
|
||||
@ -480,19 +479,6 @@ def create_alternative_hosts_variants():
|
||||
batchtime = BATCHTIME_DAY
|
||||
variants = []
|
||||
|
||||
host = HOSTS["rhel7"]
|
||||
version = "5.0"
|
||||
variants.append(
|
||||
create_variant(
|
||||
[".test-no-toolchain"],
|
||||
get_variant_name("OpenSSL 1.0.2", host, python=CPYTHONS[0], version=version),
|
||||
host=host,
|
||||
python=CPYTHONS[0],
|
||||
batchtime=batchtime,
|
||||
expansions=dict(VERSION=version, PYTHON_VERSION=CPYTHONS[0]),
|
||||
)
|
||||
)
|
||||
|
||||
version = "latest"
|
||||
for host_name in OTHER_HOSTS:
|
||||
expansions = dict(VERSION="latest")
|
||||
@ -528,22 +514,20 @@ def create_aws_lambda_variants():
|
||||
|
||||
def create_server_version_tasks():
|
||||
tasks = []
|
||||
task_inputs = []
|
||||
task_combos = set()
|
||||
# All combinations of topology, auth, ssl, and sync should be tested.
|
||||
for (topology, auth, ssl, sync), python in zip_cycle(
|
||||
list(product(TOPOLOGIES, ["auth", "noauth"], ["ssl", "nossl"], SYNCS)), ALL_PYTHONS
|
||||
):
|
||||
task_inputs.append((topology, auth, ssl, sync, python))
|
||||
task_combos.add((topology, auth, ssl, sync, python))
|
||||
|
||||
# Every python should be tested with sharded cluster, auth, ssl, with sync and async.
|
||||
for python, sync in product(ALL_PYTHONS, SYNCS):
|
||||
task_input = ("sharded_cluster", "auth", "ssl", sync, python)
|
||||
if task_input not in task_inputs:
|
||||
task_inputs.append(task_input)
|
||||
task_combos.add(("sharded_cluster", "auth", "ssl", sync, python))
|
||||
|
||||
# Assemble the tasks.
|
||||
seen = set()
|
||||
for topology, auth, ssl, sync, python in task_inputs:
|
||||
for topology, auth, ssl, sync, python in sorted(task_combos):
|
||||
combo = f"{topology}-{auth}-{ssl}"
|
||||
tags = ["server-version", f"python-{python}", combo, sync]
|
||||
if combo in [
|
||||
@ -558,7 +542,12 @@ def create_server_version_tasks():
|
||||
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology)
|
||||
if python not in PYPYS:
|
||||
expansions["COVERAGE"] = "1"
|
||||
name = get_task_name("test-server-version", python=python, sync=sync, **expansions)
|
||||
name = get_task_name(
|
||||
"test-server-version",
|
||||
python=python,
|
||||
sync=sync,
|
||||
**expansions,
|
||||
)
|
||||
server_func = FunctionCall(func="run server", vars=expansions)
|
||||
test_vars = expansions.copy()
|
||||
test_vars["PYTHON_VERSION"] = python
|
||||
@ -590,15 +579,15 @@ def create_no_toolchain_tasks():
|
||||
def create_test_non_standard_tasks():
|
||||
"""For variants that set a TEST_NAME."""
|
||||
tasks = []
|
||||
task_combos = []
|
||||
task_combos = set()
|
||||
# For each version and topology, rotate through the CPythons.
|
||||
for (version, topology), python in zip_cycle(list(product(ALL_VERSIONS, TOPOLOGIES)), CPYTHONS):
|
||||
pr = version == "latest"
|
||||
task_combos.append((version, topology, python, pr))
|
||||
task_combos.add((version, topology, python, pr))
|
||||
# For each PyPy and topology, rotate through the the versions.
|
||||
for (python, topology), version in zip_cycle(list(product(PYPYS, TOPOLOGIES)), ALL_VERSIONS):
|
||||
task_combos.append((version, topology, python, False))
|
||||
for version, topology, python, pr in task_combos:
|
||||
task_combos.add((version, topology, python, False))
|
||||
for version, topology, python, pr in sorted(task_combos):
|
||||
auth, ssl = get_standard_auth_ssl(topology)
|
||||
tags = [
|
||||
"test-non-standard",
|
||||
@ -621,6 +610,22 @@ def create_test_non_standard_tasks():
|
||||
return tasks
|
||||
|
||||
|
||||
def create_min_deps_tasks():
|
||||
"""For variants that support testing with minimum dependencies."""
|
||||
tasks = []
|
||||
for topology in TOPOLOGIES:
|
||||
auth, ssl = get_standard_auth_ssl(topology)
|
||||
tags = ["test-min-deps", f"{topology}-{auth}-{ssl}"]
|
||||
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology)
|
||||
server_func = FunctionCall(func="run server", vars=expansions)
|
||||
test_vars = expansions.copy()
|
||||
test_vars["TEST_MIN_DEPS"] = "1"
|
||||
name = get_task_name("test-min-deps", python=CPYTHONS[0], sync="sync", **test_vars)
|
||||
test_func = FunctionCall(func="run tests", vars=test_vars)
|
||||
tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func]))
|
||||
return tasks
|
||||
|
||||
|
||||
def create_standard_tasks():
|
||||
"""For variants that do not set a TEST_NAME."""
|
||||
tasks = []
|
||||
@ -794,9 +799,12 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name):
|
||||
tags.append("pr")
|
||||
|
||||
task_name = get_task_name(
|
||||
f"test-ocsp-{algo}-{base_task_name}", python=python, version=version
|
||||
f"test-ocsp-{algo}-{base_task_name}",
|
||||
python=python,
|
||||
version=version,
|
||||
)
|
||||
tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func]))
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
@ -1075,6 +1083,7 @@ def create_run_tests_func():
|
||||
"VERSION",
|
||||
"IS_WIN32",
|
||||
"REQUIRE_FIPS",
|
||||
"TEST_MIN_DEPS",
|
||||
]
|
||||
args = [".evergreen/just.sh", "setup-tests", "${TEST_NAME}", "${SUB_TEST_NAME}"]
|
||||
setup_cmd = get_subprocess_exec(include_expansions_in_env=includes, args=args)
|
||||
|
||||
@ -22,7 +22,7 @@ from shrub.v3.shrub_service import ShrubService
|
||||
##############
|
||||
|
||||
ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"]
|
||||
CPYTHONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
CPYTHONS = ["3.10", "3.9", "3.11", "3.12", "3.13", "3.14"]
|
||||
PYPYS = ["pypy3.10"]
|
||||
ALL_PYTHONS = CPYTHONS + PYPYS
|
||||
MIN_MAX_PYTHON = [CPYTHONS[0], CPYTHONS[-1]]
|
||||
@ -42,6 +42,7 @@ DISPLAY_LOOKUP = dict(
|
||||
sync={"sync": "Sync", "async": "Async"},
|
||||
coverage={"1": "cov"},
|
||||
no_ext={"1": "No C"},
|
||||
test_min_deps={True: "Min Deps"},
|
||||
)
|
||||
HOSTS = dict()
|
||||
|
||||
@ -202,7 +203,7 @@ def get_common_name(base: str, sep: str, **kwargs) -> str:
|
||||
name = f"Python{value}"
|
||||
else:
|
||||
name = f"PyPy{value.replace('pypy', '')}"
|
||||
elif key.lower() in DISPLAY_LOOKUP:
|
||||
elif key.lower() in DISPLAY_LOOKUP and value in DISPLAY_LOOKUP[key.lower()]:
|
||||
name = DISPLAY_LOOKUP[key.lower()][value]
|
||||
else:
|
||||
continue
|
||||
|
||||
@ -33,7 +33,7 @@ def _setup_azure_vm(base_env: dict[str, str]) -> None:
|
||||
env["AZUREKMS_CMD"] = "sudo apt-get install -y python3-dev build-essential"
|
||||
run_command(f"{azure_dir}/run-command.sh", env=env)
|
||||
|
||||
env["AZUREKMS_CMD"] = "bash .evergreen/just.sh setup-tests kms azure-remote"
|
||||
env["AZUREKMS_CMD"] = "NO_EXT=1 bash .evergreen/just.sh setup-tests kms azure-remote"
|
||||
run_command(f"{azure_dir}/run-command.sh", env=env)
|
||||
LOGGER.info("Setting up Azure VM... done.")
|
||||
|
||||
@ -53,7 +53,7 @@ def _setup_gcp_vm(base_env: dict[str, str]) -> None:
|
||||
env["GCPKMS_CMD"] = "sudo apt-get install -y python3-dev build-essential"
|
||||
run_command(f"{gcp_dir}/run-command.sh", env=env)
|
||||
|
||||
env["GCPKMS_CMD"] = "bash ./.evergreen/just.sh setup-tests kms gcp-remote"
|
||||
env["GCPKMS_CMD"] = "NO_EXT=1 bash ./.evergreen/just.sh setup-tests kms gcp-remote"
|
||||
run_command(f"{gcp_dir}/run-command.sh", env=env)
|
||||
LOGGER.info("Setting up GCP VM...")
|
||||
|
||||
@ -98,6 +98,13 @@ def setup_kms(sub_test_name: str) -> None:
|
||||
if sub_test_target == "azure":
|
||||
os.environ["AZUREKMS_VMNAME_PREFIX"] = "PYTHON_DRIVER"
|
||||
|
||||
# Found using "az vm image list --output table"
|
||||
os.environ[
|
||||
"AZUREKMS_IMAGE"
|
||||
] = "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest"
|
||||
else:
|
||||
os.environ["GCPKMS_IMAGEFAMILY"] = "debian-12"
|
||||
|
||||
run_command("./setup.sh", cwd=kms_dir)
|
||||
base_env = _load_kms_config(sub_test_target)
|
||||
|
||||
|
||||
@ -42,6 +42,11 @@ def setup_oidc(sub_test_name: str) -> dict[str, str] | None:
|
||||
if sub_test_name == "azure":
|
||||
env["AZUREOIDC_VMNAME_PREFIX"] = "PYTHON_DRIVER"
|
||||
if "-remote" not in sub_test_name:
|
||||
if sub_test_name == "azure":
|
||||
# Found using "az vm image list --output table"
|
||||
env["AZUREOIDC_IMAGE"] = "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest"
|
||||
else:
|
||||
env["GCPKMS_IMAGEFAMILY"] = "debian-12"
|
||||
run_command(f"bash {target_dir}/setup.sh", env=env)
|
||||
if sub_test_name in K8S_NAMES:
|
||||
run_command(f"bash {target_dir}/setup-pod.sh {sub_test_name}")
|
||||
@ -84,7 +89,7 @@ def test_oidc_send_to_remote(sub_test_name: str) -> None:
|
||||
env[f"{upper_name}OIDC_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE
|
||||
env[
|
||||
f"{upper_name}OIDC_TEST_CMD"
|
||||
] = f"OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
] = f"NO_EXT=1 OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
elif sub_test_name in K8S_NAMES:
|
||||
env["K8S_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE
|
||||
env["K8S_TEST_CMD"] = "OIDC_ENV=k8s ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
|
||||
@ -30,13 +30,14 @@ SUB_TEST_NAME = os.environ.get("SUB_TEST_NAME")
|
||||
|
||||
|
||||
def list_packages():
|
||||
packages = dict()
|
||||
packages = set()
|
||||
for distribution in importlib_metadata.distributions():
|
||||
packages[distribution.name] = distribution
|
||||
if distribution.name:
|
||||
packages.add(distribution.name)
|
||||
print("Package Version URL")
|
||||
print("------------------- ----------- ----------------------------------------------------")
|
||||
for name in sorted(packages):
|
||||
distribution = packages[name]
|
||||
distribution = importlib_metadata.distribution(name)
|
||||
url = ""
|
||||
if distribution.origin is not None:
|
||||
url = distribution.origin.url
|
||||
@ -136,6 +137,9 @@ def handle_aws_lambda() -> None:
|
||||
|
||||
|
||||
def run() -> None:
|
||||
# Add diagnostic for python version.
|
||||
print("Running with python", sys.version)
|
||||
|
||||
# List the installed packages.
|
||||
list_packages()
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ EXTRAS_MAP = {
|
||||
GROUP_MAP = dict(mockupdb="mockupdb", perf="perf")
|
||||
|
||||
# The python version used for perf tests.
|
||||
PERF_PYTHON_VERSION = "3.9.13"
|
||||
PERF_PYTHON_VERSION = "3.10.11"
|
||||
|
||||
|
||||
def is_set(var: str) -> bool:
|
||||
@ -90,6 +90,13 @@ def setup_libmongocrypt():
|
||||
distro = get_distro()
|
||||
if distro.name.startswith("Debian"):
|
||||
target = f"debian{distro.version_id}"
|
||||
elif distro.name.startswith("Ubuntu"):
|
||||
if distro.version_id == "20.04":
|
||||
target = "debian11"
|
||||
elif distro.version_id == "22.04":
|
||||
target = "debian12"
|
||||
elif distro.version_id == "24.04":
|
||||
target = "debian13"
|
||||
elif distro.name.startswith("Red Hat"):
|
||||
if distro.version_id.startswith("7"):
|
||||
target = "rhel-70-64-bit"
|
||||
@ -160,7 +167,6 @@ def handle_test_env() -> None:
|
||||
|
||||
write_env("PIP_QUIET") # Quiet by default.
|
||||
write_env("PIP_PREFER_BINARY") # Prefer binary dists by default.
|
||||
write_env("UV_FROZEN") # Do not modify lock files.
|
||||
|
||||
# Set an environment variable for the test name and sub test name.
|
||||
write_env(f"TEST_{test_name.upper()}")
|
||||
@ -178,6 +184,9 @@ def handle_test_env() -> None:
|
||||
if group := GROUP_MAP.get(test_name, ""):
|
||||
UV_ARGS.append(f"--group {group}")
|
||||
|
||||
if opts.test_min_deps:
|
||||
UV_ARGS.append("--resolution=lowest-direct")
|
||||
|
||||
if test_name == "auth_oidc":
|
||||
from oidc_tester import setup_oidc
|
||||
|
||||
@ -233,7 +242,7 @@ def handle_test_env() -> None:
|
||||
if is_set("MONGODB_URI"):
|
||||
write_env("PYMONGO_MUST_CONNECT", "true")
|
||||
|
||||
if is_set("DISABLE_TEST_COMMANDS") or opts.disable_test_commands:
|
||||
if opts.disable_test_commands:
|
||||
write_env("PYMONGO_DISABLE_TEST_COMMANDS", "1")
|
||||
|
||||
if test_name == "enterprise_auth":
|
||||
@ -345,10 +354,10 @@ def handle_test_env() -> None:
|
||||
if not (ROOT / "libmongocrypt").exists():
|
||||
setup_libmongocrypt()
|
||||
|
||||
# TODO: Test with 'pip install pymongocrypt'
|
||||
UV_ARGS.append(
|
||||
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
|
||||
)
|
||||
if not opts.test_min_deps:
|
||||
UV_ARGS.append(
|
||||
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
|
||||
)
|
||||
|
||||
# Use the nocrypto build to avoid dependency issues with older windows/python versions.
|
||||
BASE = ROOT / "libmongocrypt/nocrypto"
|
||||
@ -377,7 +386,7 @@ def handle_test_env() -> None:
|
||||
if sub_test_name == "pyopenssl":
|
||||
UV_ARGS.append("--extra ocsp")
|
||||
|
||||
if is_set("TEST_CRYPT_SHARED") or opts.crypt_shared:
|
||||
if opts.crypt_shared:
|
||||
config = read_env(f"{DRIVERS_TOOLS}/mo-expansion.sh")
|
||||
CRYPT_SHARED_DIR = Path(config["CRYPT_SHARED_LIB_PATH"]).parent.as_posix()
|
||||
LOGGER.info("Using crypt_shared_dir %s", CRYPT_SHARED_DIR)
|
||||
@ -447,14 +456,14 @@ def handle_test_env() -> None:
|
||||
|
||||
# Add coverage if requested.
|
||||
# Only cover CPython. PyPy reports suspiciously low coverage.
|
||||
if (is_set("COVERAGE") or opts.cov) and platform.python_implementation() == "CPython":
|
||||
if opts.cov and platform.python_implementation() == "CPython":
|
||||
# Keep in sync with combine-coverage.sh.
|
||||
# coverage >=5 is needed for relative_files=true.
|
||||
UV_ARGS.append("--group coverage")
|
||||
TEST_ARGS = f"{TEST_ARGS} --cov"
|
||||
write_env("COVERAGE")
|
||||
|
||||
if is_set("GREEN_FRAMEWORK") or opts.green_framework:
|
||||
if opts.green_framework:
|
||||
framework = opts.green_framework or os.environ["GREEN_FRAMEWORK"]
|
||||
UV_ARGS.append(f"--group {framework}")
|
||||
|
||||
|
||||
@ -60,6 +60,9 @@ NO_RUN_ORCHESTRATION = [
|
||||
"ocsp",
|
||||
]
|
||||
|
||||
# Mapping of env variables to options
|
||||
OPTION_TO_ENV_VAR = {"cov": "COVERAGE", "crypt_shared": "TEST_CRYPT_SHARED"}
|
||||
|
||||
|
||||
def get_test_options(
|
||||
description, require_sub_test_name=True, allow_extra_opts=False
|
||||
@ -94,6 +97,9 @@ def get_test_options(
|
||||
)
|
||||
parser.add_argument("--auth", action="store_true", help="Whether to add authentication.")
|
||||
parser.add_argument("--ssl", action="store_true", help="Whether to add TLS configuration.")
|
||||
parser.add_argument(
|
||||
"--test-min-deps", action="store_true", help="Test against minimum dependency versions"
|
||||
)
|
||||
|
||||
# Add the test modifiers.
|
||||
if require_sub_test_name:
|
||||
@ -127,26 +133,53 @@ def get_test_options(
|
||||
opts, extra_opts = parser.parse_args(), []
|
||||
else:
|
||||
opts, extra_opts = parser.parse_known_args()
|
||||
if opts.verbose:
|
||||
LOGGER.setLevel(logging.DEBUG)
|
||||
elif opts.quiet:
|
||||
LOGGER.setLevel(logging.WARNING)
|
||||
|
||||
# Convert list inputs to strings.
|
||||
for name in vars(opts):
|
||||
value = getattr(opts, name)
|
||||
if isinstance(value, list):
|
||||
setattr(opts, name, value[0])
|
||||
|
||||
# Handle validation and environment variable overrides.
|
||||
test_name = opts.test_name
|
||||
sub_test_name = opts.sub_test_name if require_sub_test_name else ""
|
||||
if require_sub_test_name and test_name in SUB_TEST_REQUIRED and not sub_test_name:
|
||||
raise ValueError(f"Test '{test_name}' requires a sub_test_name")
|
||||
if "auth" in test_name or os.environ.get("AUTH") == "auth":
|
||||
handle_env_overrides(parser, opts)
|
||||
if "auth" in test_name:
|
||||
opts.auth = True
|
||||
# 'auth_aws ecs' shouldn't have extra auth set.
|
||||
if test_name == "auth_aws" and sub_test_name == "ecs":
|
||||
opts.auth = False
|
||||
if os.environ.get("SSL") == "ssl":
|
||||
opts.ssl = True
|
||||
if opts.verbose:
|
||||
LOGGER.setLevel(logging.DEBUG)
|
||||
elif opts.quiet:
|
||||
LOGGER.setLevel(logging.WARNING)
|
||||
return opts, extra_opts
|
||||
|
||||
|
||||
def handle_env_overrides(parser: argparse.ArgumentParser, opts: argparse.Namespace) -> None:
|
||||
# Get the options, and then allow environment variable overrides.
|
||||
for key in vars(opts):
|
||||
if key in OPTION_TO_ENV_VAR:
|
||||
env_var = OPTION_TO_ENV_VAR[key]
|
||||
else:
|
||||
env_var = key.upper()
|
||||
if env_var in os.environ:
|
||||
if parser.get_default(key) != getattr(opts, key):
|
||||
LOGGER.info("Overriding env var '%s' with cli option", env_var)
|
||||
elif env_var == "AUTH":
|
||||
opts.auth = os.environ.get("AUTH") == "auth"
|
||||
elif env_var == "SSL":
|
||||
ssl_opt = os.environ.get("SSL", "")
|
||||
opts.ssl = ssl_opt and ssl_opt.lower() != "nossl"
|
||||
elif isinstance(getattr(opts, key), bool):
|
||||
if os.environ[env_var]:
|
||||
setattr(opts, key, True)
|
||||
else:
|
||||
setattr(opts, key, os.environ[env_var])
|
||||
|
||||
|
||||
def read_env(path: Path | str) -> dict[str, str]:
|
||||
config = dict()
|
||||
with Path(path).open() as fid:
|
||||
|
||||
31
.github/workflows/test-python.yml
vendored
31
.github/workflows/test-python.yml
vendored
@ -225,7 +225,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
name: Test using minimum dependencies and supported Python
|
||||
name: Test minimum dependencies and Python
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
@ -238,37 +238,10 @@ jobs:
|
||||
uses: mongodb-labs/drivers-evergreen-tools@master
|
||||
with:
|
||||
version: "8.0"
|
||||
# Async and our test_dns do not support dnspython 1.X, so we don't run async or dns tests here
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[test]" --resolution=lowest-direct
|
||||
pytest -v test/test_srv_polling.py
|
||||
|
||||
test_minimum_for_async:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
name: Test async's minimum dependencies and Python
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v5
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: setup-mongodb
|
||||
uses: mongodb-labs/drivers-evergreen-tools@master
|
||||
with:
|
||||
version: "8.0"
|
||||
# The lifetime kwarg we use in srv resolution was added to the async resolver API in dnspython 2.1.0
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[test]" --resolution=lowest-direct dnspython==2.1.0 --force-reinstall
|
||||
uv pip install -e ".[test]" --resolution=lowest-direct --force-reinstall
|
||||
pytest -v test/test_srv_polling.py test/test_dns.py test/asynchronous/test_srv_polling.py test/asynchronous/test_dns.py
|
||||
|
||||
@ -382,6 +382,11 @@ If you are running one of the `no-responder` tests, omit the `run-server` step.
|
||||
- Finally, you can use `just setup-tests --debug-log`.
|
||||
- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for failed tests in the patch.
|
||||
|
||||
## Testing minimum dependencies
|
||||
|
||||
To run any of the test suites with minimum supported dependencies, pass `--test-min-deps` to
|
||||
`just setup-tests`.
|
||||
|
||||
## Adding a new test suite
|
||||
|
||||
- If adding new tests files that should only be run for that test suite, add a pytest marker to the file and add
|
||||
|
||||
@ -9,6 +9,8 @@ PyMongo 4.16 brings a number of changes including:
|
||||
- Removed invalid documents from :class:`bson.errors.InvalidDocument` error messages as
|
||||
doing so may leak sensitive user data.
|
||||
Instead, invalid documents are stored in :attr:`bson.errors.InvalidDocument.document`.
|
||||
- PyMongo now requires ``dnspython>=2.6.1``, since ``dnspython`` 1.0 is no longer maintained and is incompatible with
|
||||
Python 3.10+. The minimum version is ``2.6.1`` to account for `CVE-2023-29483 <https://www.cve.org/CVERecord?id=CVE-2023-29483>`_.
|
||||
- Removed support for Eventlet.
|
||||
Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency.
|
||||
|
||||
|
||||
@ -58,20 +58,11 @@ async def _resolve(*args: Any, **kwargs: Any) -> resolver.Answer:
|
||||
if _IS_SYNC:
|
||||
from dns import resolver
|
||||
|
||||
if hasattr(resolver, "resolve"):
|
||||
# dnspython >= 2
|
||||
return resolver.resolve(*args, **kwargs)
|
||||
# dnspython 1.X
|
||||
return resolver.query(*args, **kwargs)
|
||||
return resolver.resolve(*args, **kwargs)
|
||||
else:
|
||||
from dns import asyncresolver
|
||||
|
||||
if hasattr(asyncresolver, "resolve"):
|
||||
# dnspython >= 2
|
||||
return await asyncresolver.resolve(*args, **kwargs) # type:ignore[return-value]
|
||||
raise ConfigurationError(
|
||||
"Upgrade to dnspython version >= 2.0 to use AsyncMongoClient with mongodb+srv:// connections."
|
||||
)
|
||||
return await asyncresolver.resolve(*args, **kwargs) # type:ignore[return-value]
|
||||
|
||||
|
||||
_INVALID_HOST_MSG = (
|
||||
|
||||
@ -58,20 +58,11 @@ def _resolve(*args: Any, **kwargs: Any) -> resolver.Answer:
|
||||
if _IS_SYNC:
|
||||
from dns import resolver
|
||||
|
||||
if hasattr(resolver, "resolve"):
|
||||
# dnspython >= 2
|
||||
return resolver.resolve(*args, **kwargs)
|
||||
# dnspython 1.X
|
||||
return resolver.query(*args, **kwargs)
|
||||
return resolver.resolve(*args, **kwargs)
|
||||
else:
|
||||
from dns import asyncresolver
|
||||
|
||||
if hasattr(asyncresolver, "resolve"):
|
||||
# dnspython >= 2
|
||||
return asyncresolver.resolve(*args, **kwargs) # type:ignore[return-value]
|
||||
raise ConfigurationError(
|
||||
"Upgrade to dnspython version >= 2.0 to use MongoClient with mongodb+srv:// connections."
|
||||
)
|
||||
return asyncresolver.resolve(*args, **kwargs) # type:ignore[return-value]
|
||||
|
||||
|
||||
_INVALID_HOST_MSG = (
|
||||
|
||||
@ -48,8 +48,7 @@ Tracker = "https://jira.mongodb.org/projects/PYTHON/issues"
|
||||
[dependency-groups]
|
||||
dev = []
|
||||
pip = ["pip"]
|
||||
# TODO: PYTHON-5464
|
||||
gevent = ["gevent", "cffi>=2.0.0b1;python_version=='3.14'"]
|
||||
gevent = ["gevent>=20.6.0"]
|
||||
coverage = [
|
||||
"pytest-cov",
|
||||
"coverage>=5,<=7.10.6"
|
||||
@ -57,7 +56,7 @@ coverage = [
|
||||
mockupdb = [
|
||||
"mockupdb@git+https://github.com/mongodb-labs/mongo-mockup-db@master"
|
||||
]
|
||||
perf = ["simplejson"]
|
||||
perf = ["simplejson>=3.17.0"]
|
||||
typing = [
|
||||
"mypy==1.18.1",
|
||||
"pyright==1.1.405",
|
||||
|
||||
@ -1 +1 @@
|
||||
dnspython>=1.16.0,<3.0.0
|
||||
dnspython>=2.6.1,<3.0.0
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
pymongo-auth-aws>=1.1.0,<2.0.0
|
||||
pymongocrypt>=1.13.0,<2.0.0
|
||||
certifi;os.name=='nt' or sys_platform=='darwin'
|
||||
certifi>=2023.7.22;os.name=='nt' or sys_platform=='darwin'
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# Fallback to certifi on Windows if we can't load CA certs from the system
|
||||
# store and just use certifi on macOS.
|
||||
# https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_default_verify_paths
|
||||
certifi;os.name=='nt' or sys_platform=='darwin'
|
||||
certifi>=2023.7.22;os.name=='nt' or sys_platform=='darwin'
|
||||
pyopenssl>=17.2.0
|
||||
requests<3.0.0
|
||||
cryptography>=2.5
|
||||
|
||||
@ -519,6 +519,19 @@ class ClientContext:
|
||||
"Libmongocrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_pymongocrypt_min(self, *ver):
|
||||
other_version = Version(*ver)
|
||||
if not _HAVE_PYMONGOCRYPT:
|
||||
version = Version.from_string("0.0.0")
|
||||
else:
|
||||
from pymongocrypt import __version__ as pymongocrypt_version
|
||||
|
||||
version = Version.from_string(pymongocrypt_version)
|
||||
return self._require(
|
||||
lambda: version >= other_version,
|
||||
"PyMongoCrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_auth(self, func):
|
||||
"""Run a test only if the server is running with auth enabled."""
|
||||
return self._require(
|
||||
|
||||
@ -519,6 +519,19 @@ class AsyncClientContext:
|
||||
"Libmongocrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_pymongocrypt_min(self, *ver):
|
||||
other_version = Version(*ver)
|
||||
if not _HAVE_PYMONGOCRYPT:
|
||||
version = Version.from_string("0.0.0")
|
||||
else:
|
||||
from pymongocrypt import __version__ as pymongocrypt_version
|
||||
|
||||
version = Version.from_string(pymongocrypt_version)
|
||||
return self._require(
|
||||
lambda: version >= other_version,
|
||||
"PyMongoCrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_auth(self, func):
|
||||
"""Run a test only if the server is running with auth enabled."""
|
||||
return self._require(
|
||||
|
||||
@ -2059,7 +2059,7 @@ class TestClient(AsyncIntegrationTest):
|
||||
async def test_handshake_01_aws(self):
|
||||
await self._test_handshake(
|
||||
{
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9",
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10",
|
||||
"AWS_REGION": "us-east-2",
|
||||
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
|
||||
},
|
||||
@ -2097,7 +2097,7 @@ class TestClient(AsyncIntegrationTest):
|
||||
|
||||
async def test_handshake_05_multiple(self):
|
||||
await self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10", "FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
None,
|
||||
)
|
||||
# Extra cases for other combos.
|
||||
@ -2109,13 +2109,16 @@ class TestClient(AsyncIntegrationTest):
|
||||
|
||||
async def test_handshake_06_region_too_long(self):
|
||||
await self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "AWS_REGION": "a" * 512},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10", "AWS_REGION": "a" * 512},
|
||||
{"name": "aws.lambda"},
|
||||
)
|
||||
|
||||
async def test_handshake_07_memory_invalid_int(self):
|
||||
await self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big"},
|
||||
{
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10",
|
||||
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big",
|
||||
},
|
||||
{"name": "aws.lambda"},
|
||||
)
|
||||
|
||||
|
||||
@ -485,7 +485,7 @@ class TestServerMonitoringMode(AsyncIntegrationTest):
|
||||
|
||||
async def test_rtt_connection_is_disabled_auto(self):
|
||||
envs = [
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9"},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10"},
|
||||
{"FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
{"K_SERVICE": "gcpservicename"},
|
||||
{"FUNCTION_NAME": "gcpfunctionname"},
|
||||
|
||||
@ -3448,6 +3448,7 @@ class TestExplicitTextEncryptionProse(AsyncEncryptionIntegrationTest):
|
||||
@async_client_context.require_no_standalone
|
||||
@async_client_context.require_version_min(8, 2, -1)
|
||||
@async_client_context.require_libmongocrypt_min(1, 15, 1)
|
||||
@async_client_context.require_pymongocrypt_min(1, 16, 0)
|
||||
async def asyncSetUp(self):
|
||||
await super().asyncSetUp()
|
||||
# Load the file key1-document.json as key1Document.
|
||||
|
||||
@ -2016,7 +2016,7 @@ class TestClient(IntegrationTest):
|
||||
def test_handshake_01_aws(self):
|
||||
self._test_handshake(
|
||||
{
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9",
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10",
|
||||
"AWS_REGION": "us-east-2",
|
||||
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
|
||||
},
|
||||
@ -2054,7 +2054,7 @@ class TestClient(IntegrationTest):
|
||||
|
||||
def test_handshake_05_multiple(self):
|
||||
self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10", "FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
None,
|
||||
)
|
||||
# Extra cases for other combos.
|
||||
@ -2066,13 +2066,16 @@ class TestClient(IntegrationTest):
|
||||
|
||||
def test_handshake_06_region_too_long(self):
|
||||
self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "AWS_REGION": "a" * 512},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10", "AWS_REGION": "a" * 512},
|
||||
{"name": "aws.lambda"},
|
||||
)
|
||||
|
||||
def test_handshake_07_memory_invalid_int(self):
|
||||
self._test_handshake(
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big"},
|
||||
{
|
||||
"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10",
|
||||
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big",
|
||||
},
|
||||
{"name": "aws.lambda"},
|
||||
)
|
||||
|
||||
|
||||
@ -483,7 +483,7 @@ class TestServerMonitoringMode(IntegrationTest):
|
||||
|
||||
def test_rtt_connection_is_disabled_auto(self):
|
||||
envs = [
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.9"},
|
||||
{"AWS_EXECUTION_ENV": "AWS_Lambda_python3.10"},
|
||||
{"FUNCTIONS_WORKER_RUNTIME": "python"},
|
||||
{"K_SERVICE": "gcpservicename"},
|
||||
{"FUNCTION_NAME": "gcpfunctionname"},
|
||||
|
||||
@ -3430,6 +3430,7 @@ class TestExplicitTextEncryptionProse(EncryptionIntegrationTest):
|
||||
@client_context.require_no_standalone
|
||||
@client_context.require_version_min(8, 2, -1)
|
||||
@client_context.require_libmongocrypt_min(1, 15, 1)
|
||||
@client_context.require_pymongocrypt_min(1, 16, 0)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Load the file key1-document.json as key1Document.
|
||||
|
||||
14
uv.lock
generated
14
uv.lock
generated
@ -1201,7 +1201,6 @@ coverage = [
|
||||
{ name = "pytest-cov" },
|
||||
]
|
||||
gevent = [
|
||||
{ name = "cffi", version = "2.0.0b1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.14.*'" },
|
||||
{ name = "gevent" },
|
||||
]
|
||||
mockupdb = [
|
||||
@ -1222,10 +1221,10 @@ typing = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')", specifier = ">=2023.7.22" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')", specifier = ">=2023.7.22" },
|
||||
{ name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=2.5" },
|
||||
{ name = "dnspython", specifier = ">=1.16.0,<3.0.0" },
|
||||
{ name = "dnspython", specifier = ">=2.6.1,<3.0.0" },
|
||||
{ name = "furo", marker = "extra == 'docs'", specifier = "==2025.7.19" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.13' and extra == 'test'", specifier = ">=7.0" },
|
||||
{ name = "pykerberos", marker = "os_name != 'nt' and extra == 'gssapi'" },
|
||||
@ -1254,12 +1253,9 @@ coverage = [
|
||||
{ name = "pytest-cov" },
|
||||
]
|
||||
dev = []
|
||||
gevent = [
|
||||
{ name = "cffi", marker = "python_full_version == '3.14.*'", specifier = ">=2.0.0b1" },
|
||||
{ name = "gevent" },
|
||||
]
|
||||
gevent = [{ name = "gevent", specifier = ">=20.6.0" }]
|
||||
mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mockup-db?rev=master" }]
|
||||
perf = [{ name = "simplejson" }]
|
||||
perf = [{ name = "simplejson", specifier = ">=3.17.0" }]
|
||||
pip = [{ name = "pip" }]
|
||||
typing = [
|
||||
{ name = "mypy", specifier = "==1.18.1" },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user