PYTHON-2545 Test Atlas Serverless (#664)

This commit is contained in:
Prashant Mital 2021-07-27 16:35:09 -07:00 committed by GitHub
parent 9833ce0a03
commit f07da34f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 293 additions and 84 deletions

View File

@ -415,6 +415,12 @@ functions:
export SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}"
export MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}"
fi
if [ -n "${test_serverless}" ]; then
export TEST_SERVERLESS=1
export MONGODB_URI="${MONGODB_URI}"
export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}"
export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}"
fi
PYTHON_BINARY=${PYTHON_BINARY} \
GREEN_FRAMEWORK=${GREEN_FRAMEWORK} \
@ -836,9 +842,41 @@ post:
- func: "cleanup"
- func: "teardown_docker"
task_groups:
- name: serverless_task_group
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800 # 30 minutes
setup_group:
- func: "fetch source"
- func: "prepare resources"
- command: shell.exec
params:
shell: "bash"
script: |
${PREPARE_SHELL}
set +o xtrace
SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \
SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \
SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \
bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh
- command: expansions.update
params:
file: serverless-expansion.yml
teardown_group:
- command: shell.exec
params:
script: |
${PREPARE_SHELL}
set +o xtrace
SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \
SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \
SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \
SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} \
bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh
tasks:
- ".serverless"
tasks:
# Wildcard task. Do you need to find out what tools are available and where?
# Throw it here, and execute this task on all buildvariants
- name: getdata
@ -1184,6 +1222,11 @@ tasks:
TOPOLOGY: "sharded_cluster"
- func: "run tests"
- name: "test-serverless"
tags: ["serverless"]
commands:
- func: "run tests"
- name: "test-enterprise-auth"
tags: ["enterprise-auth"]
commands:
@ -2040,6 +2083,15 @@ axes:
test_loadbalancer: true
batchtime: 10080 # 7 days
- id: serverless
display_name: "Serverless"
values:
- id: "enabled"
display_name: "Serverless"
variables:
test_serverless: true
batchtime: 10080 # 7 days
buildvariants:
- matrix_name: "tests-all"
matrix_spec:
@ -2473,6 +2525,16 @@ buildvariants:
tasks:
- name: "atlas-connect"
- matrix_name: "serverless"
matrix_spec:
platform: awslinux
python-version: *amazon1-pythons
auth-ssl: auth-ssl
serverless: "*"
display_name: "Serverless ${python-version} ${platform}"
tasks:
- "serverless_task_group"
- matrix_name: "data-lake-spec-tests"
matrix_spec:
platform: ubuntu-16.04

View File

@ -39,12 +39,15 @@ if [ -n "$MONGODB_API_VERSION" ]; then
fi
if [ "$AUTH" != "noauth" ]; then
if [ -z "$DATA_LAKE" ]; then
export DB_USER="bob"
export DB_PASSWORD="pwd123"
else
if [ ! -z "$DATA_LAKE" ]; then
export DB_USER="mhuser"
export DB_PASSWORD="pencil"
elif [ ! -z "$TEST_SERVERLESS" ]; then
export DB_USER=$SERVERLESS_ATLAS_USER
export DB_PASSWORD=$SERVERLESS_ATLAS_PASSWORD
else
export DB_USER="bob"
export DB_PASSWORD="pwd123"
fi
fi

View File

