PYTHON-4708 - Convert test.qcheck to async (#1832)
This commit is contained in:
parent
26c55048d4
commit
6e9bf1e4a8
255
test/asynchronous/qcheck.py
Normal file
255
test/asynchronous/qcheck.py
Normal file
@ -0,0 +1,255 @@
|
||||
# Copyright 2009-present MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from bson.dbref import DBRef
|
||||
from bson.objectid import ObjectId
|
||||
from bson.son import SON
|
||||
|
||||
_IS_SYNC = False
|
||||
|
||||
gen_target = 100
|
||||
reduction_attempts = 10
|
||||
examples = 5
|
||||
|
||||
|
||||
def lift(value):
|
||||
return lambda: value
|
||||
|
||||
|
||||
def choose_lifted(generator_list):
|
||||
return lambda: random.choice(generator_list)
|
||||
|
||||
|
||||
def my_map(generator, function):
|
||||
return lambda: function(generator())
|
||||
|
||||
|
||||
def choose(list):
|
||||
return lambda: random.choice(list)()
|
||||
|
||||
|
||||
def gen_range(start, stop):
|
||||
return lambda: random.randint(start, stop)
|
||||
|
||||
|
||||
def gen_int():
|
||||
max_int = 2147483647
|
||||
return lambda: random.randint(-max_int - 1, max_int)
|
||||
|
||||
|
||||
def gen_float():
|
||||
return lambda: (random.random() - 0.5) * sys.maxsize
|
||||
|
||||
|
||||
def gen_boolean():
|
||||
return lambda: random.choice([True, False])
|
||||
|
||||
|
||||
def gen_printable_char():
|
||||
return lambda: chr(random.randint(32, 126))
|
||||
|
||||
|
||||
def gen_printable_string(gen_length):
|
||||
return lambda: "".join(gen_list(gen_printable_char(), gen_length)())
|
||||
|
||||
|
||||
def gen_char(set=None):
|
||||
return lambda: bytes([random.randint(0, 255)])
|
||||
|
||||
|
||||
def gen_string(gen_length):
|
||||
return lambda: b"".join(gen_list(gen_char(), gen_length)())
|
||||
|
||||
|
||||
def gen_unichar():
|
||||
return lambda: chr(random.randint(1, 0xFFF))
|
||||
|
||||
|
||||
def gen_unicode(gen_length):
|
||||
return lambda: "".join([x for x in gen_list(gen_unichar(), gen_length)() if x not in ".$"])
|
||||
|
||||
|
||||
def gen_list(generator, gen_length):
|
||||
return lambda: [generator() for _ in range(gen_length())]
|
||||
|
||||
|
||||
def gen_datetime():
|
||||
return lambda: datetime.datetime(
|
||||
random.randint(1970, 2037),
|
||||
random.randint(1, 12),
|
||||
random.randint(1, 28),
|
||||
random.randint(0, 23),
|
||||
random.randint(0, 59),
|
||||
random.randint(0, 59),
|
||||
random.randint(0, 999) * 1000,
|
||||
)
|
||||
|
||||
|
||||
def gen_dict(gen_key, gen_value, gen_length):
|
||||
def a_dict(gen_key, gen_value, length):
|
||||
result = {}
|
||||
for _ in range(length):
|
||||
result[gen_key()] = gen_value()
|
||||
return result
|
||||
|
||||
return lambda: a_dict(gen_key, gen_value, gen_length())
|
||||
|
||||
|
||||
def gen_regexp(gen_length):
|
||||
# TODO our patterns only consist of one letter.
|
||||
# this is because of a bug in CPython's regex equality testing,
|
||||
# which I haven't quite tracked down, so I'm just ignoring it...
|
||||
def pattern():
|
||||
return "".join(gen_list(choose_lifted("a"), gen_length)())
|
||||
|
||||
def gen_flags():
|
||||
flags = 0
|
||||
if random.random() > 0.5:
|
||||
flags = flags | re.IGNORECASE
|
||||
if random.random() > 0.5:
|
||||
flags = flags | re.MULTILINE
|
||||
if random.random() > 0.5:
|
||||
flags = flags | re.VERBOSE
|
||||
|
||||
return flags
|
||||
|
||||
return lambda: re.compile(pattern(), gen_flags())
|
||||
|
||||
|
||||
def gen_objectid():
|
||||
return lambda: ObjectId()
|
||||
|
||||
|
||||
def gen_dbref():
|
||||
collection = gen_unicode(gen_range(0, 20))
|
||||
return lambda: DBRef(collection(), gen_mongo_value(1, True)())
|
||||
|
||||
|
||||
def gen_mongo_value(depth, ref):
|
||||
choices = [
|
||||
gen_unicode(gen_range(0, 50)),
|
||||
gen_printable_string(gen_range(0, 50)),
|
||||
my_map(gen_string(gen_range(0, 1000)), bytes),
|
||||
gen_int(),
|
||||
gen_float(),
|
||||
gen_boolean(),
|
||||
gen_datetime(),
|
||||
gen_objectid(),
|
||||
lift(None),
|
||||
]
|
||||
if ref:
|
||||
choices.append(gen_dbref())
|
||||
if depth > 0:
|
||||
choices.append(gen_mongo_list(depth, ref))
|
||||
choices.append(gen_mongo_dict(depth, ref))
|
||||
return choose(choices)
|
||||
|
||||
|
||||
def gen_mongo_list(depth, ref):
|
||||
return gen_list(gen_mongo_value(depth - 1, ref), gen_range(0, 10))
|
||||
|
||||
|
||||
def gen_mongo_dict(depth, ref=True):
|
||||
return my_map(
|
||||
gen_dict(gen_unicode(gen_range(0, 20)), gen_mongo_value(depth - 1, ref), gen_range(0, 10)),
|
||||
SON,
|
||||
)
|
||||
|
||||
|
||||
def simplify(case): # TODO this is a hack
|
||||
if isinstance(case, SON) and "$ref" not in case:
|
||||
simplified = SON(case) # make a copy!
|
||||
if random.choice([True, False]):
|
||||
# delete
|
||||
simplified_keys = list(simplified)
|
||||
if not len(simplified_keys):
|
||||
return (False, case)
|
||||
simplified.pop(random.choice(simplified_keys))
|
||||
return (True, simplified)
|
||||
else:
|
||||
# simplify a value
|
||||
simplified_items = list(simplified.items())
|
||||
if not len(simplified_items):
|
||||
return (False, case)
|
||||
(key, value) = random.choice(simplified_items)
|
||||
(success, value) = simplify(value)
|
||||
simplified[key] = value
|
||||
return (success, success and simplified or case)
|
||||
if isinstance(case, list):
|
||||
simplified = list(case)
|
||||
if random.choice([True, False]):
|
||||
# delete
|
||||
if not len(simplified):
|
||||
return (False, case)
|
||||
simplified.pop(random.randrange(len(simplified)))
|
||||
return (True, simplified)
|
||||
else:
|
||||
# simplify an item
|
||||
if not len(simplified):
|
||||
return (False, case)
|
||||
index = random.randrange(len(simplified))
|
||||
(success, value) = simplify(simplified[index])
|
||||
simplified[index] = value
|
||||
return (success, success and simplified or case)
|
||||
return (False, case)
|
||||
|
||||
|
||||
async def reduce(case, predicate, reductions=0):
|
||||
for _ in range(reduction_attempts):
|
||||
(reduced, simplified) = simplify(case)
|
||||
if reduced and not await predicate(simplified):
|
||||
return await reduce(simplified, predicate, reductions + 1)
|
||||
return (reductions, case)
|
||||
|
||||
|
||||
async def isnt(predicate):
|
||||
async def is_not(x):
|
||||
return not await predicate(x)
|
||||
|
||||
return is_not
|
||||
|
||||
|
||||
async def check(predicate, generator):
|
||||
counter_examples = []
|
||||
for _ in range(gen_target):
|
||||
case = generator()
|
||||
try:
|
||||
if not await predicate(case):
|
||||
reduction = await reduce(case, predicate)
|
||||
counter_examples.append("after {} reductions: {!r}".format(*reduction))
|
||||
except:
|
||||
counter_examples.append(f"{case!r} : {traceback.format_exc()}")
|
||||
return counter_examples
|
||||
|
||||
|
||||
async def check_unittest(test, predicate, generator):
|
||||
counter_examples = await check(predicate, generator)
|
||||
if counter_examples:
|
||||
failures = len(counter_examples)
|
||||
message = "\n".join([" -> %s" % f for f in counter_examples[:examples]])
|
||||
message = "found %d counter examples, displaying first %d:\n%s" % (
|
||||
failures,
|
||||
min(failures, examples),
|
||||
message,
|
||||
)
|
||||
test.fail(message)
|
||||
@ -21,17 +21,21 @@ import io
|
||||
import sys
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from test.asynchronous import AsyncIntegrationTest, AsyncUnitTest, async_client_context
|
||||
from test.asynchronous import (
|
||||
AsyncIntegrationTest,
|
||||
AsyncUnitTest,
|
||||
async_client_context,
|
||||
qcheck,
|
||||
unittest,
|
||||
)
|
||||
|
||||
from pymongo.asynchronous.database import AsyncDatabase
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from test import IntegrationTest, qcheck, unittest
|
||||
from test.utils import EventListener, async_rs_or_single_client, rs_or_single_client
|
||||
from test.utils import EventListener, async_rs_or_single_client
|
||||
|
||||
from bson.objectid import ObjectId
|
||||
from gridfs import GridFS
|
||||
from gridfs.asynchronous.grid_file import (
|
||||
_SEEK_CUR,
|
||||
_SEEK_END,
|
||||
@ -44,7 +48,7 @@ from gridfs.asynchronous.grid_file import (
|
||||
from gridfs.errors import NoFile
|
||||
from pymongo import AsyncMongoClient
|
||||
from pymongo.asynchronous.helpers import aiter, anext
|
||||
from pymongo.errors import ConfigurationError, InvalidOperation, ServerSelectionTimeoutError
|
||||
from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError
|
||||
from pymongo.message import _CursorAddress
|
||||
|
||||
_IS_SYNC = False
|
||||
@ -407,8 +411,6 @@ class AsyncTestGridFile(AsyncIntegrationTest):
|
||||
g = AsyncGridOut(self.db.fs, f._id)
|
||||
self.assertEqual(random_string, await g.read())
|
||||
|
||||
# TODO: https://jira.mongodb.org/browse/PYTHON-4708
|
||||
@async_client_context.require_sync
|
||||
async def test_small_chunks(self):
|
||||
self.files = 0
|
||||
self.chunks = 0
|
||||
@ -431,7 +433,7 @@ class AsyncTestGridFile(AsyncIntegrationTest):
|
||||
self.assertEqual(data, await g.read(10) + await g.read(10))
|
||||
return True
|
||||
|
||||
qcheck.check_unittest(self, helper, qcheck.gen_string(qcheck.gen_range(0, 20)))
|
||||
await qcheck.check_unittest(self, helper, qcheck.gen_string(qcheck.gen_range(0, 20)))
|
||||
|
||||
async def test_seek(self):
|
||||
f = AsyncGridIn(self.db.fs, chunkSize=3)
|
||||
|
||||
@ -25,6 +25,8 @@ from bson.dbref import DBRef
|
||||
from bson.objectid import ObjectId
|
||||
from bson.son import SON
|
||||
|
||||
_IS_SYNC = True
|
||||
|
||||
gen_target = 100
|
||||
reduction_attempts = 10
|
||||
examples = 5
|
||||
@ -221,7 +223,10 @@ def reduce(case, predicate, reductions=0):
|
||||
|
||||
|
||||
def isnt(predicate):
|
||||
return lambda x: not predicate(x)
|
||||
def is_not(x):
|
||||
return not predicate(x)
|
||||
|
||||
return is_not
|
||||
|
||||
|
||||
def check(predicate, generator):
|
||||
|
||||
@ -21,17 +21,21 @@ import io
|
||||
import sys
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from test import IntegrationTest, UnitTest, client_context
|
||||
from test import (
|
||||
IntegrationTest,
|
||||
UnitTest,
|
||||
client_context,
|
||||
qcheck,
|
||||
unittest,
|
||||
)
|
||||
|
||||
from pymongo.synchronous.database import Database
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from test import IntegrationTest, qcheck, unittest
|
||||
from test.utils import EventListener, rs_or_single_client
|
||||
|
||||
from bson.objectid import ObjectId
|
||||
from gridfs import GridFS
|
||||
from gridfs.errors import NoFile
|
||||
from gridfs.synchronous.grid_file import (
|
||||
_SEEK_CUR,
|
||||
@ -43,7 +47,7 @@ from gridfs.synchronous.grid_file import (
|
||||
GridOutCursor,
|
||||
)
|
||||
from pymongo import MongoClient
|
||||
from pymongo.errors import ConfigurationError, InvalidOperation, ServerSelectionTimeoutError
|
||||
from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError
|
||||
from pymongo.message import _CursorAddress
|
||||
from pymongo.synchronous.helpers import iter, next
|
||||
|
||||
@ -405,8 +409,6 @@ class TestGridFile(IntegrationTest):
|
||||
g = GridOut(self.db.fs, f._id)
|
||||
self.assertEqual(random_string, g.read())
|
||||
|
||||
# TODO: https://jira.mongodb.org/browse/PYTHON-4708
|
||||
@client_context.require_sync
|
||||
def test_small_chunks(self):
|
||||
self.files = 0
|
||||
self.chunks = 0
|
||||
|
||||
@ -159,6 +159,7 @@ converted_tests = [
|
||||
"conftest.py",
|
||||
"pymongo_mocks.py",
|
||||
"utils_spec_runner.py",
|
||||
"qcheck.py",
|
||||
"test_bulk.py",
|
||||
"test_client.py",
|
||||
"test_client_bulk_write.py",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user