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 sys
|
||||||
import zipfile
|
import zipfile
|
||||||
from io import BytesIO
|
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
|
from pymongo.asynchronous.database import AsyncDatabase
|
||||||
|
|
||||||
sys.path[0:0] = [""]
|
sys.path[0:0] = [""]
|
||||||
|
|
||||||
from test import IntegrationTest, qcheck, unittest
|
from test.utils import EventListener, async_rs_or_single_client
|
||||||
from test.utils import EventListener, async_rs_or_single_client, rs_or_single_client
|
|
||||||
|
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from gridfs import GridFS
|
|
||||||
from gridfs.asynchronous.grid_file import (
|
from gridfs.asynchronous.grid_file import (
|
||||||
_SEEK_CUR,
|
_SEEK_CUR,
|
||||||
_SEEK_END,
|
_SEEK_END,
|
||||||
@ -44,7 +48,7 @@ from gridfs.asynchronous.grid_file import (
|
|||||||
from gridfs.errors import NoFile
|
from gridfs.errors import NoFile
|
||||||
from pymongo import AsyncMongoClient
|
from pymongo import AsyncMongoClient
|
||||||
from pymongo.asynchronous.helpers import aiter, anext
|
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
|
from pymongo.message import _CursorAddress
|
||||||
|
|
||||||
_IS_SYNC = False
|
_IS_SYNC = False
|
||||||
@ -407,8 +411,6 @@ class AsyncTestGridFile(AsyncIntegrationTest):
|
|||||||
g = AsyncGridOut(self.db.fs, f._id)
|
g = AsyncGridOut(self.db.fs, f._id)
|
||||||
self.assertEqual(random_string, await g.read())
|
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):
|
async def test_small_chunks(self):
|
||||||
self.files = 0
|
self.files = 0
|
||||||
self.chunks = 0
|
self.chunks = 0
|
||||||
@ -431,7 +433,7 @@ class AsyncTestGridFile(AsyncIntegrationTest):
|
|||||||
self.assertEqual(data, await g.read(10) + await g.read(10))
|
self.assertEqual(data, await g.read(10) + await g.read(10))
|
||||||
return True
|
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):
|
async def test_seek(self):
|
||||||
f = AsyncGridIn(self.db.fs, chunkSize=3)
|
f = AsyncGridIn(self.db.fs, chunkSize=3)
|
||||||
|
|||||||
@ -25,6 +25,8 @@ from bson.dbref import DBRef
|
|||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from bson.son import SON
|
from bson.son import SON
|
||||||
|
|
||||||
|
_IS_SYNC = True
|
||||||
|
|
||||||
gen_target = 100
|
gen_target = 100
|
||||||
reduction_attempts = 10
|
reduction_attempts = 10
|
||||||
examples = 5
|
examples = 5
|
||||||
@ -221,7 +223,10 @@ def reduce(case, predicate, reductions=0):
|
|||||||
|
|
||||||
|
|
||||||
def isnt(predicate):
|
def isnt(predicate):
|
||||||
return lambda x: not predicate(x)
|
def is_not(x):
|
||||||
|
return not predicate(x)
|
||||||
|
|
||||||
|
return is_not
|
||||||
|
|
||||||
|
|
||||||
def check(predicate, generator):
|
def check(predicate, generator):
|
||||||
|
|||||||
@ -21,17 +21,21 @@ import io
|
|||||||
import sys
|
import sys
|
||||||
import zipfile
|
import zipfile
|
||||||
from io import BytesIO
|
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
|
from pymongo.synchronous.database import Database
|
||||||
|
|
||||||
sys.path[0:0] = [""]
|
sys.path[0:0] = [""]
|
||||||
|
|
||||||
from test import IntegrationTest, qcheck, unittest
|
|
||||||
from test.utils import EventListener, rs_or_single_client
|
from test.utils import EventListener, rs_or_single_client
|
||||||
|
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from gridfs import GridFS
|
|
||||||
from gridfs.errors import NoFile
|
from gridfs.errors import NoFile
|
||||||
from gridfs.synchronous.grid_file import (
|
from gridfs.synchronous.grid_file import (
|
||||||
_SEEK_CUR,
|
_SEEK_CUR,
|
||||||
@ -43,7 +47,7 @@ from gridfs.synchronous.grid_file import (
|
|||||||
GridOutCursor,
|
GridOutCursor,
|
||||||
)
|
)
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from pymongo.errors import ConfigurationError, InvalidOperation, ServerSelectionTimeoutError
|
from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError
|
||||||
from pymongo.message import _CursorAddress
|
from pymongo.message import _CursorAddress
|
||||||
from pymongo.synchronous.helpers import iter, next
|
from pymongo.synchronous.helpers import iter, next
|
||||||
|
|
||||||
@ -405,8 +409,6 @@ class TestGridFile(IntegrationTest):
|
|||||||
g = GridOut(self.db.fs, f._id)
|
g = GridOut(self.db.fs, f._id)
|
||||||
self.assertEqual(random_string, g.read())
|
self.assertEqual(random_string, g.read())
|
||||||
|
|
||||||
# TODO: https://jira.mongodb.org/browse/PYTHON-4708
|
|
||||||
@client_context.require_sync
|
|
||||||
def test_small_chunks(self):
|
def test_small_chunks(self):
|
||||||
self.files = 0
|
self.files = 0
|
||||||
self.chunks = 0
|
self.chunks = 0
|
||||||
|
|||||||
@ -159,6 +159,7 @@ converted_tests = [
|
|||||||
"conftest.py",
|
"conftest.py",
|
||||||
"pymongo_mocks.py",
|
"pymongo_mocks.py",
|
||||||
"utils_spec_runner.py",
|
"utils_spec_runner.py",
|
||||||
|
"qcheck.py",
|
||||||
"test_bulk.py",
|
"test_bulk.py",
|
||||||
"test_client.py",
|
"test_client.py",
|
||||||
"test_client_bulk_write.py",
|
"test_client_bulk_write.py",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user