@ -94,6 +94,7 @@ if CA_PEM:
COMPRESSORS = os.environ.get("COMPRESSORS")
MONGODB_API_VERSION = os.environ.get("MONGODB_API_VERSION")
TEST_LOADBALANCER = bool(os.environ.get("TEST_LOADBALANCER"))
TEST_SERVERLESS = bool(os.environ.get("TEST_SERVERLESS"))
SINGLE_MONGOS_LB_URI = os.environ.get("SINGLE_MONGOS_LB_URI")
MULTI_MONGOS_LB_URI = os.environ.get("MULTI_MONGOS_LB_URI")
if TEST_LOADBALANCER:
@ -104,6 +105,13 @@ if TEST_LOADBALANCER:
host, port = res['nodelist'][0]
db_user = res['username'] or db_user
db_pwd = res['password'] or db_pwd
elif TEST_SERVERLESS:
res = parse_uri(os.environ["MONGODB_URI"])
host, port = res['nodelist'].pop(0)
additional_serverless_mongoses = res['nodelist']
db_user = res['username'] or db_user
db_pwd = res['password'] or db_pwd
TLS_OPTIONS = {'tls': True}
def is_server_resolvable():
@ -231,6 +239,7 @@ class ClientContext(object):
self.conn_lock = threading.Lock()
self.is_data_lake = False
self.load_balancer = TEST_LOADBALANCER
self.serverless = TEST_SERVERLESS
if self.load_balancer:
self.default_client_options["loadBalanced"] = True
if COMPRESSORS:
@ -309,22 +318,26 @@ class ClientContext(object):
if self.client:
self.connected = True
try:
self.cmd_line = self.client.admin.command('getCmdLineOpts')
except pymongo.errors.OperationFailure as e:
msg = e.details.get('errmsg', '')
if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
# Unauthorized.
self.auth_enabled = True
else:
raise
if self.serverless:
self.auth_enabled = True
else:
self.auth_enabled = self._server_started_with_auth()
try:
self.cmd_line = self.client.admin.command('getCmdLineOpts')
except pymongo.errors.OperationFailure as e:
msg = e.details.get('errmsg', '')
if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
# Unauthorized.
self.auth_enabled = True
else:
raise
else:
self.auth_enabled = self._server_started_with_auth()
if self.auth_enabled:
# See if db_user already exists.
if not self._check_user_provided():
_create_user(self.client.admin, db_user, db_pwd)
if not self.serverless:
# See if db_user already exists.
if not self._check_user_provided():
_create_user(self.client.admin, db_user, db_pwd)
self.client = self._connect(
host, port, username=db_user, password=db_pwd,
@ -334,10 +347,13 @@ class ClientContext(object):
# May not have this if OperationFailure was raised earlier.
self.cmd_line = self.client.admin.command('getCmdLineOpts')
self.server_status = self.client.admin.command('serverStatus')
if self.storage_engine == "mmapv1":
# MMAPv1 does not support retryWrites=True.
self.default_client_options['retryWrites'] = False
if self.serverless:
self.server_status = {}
else:
self.server_status = self.client.admin.command('serverStatus')
if self.storage_engine == "mmapv1":
# MMAPv1 does not support retryWrites=True.
self.default_client_options['retryWrites'] = False
ismaster = self.ismaster
self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster
@ -374,33 +390,41 @@ class ClientContext(object):
self.nodes = set([(host, port)])
self.w = len(ismaster.get("hosts", [])) or 1
self.version = Version.from_client(self.client)
self.server_parameters = self.client.admin.command(
'getParameter', '*')
if 'enableTestCommands=1' in self.cmd_line['argv']:
if TEST_SERVERLESS:
self.test_commands_enabled = True
elif 'parsed' in self.cmd_line:
params = self.cmd_line['parsed'].get('setParameter', [])
if 'enableTestCommands=1' in params:
self.has_ipv6 = False
else:
self.server_parameters = self.client.admin.command(
'getParameter', '*')
if 'enableTestCommands=1' in self.cmd_line['argv']:
self.test_commands_enabled = True
else:
params = self.cmd_line['parsed'].get('setParameter', {})
if params.get('enableTestCommands') == '1':
elif 'parsed' in self.cmd_line:
params = self.cmd_line['parsed'].get('setParameter', [])
if 'enableTestCommands=1' in params:
self.test_commands_enabled = True
else:
params = self.cmd_line['parsed'].get('setParameter', {})
if params.get('enableTestCommands') == '1':
self.test_commands_enabled = True
self.has_ipv6 = self._server_started_with_ipv6()
self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
self.has_ipv6 = self._server_started_with_ipv6()
if self.is_mongos:
# Check for another mongos on the next port.
address = self.client.address
next_address = address[0], address[1] + 1
self.mongoses.append(address)
mongos_client = self._connect(*next_address,
**self.default_client_options)
if mongos_client:
ismaster = mongos_client.admin.command('ismaster')
if ismaster.get('msg') == 'isdbgrid':
self.mongoses.append(next_address)
if self.serverless:
self.mongoses.append(self.client.address)
self.mongoses.extend(additional_serverless_mongoses)
else:
# Check for another mongos on the next port.
address = self.client.address
next_address = address[0], address[1] + 1
self.mongoses.append(address)
mongos_client = self._connect(
*next_address, **self.default_client_options)
if mongos_client:
ismaster = mongos_client.admin.command('ismaster')
if ismaster.get('msg') == 'isdbgrid':
self.mongoses.append(next_address)
def init(self):
with self.conn_lock:
@ -891,6 +915,9 @@ class IntegrationTest(PyMongoTestCase):
if (client_context.load_balancer and
not getattr(cls, 'RUN_ON_LOAD_BALANCER', False)):
raise SkipTest('this test does not support load balancers')
if (client_context.serverless and
not getattr(cls, 'RUN_ON_SERVERLESS', False)):
raise SkipTest('this test does not support serverless')
cls.client = client_context.client
cls.db = cls.client.pymongo_test
if client_context.auth_enabled:

View File

@ -1,6 +1,6 @@
{
"description": "aggregate-let",
"schemaVersion": "1.0",
"schemaVersion": "1.4",
"createEntities": [
{
"client": {
@ -310,7 +310,8 @@
"description": "Aggregate to collection with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
"minServerVersion": "5.0",
"serverless": "forbid"
}
],
"operations": [

View File

@ -1,13 +1,14 @@
{
"description": "aggregate-out-readConcern",
"schemaVersion": "1.1",
"schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.1.0",
"topologies": [
"replicaset",
"sharded"
]
],
"serverless": "forbid"
}
],
"createEntities": [

View File

@ -6,6 +6,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "Aggregate with collation",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "2.6",
"serverless": "forbid",
"tests": [
{
"description": "Aggregate with $out",

View File

@ -6,6 +6,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "Count documents with collation",

View File

@ -10,6 +10,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "Distinct with a collation",

View File

@ -6,6 +6,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "Find with a collation",

View File

@ -22,6 +22,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "BulkWrite with delete operations and collation",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "DeleteMany when many documents match with collation",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "DeleteOne when many documents matches with collation",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "FindOneAndDelete when one document matches with collation",

View File

@ -10,6 +10,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "FindOneAndReplace when one document matches with collation returning the document after modification",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "FindOneAndUpdate when many documents match with collation returning the document before modification",

View File

@ -10,6 +10,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "ReplaceOne when one document matches with collation",

View File

@ -14,6 +14,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "UpdateMany when many documents match with collation",

View File

@ -10,6 +10,7 @@
}
],
"minServerVersion": "3.4",
"serverless": "forbid",
"tests": [
{
"description": "UpdateOne when one document matches with collation",

View File

@ -12,7 +12,8 @@
"topology": [
"sharded",
"load-balanced"
]
],
"serverless": "forbid"
}
],
"database_name": "retryable-reads-tests",

View File

@ -27,7 +27,7 @@ from pymongo.auth import HAVE_KERBEROS, _build_credentials_tuple
from pymongo.errors import OperationFailure
from pymongo.read_preferences import ReadPreference
from pymongo.saslprep import HAVE_STRINGPREP
from test import client_context, SkipTest, unittest, Version
from test import client_context, IntegrationTest, SkipTest, unittest, Version
from test.utils import (delay,
ignore_deprecations,
single_client,
@ -303,11 +303,12 @@ class TestSASLPlain(unittest.TestCase):
self.assertRaises(OperationFailure, bad_pwd.admin.command, 'ismaster')
class TestSCRAMSHA1(unittest.TestCase):
class TestSCRAMSHA1(IntegrationTest):
@client_context.require_auth
@client_context.require_version_min(2, 7, 2)
def setUp(self):
super(TestSCRAMSHA1, self).setUp()
# Before 2.7.7, SCRAM-SHA-1 had to be enabled from the command line.
if client_context.version < Version(2, 7, 7):
cmd_line = client_context.cmd_line
@ -321,6 +322,7 @@ class TestSCRAMSHA1(unittest.TestCase):
def tearDown(self):
client_context.drop_user('pymongo_test', 'user')
super(TestSCRAMSHA1, self).tearDown()
def test_scram_sha1(self):
host, port = client_context.host, client_context.port
@ -343,11 +345,12 @@ class TestSCRAMSHA1(unittest.TestCase):
# https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#scram-sha-256-and-mechanism-negotiation
class TestSCRAM(unittest.TestCase):
class TestSCRAM(IntegrationTest):
@client_context.require_auth
@client_context.require_version_min(3, 7, 2)
def setUp(self):
super(TestSCRAM, self).setUp()
self._SENSITIVE_COMMANDS = monitoring._SENSITIVE_COMMANDS
monitoring._SENSITIVE_COMMANDS = set([])
self.listener = WhiteListEventListener("saslStart")
@ -356,6 +359,7 @@ class TestSCRAM(unittest.TestCase):
monitoring._SENSITIVE_COMMANDS = self._SENSITIVE_COMMANDS
client_context.client.testscram.command("dropAllUsersFromDatabase")
client_context.client.drop_database("testscram")
super(TestSCRAM, self).tearDown()
def test_scram_skip_empty_exchange(self):
listener = WhiteListEventListener("saslStart", "saslContinue")
@ -571,10 +575,11 @@ class TestSCRAM(unittest.TestCase):
self.assertTrue(thread.success)
class TestAuthURIOptions(unittest.TestCase):
class TestAuthURIOptions(IntegrationTest):
@client_context.require_auth
def setUp(self):
super(TestAuthURIOptions, self).setUp()
client_context.create_user('admin', 'admin', 'pass')
client_context.create_user(
'pymongo_test', 'user', 'pass', ['userAdmin', 'readWrite'])
@ -582,6 +587,7 @@ class TestAuthURIOptions(unittest.TestCase):
def tearDown(self):
client_context.drop_user('pymongo_test', 'user')
client_context.drop_user('admin', 'admin')
super(TestAuthURIOptions, self).tearDown()
def test_uri_options(self):
# Test default to admin

View File

@ -1046,14 +1046,17 @@ class TestAllLegacyScenarios(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super(TestAllLegacyScenarios, cls).setUpClass()
cls.listener = WhiteListEventListener("aggregate", "getMore")
cls.client = rs_or_single_client(event_listeners=[cls.listener])
@classmethod
def tearDownClass(cls):
cls.client.close()
super(TestAllLegacyScenarios, cls).tearDownClass()
def setUp(self):
super(TestAllLegacyScenarios, self).setUp()
self.listener.results.clear()
def setUpCluster(self, scenario_dict):

View File

@ -30,6 +30,15 @@ class TestClientContext(unittest.TestCase):
'PYMONGO_MUST_CONNECT is set. Failed attempts:\n%s' %
(client_context.connection_attempt_info(),))
def test_serverless(self):
if 'TEST_SERVERLESS' not in os.environ:
raise SkipTest('TEST_SERVERLESS is not set')
self.assertTrue(client_context.connected and client_context.serverless,
'client context must be connected to serverless when '
'TEST_SERVERLESS is set. Failed attempts:\n%s' %
(client_context.connection_attempt_info(),))
def test_enableTestCommands_is_disabled(self):
if 'PYMONGO_DISABLE_TEST_COMMANDS' not in os.environ:
raise SkipTest('PYMONGO_DISABLE_TEST_COMMANDS is not set')

View File

@ -25,7 +25,7 @@ from pymongo.errors import ConfigurationError
from pymongo.operations import (DeleteMany, DeleteOne, IndexModel, ReplaceOne,
UpdateMany, UpdateOne)
from pymongo.write_concern import WriteConcern
from test import unittest, client_context
from test import client_context, IntegrationTest, unittest
from test.utils import EventListener, ignore_deprecations, rs_or_single_client
@ -88,11 +88,11 @@ def raisesConfigurationErrorForOldMongoDB(func):
return wrapper
class TestCollation(unittest.TestCase):
class TestCollation(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super(TestCollation, cls).setUpClass()
cls.listener = EventListener()
cls.client = rs_or_single_client(event_listeners=[cls.listener])
cls.db = cls.client.pymongo_test
@ -106,9 +106,11 @@ class TestCollation(unittest.TestCase):
cls.warn_context.__exit__()
cls.warn_context = None
cls.client.close()
super(TestCollation, cls).tearDownClass()
def tearDown(self):
self.listener.results.clear()
super(TestCollation, self).tearDown()
def last_command_started(self):
return self.listener.results['started'][-1].command

View File

@ -28,7 +28,8 @@ TEST_PATH = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'crud', 'unified')
# Generate unified tests.
globals().update(generate_test_classes(TEST_PATH, module=__name__))
globals().update(generate_test_classes(
TEST_PATH, module=__name__, RUN_ON_SERVERLESS=True))
if __name__ == "__main__":
unittest.main()

View File

@ -32,7 +32,7 @@ from pymongo.operations import (InsertOne,
UpdateOne,
UpdateMany)
from test import unittest, IntegrationTest
from test import client_context, unittest, IntegrationTest
from test.utils import (camel_to_snake, camel_to_upper_camel,
camel_to_snake_args, drop_collections, TestCreator)
@ -42,7 +42,7 @@ _TEST_PATH = os.path.join(
class TestAllScenarios(IntegrationTest):
pass
RUN_ON_SERVERLESS = True
def check_result(self, expected_result, result):

View File

@ -32,7 +32,7 @@ from pymongo.read_preferences import ReadPreference
from pymongo.write_concern import WriteConcern
from test import (client_context,
client_knobs,
PyMongoTestCase,
IntegrationTest,
sanitize_cmd,
unittest)
from test.utils import (EventListener,
@ -42,11 +42,12 @@ from test.utils import (EventListener,
wait_until)
class TestCommandMonitoring(PyMongoTestCase):
class TestCommandMonitoring(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super(TestCommandMonitoring, cls).setUpClass()
cls.listener = EventListener()
cls.client = rs_or_single_client(
event_listeners=[cls.listener],
@ -55,9 +56,11 @@ class TestCommandMonitoring(PyMongoTestCase):
@classmethod
def tearDownClass(cls):
cls.client.close()
super(TestCommandMonitoring, cls).tearDownClass()
def tearDown(self):
self.listener.results.clear()
super(TestCommandMonitoring, self).tearDown()
def test_started_simple(self):
self.client.pymongo_test.command('ismaster')
@ -1129,11 +1132,12 @@ class TestCommandMonitoring(PyMongoTestCase):
self.assertEqual({}, succeeded.reply)
class TestGlobalListener(PyMongoTestCase):
class TestGlobalListener(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super(TestGlobalListener, cls).setUpClass()
cls.listener = EventListener()
# We plan to call register(), which internally modifies _LISTENERS.
cls.saved_listeners = copy.deepcopy(monitoring._LISTENERS)
@ -1146,8 +1150,10 @@ class TestGlobalListener(PyMongoTestCase):
def tearDownClass(cls):
monitoring._LISTENERS = cls.saved_listeners
cls.client.close()
super(TestGlobalListener, cls).tearDownClass()
def setUp(self):
super(TestGlobalListener, self).setUp()
self.listener.results.clear()
def test_simple(self):
@ -1167,7 +1173,7 @@ class TestGlobalListener(PyMongoTestCase):
self.assertTrue(isinstance(started.request_id, int))
class TestEventClasses(PyMongoTestCase):
class TestEventClasses(unittest.TestCase):
def test_command_event_repr(self):
request_id, connection_id, operation_id = 1, ('localhost', 27017), 2

View File

@ -34,7 +34,7 @@ sys.path[0:0] = [""]
from pymongo.pool import Pool, PoolOptions
from pymongo.socket_checker import SocketChecker
from test import client_context, unittest
from test import client_context, IntegrationTest, unittest
from test.utils import (get_pool,
joinall,
delay,
@ -154,10 +154,11 @@ def run_cases(client, cases):
assert t.passed, "%s.run() threw an exception" % repr(t)
class _TestPoolingBase(unittest.TestCase):
class _TestPoolingBase(IntegrationTest):
"""Base class for all connection-pool tests."""
def setUp(self):
super(_TestPoolingBase, self).setUp()
self.c = rs_or_single_client()
db = self.c[DB]
db.unique.drop()
@ -167,6 +168,7 @@ class _TestPoolingBase(unittest.TestCase):
def tearDown(self):
self.c.close()
super(_TestPoolingBase, self).tearDown()
def create_pool(
self,

View File

@ -15,18 +15,19 @@
"""Test the read_concern module."""
from bson.son import SON
from pymongo.errors import ConfigurationError, OperationFailure
from pymongo.errors import ConfigurationError
from pymongo.read_concern import ReadConcern
from test import client_context, PyMongoTestCase
from test import client_context, IntegrationTest
from test.utils import single_client, rs_or_single_client, OvertCommandListener
class TestReadConcern(PyMongoTestCase):
class TestReadConcern(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super(TestReadConcern, cls).setUpClass()
cls.listener = OvertCommandListener()
cls.client = single_client(event_listeners=[cls.listener])
cls.db = cls.client.pymongo_test
@ -36,9 +37,11 @@ class TestReadConcern(PyMongoTestCase):
def tearDownClass(cls):
cls.client.close()
client_context.client.pymongo_test.drop_collection('coll')
super(TestReadConcern, cls).tearDownClass()
def tearDown(self):
self.listener.results.clear()
super(TestReadConcern, self).tearDown()
def test_read_concern(self):
rc = ReadConcern()

View File

@ -49,7 +49,7 @@ from test.utils import (connected,
from test.version import Version
class TestSelections(unittest.TestCase):
class TestSelections(IntegrationTest):
@client_context.require_connection
def test_bool(self):
@ -471,7 +471,8 @@ class TestMovingAverage(unittest.TestCase):
avg.add_sample(30)
self.assertAlmostEqual(15.6, avg.get())
class TestMongosAndReadPreference(unittest.TestCase):
class TestMongosAndReadPreference(IntegrationTest):
def test_read_preference_document(self):

View File

@ -52,6 +52,7 @@ class TestClientOptions(PyMongoTestCase):
class TestSpec(SpecRunner):
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
@classmethod
@client_context.require_failCommand_fail_point
@ -68,11 +69,25 @@ class TestSpec(SpecRunner):
if name.lower() in test['description'].lower():
self.skipTest('PyMongo does not support %s' % (name,))
# Skip changeStream related tests on MMAPv1.
# Serverless does not support $out and collation.
if client_context.serverless:
for operation in test['operations']:
if operation['name'] == 'aggregate':
for stage in operation['arguments']['pipeline']:
if "$out" in stage:
self.skipTest(
"MongoDB Serverless does not support $out")
if "collation" in operation['arguments']:
self.skipTest(
"MongoDB Serverless does not support collations")
# Skip changeStream related tests on MMAPv1 and serverless.
test_name = self.id().rsplit('.')[-1]
if ('changestream' in test_name.lower() and
client_context.storage_engine == 'mmapv1'):
self.skipTest("MMAPv1 does not support change streams.")
if 'changestream' in test_name.lower():
if client_context.storage_engine == 'mmapv1':
self.skipTest("MMAPv1 does not support change streams.")
if client_context.serverless:
self.skipTest("Serverless does not support change streams.")
def get_scenario_coll_name(self, scenario_def):
"""Override a test's collection name to support GridFS tests."""

View File

@ -55,6 +55,7 @@ _TEST_PATH = os.path.join(
class TestAllScenarios(SpecRunner):
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
def get_object_name(self, op):
return op.get('object', 'collection')
@ -123,6 +124,7 @@ def non_retryable_single_statement_ops(coll):
class IgnoreDeprecationsTest(IntegrationTest):
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
@classmethod
def setUpClass(cls):
@ -420,6 +422,7 @@ class TestRetryableWrites(IgnoreDeprecationsTest):
class TestWriteConcernError(IntegrationTest):
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
@classmethod
@client_context.require_replica_set

View File

@ -958,17 +958,27 @@ class TestCausalConsistency(unittest.TestCase):
lambda coll, session: coll.drop_index("foo_1", session=session))
self._test_no_read_concern(
lambda coll, session: coll.drop_indexes(session=session))
# Not a write, but explain also doesn't support readConcern.
self._test_no_read_concern(
lambda coll, session: coll.find({}, session=session).explain())
@client_context.require_no_standalone
@unittest.skipIf(client_context.serverless,
"Serverless does not support currentOp")
def test_writes_do_not_include_read_concern_current_op(self):
# Not a write, but currentOp also doesn't support readConcern.
self._test_no_read_concern(
lambda coll, session: coll.database.current_op(session=session))
@client_context.require_no_standalone
@unittest.skipIf(client_context.serverless,
"Serverless does not support mapReduce")
def test_writes_do_not_include_read_concern_map_reduce(self):
self._test_no_read_concern(
lambda coll, session: coll.map_reduce(
'function() {}', 'function() {}', 'mrout', session=session))
# They are not writes, but currentOp and explain also don't support
# readConcern.
self._test_no_read_concern(
lambda coll, session: coll.database.current_op(session=session))
self._test_no_read_concern(
lambda coll, session: coll.find({}, session=session).explain())
@client_context.require_no_standalone
@client_context.require_version_max(4, 1, 0)
def test_aggregate_out_does_not_include_read_concern(self):
@ -1164,6 +1174,7 @@ class TestClusterTime(IntegrationTest):
class TestSpec(SpecRunner):
RUN_ON_SERVERLESS = True
# Location of JSON test specifications.
TEST_PATH = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'sessions', 'legacy')

View File

@ -76,6 +76,7 @@ class TransactionsBase(SpecRunner):
class TestTransactions(TransactionsBase):
RUN_ON_SERVERLESS = True
@client_context.require_transactions
def test_transaction_options_validation(self):
default_options = TransactionOptions()

View File

@ -34,6 +34,7 @@ globals().update(generate_test_classes(TEST_PATH, module=__name__))
class TestServerApi(IntegrationTest):
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
def test_server_api_defaults(self):
api = ServerApi(ServerApiVersion.V1)

View File

@ -117,6 +117,14 @@ def is_run_on_requirement_satisfied(requirement):
max_version_satisfied = Version.from_string(
req_max_server_version) >= server_version
serverless = requirement.get('serverless')
if serverless == "require":
serverless_satisfied = client_context.serverless
elif serverless == "forbid":
serverless_satisfied = not client_context.serverless
else: # unset or "allow"
serverless_satisfied = True
params_satisfied = True
params = requirement.get('serverParameters')
if params:
@ -135,7 +143,8 @@ def is_run_on_requirement_satisfied(requirement):
auth_satisfied = not client_context.auth_enabled
return (topology_satisfied and min_version_satisfied and
max_version_satisfied and params_satisfied and auth_satisfied)
max_version_satisfied and serverless_satisfied and
params_satisfied and auth_satisfied)
def parse_collection_or_database_options(options):
@ -1154,7 +1163,8 @@ _SCHEMA_VERSION_MAJOR_TO_MIXIN_CLASS = {
def generate_test_classes(test_path, module=__name__, class_name_prefix='',
expected_failures=[],
bypass_test_generation_errors=False):
bypass_test_generation_errors=False,
**kwargs):
"""Method for generating test classes. Returns a dictionary where keys are
the names of test classes and values are the test class objects."""
test_klasses = {}
@ -1195,10 +1205,12 @@ def generate_test_classes(test_path, module=__name__, class_name_prefix='',
raise ValueError(
"test file '%s' has unsupported schemaVersion '%s'" % (
fpath, schema_version))
module_dict = {'__module__': module}
module_dict.update(kwargs)
test_klasses[class_name] = type(
class_name,
(mixin_class, test_base_class_factory(scenario_def),),
{'__module__': module})
module_dict)
except Exception:
if bypass_test_generation_errors:
continue

View File

@ -24,6 +24,7 @@ import shutil
import sys
import threading
import time
import unittest
import warnings
from collections import abc, defaultdict
@ -391,6 +392,18 @@ class TestCreator(object):
if max_ver is not None:
method = client_context.require_version_max(*max_ver)(method)
if 'serverless' in scenario_def:
serverless = scenario_def['serverless']
if serverless == "require":
serverless_satisfied = client_context.serverless
elif serverless == "forbid":
serverless_satisfied = not client_context.serverless
else: # unset or "allow"
serverless_satisfied = True
method = unittest.skipUnless(
serverless_satisfied,
"Serverless requirement not satisfied")(method)
return method
@staticmethod
@ -423,6 +436,16 @@ class TestCreator(object):
return not client_context.auth_enabled
return True
@staticmethod
def serverless_ok(run_on_req):
serverless = run_on_req['serverless']
if serverless == "require":
return client_context.serverless
elif serverless == "forbid":
return not client_context.serverless
else: # unset or "allow"
return True
def should_run_on(self, scenario_def):
run_on = scenario_def.get('runOn', [])
if not run_on:
@ -433,7 +456,8 @@ class TestCreator(object):
if (self.valid_topology(req) and
self.min_server_version(req) and
self.max_server_version(req) and
self.valid_auth_enabled(req)):
self.valid_auth_enabled(req) and
self.serverless_ok(req)):
return True
return False