Skip to content

Commit 1826ff0

Browse files
committed
add CBOR integration testing (#177)
This commit adds a re-run of the integration tests, but carried over CBOR. The commit also enables parameter testing, but modifying the "protocol" tests and adding an equality test with a parameter for each of them; the paramter is the same value that the tests use to fetch from the server. With this commit the application now queries the name of the cluster to match the cataog name against (so that it can be run against nightly build or a local compmilation easily). (cherry picked from commit 2572afc)
1 parent 4f1414d commit 1826ff0

File tree

3 files changed

+98
-44
lines changed

3 files changed

+98
-44
lines changed

test/integration/elasticsearch.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,20 +261,23 @@ def reset(self, es_dir):
261261
self._enable_xpack(es_dir)
262262

263263
@staticmethod
264-
def is_listening(password=None):
264+
def cluster_name(password=None):
265265
auth = ("elastic", password) if password else None
266266
try:
267-
req = requests.get("http://localhost:%s" % Elasticsearch.ES_PORT, auth=auth, timeout=.5)
267+
resp = requests.get("http://localhost:%s" % Elasticsearch.ES_PORT, auth=auth, timeout=.5)
268268
except (requests.Timeout, requests.ConnectionError):
269-
return False
270-
if req.status_code != 200:
269+
return None
270+
if resp.status_code != 200:
271271
if password:
272-
raise Exception("unexpected ES response code received: %s" % req.status_code)
272+
raise Exception("unexpected ES response code received: %s" % resp.status_code)
273273
else:
274-
return True
275-
if "You Know, for Search" not in req.text:
276-
raise Exception("unexpected ES answer received: %s" % req.text)
277-
return True
274+
return ""
275+
if "cluster_name" not in resp.json():
276+
raise Exception("unexpected ES answer received: %s" % resp.text)
277+
return resp.json().get("cluster_name")
278278

279+
@staticmethod
280+
def is_listening(password=None):
281+
return Elasticsearch.cluster_name(password) is not None
279282

280283
# vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=118 :

test/integration/ites.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,13 @@ def ites(args):
6565
# run the tests
6666
if not args.skip_tests:
6767
assert(data is not None)
68-
tests = Testing(data, args.dsn)
69-
tests.perform()
68+
cluster_name = es.cluster_name(Elasticsearch.AUTH_PASSWORD)
69+
assert(len(cluster_name))
70+
if args.dsn:
71+
Testing(data, cluster_name, args.dsn).perform()
72+
else:
73+
Testing(data, cluster_name, "Packing=JSON;").perform()
74+
Testing(data, cluster_name, "Packing=CBOR;").perform()
7075

7176
def main():
7277
parser = argparse.ArgumentParser(description='Integration Testing with Elasticsearch.')
@@ -78,11 +83,11 @@ def main():
7883
stage_grp.add_argument("-p", "--pre-staged", help="Use a pre-staged and running Elasticsearch instance",
7984
action="store_true", default=False)
8085

81-
driver_grp = parser.add_mutually_exclusive_group()
82-
driver_grp.add_argument("-d", "--driver", help="The path to the driver file to test; if not provided, the driver "
86+
parser.add_argument("-d", "--driver", help="The path to the driver file to test; if not provided, the driver "
8387
"is assumed to have been installed.")
84-
driver_grp.add_argument("-c", "--dsn", help="The connection string to use with a preinstalled driver; the DSN must"
85-
" contain the name under which the driver to test is registered.")
88+
parser.add_argument("-c", "--dsn", help="The full or partial connection string to use with a preinstalled "
89+
"driver; if the provided string contains the name under which the driver to test is registered, it will "
90+
"be used as such; otherwise it will be appended as additional parameters to a pre-configured DSN.")
8691
parser.add_argument("-o", "--offline_dir", help="The directory path holding the files to copy the test data from, "
8792
"as opposed to downloading them.")
8893
parser.add_argument("-e", "--ephemeral", help="Remove the staged Elasticsearch and installed driver after testing"
@@ -104,6 +109,9 @@ def main():
104109
if not (args.driver or args.version or args.es_reset or args.pre_staged):
105110
parser.error("don't know what Elasticsearch version to test against.")
106111

112+
if args.driver and args.dsn and "Driver=" in args.dsn:
113+
parser.error("driver specified both by -d/--driver and -c/--dsn arguments")
114+
107115
try:
108116
started_at = time.time()
109117

test/integration/testing.py

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@
1616

1717
UID = "elastic"
1818
CONNECT_STRING = 'Driver={Elasticsearch Driver};UID=%s;PWD=%s;Secure=0;' % (UID, Elasticsearch.AUTH_PASSWORD)
19-
CATALOG = "elasticsearch" # nightly built
20-
#CATALOG = "distribution_run" # source built
19+
CATALOG = "distribution_run" # source built, "elasticsearch": nightly builds
2120

2221
class Testing(unittest.TestCase):
2322

2423
_data = None
2524
_dsn = None
2625
_pyodbc = None
26+
_catalog = None
2727

28-
def __init__(self, test_data, dsn=None):
28+
def __init__(self, test_data, catalog=CATALOG, dsn=None):
2929
super().__init__()
3030
self._data = test_data
31-
self._dsn = dsn if dsn else CONNECT_STRING
31+
self._catalog = catalog
32+
if dsn:
33+
if "Driver=" not in dsn:
34+
self._dsn = CONNECT_STRING + dsn
35+
else:
36+
self._dsn = dsn
37+
else:
38+
self._dsn = CONNECT_STRING
3239
print("Using DSN: '%s'." % self._dsn)
3340

3441
# only import pyODBC if running tests (vs. for instance only loading test data in ES)
@@ -120,8 +127,8 @@ def _catalog_tables(self, no_table_type_as=""):
120127
res = curs.tables("", "%", "", no_table_type_as).fetchall()
121128
self.assertEqual(len(res), 1)
122129
for i in range(0,10):
123-
self.assertEqual(res[0][i], None if i else CATALOG)
124-
#self.assertEqual(res, [tuple([CATALOG] + [None for i in range(9)])]) # XXX?
130+
self.assertEqual(res[0][i], None if i else self._catalog)
131+
#self.assertEqual(res, [tuple([self._catalog] + [None for i in range(9)])]) # XXX?
125132

126133
# enumerate table types
127134
res = curs.tables("", "", "", "%").fetchall()
@@ -146,11 +153,12 @@ def _catalog_columns(self, use_catalog=False, use_surrogate=True):
146153
cnxn.autocommit = True
147154
curs = cnxn.cursor()
148155
if not use_surrogate:
149-
res = curs.columns(table=TestData.BATTERS_INDEX, catalog=CATALOG if use_catalog else None).fetchall()
156+
res = curs.columns(table=TestData.BATTERS_INDEX, \
157+
catalog=self._catalog if use_catalog else None).fetchall()
150158
else:
151159
if use_catalog:
152160
stmt = "SYS COLUMNS CATALOG '%s' TABLE LIKE '%s' ESCAPE '\\' LIKE '%%' ESCAPE '\\'" % \
153-
(CATALOG, TestData.BATTERS_INDEX)
161+
(self._catalog, TestData.BATTERS_INDEX)
154162
else:
155163
stmt = "SYS COLUMNS TABLE LIKE '%s' ESCAPE '\\' LIKE '%%' ESCAPE '\\'" % TestData.BATTERS_INDEX
156164
res = curs.execute(stmt)
@@ -207,6 +215,8 @@ def _type_to_instance(self, data_type, data_val):
207215
instance = float(data_val)
208216
elif data_type == "float":
209217
instance = float(data_val.strip("fF"))
218+
# reduce precision, py's float is a double
219+
instance = ctypes.c_float(instance).value
210220
elif data_type in ["datetime", "date", "time"]:
211221
fmt = "%H:%M:%S"
212222
fmt = "%Y-%m-%dT" + fmt
@@ -257,32 +267,65 @@ def _proto_tests(self):
257267
for t in tests:
258268
(query, col_name, data_type, data_val, cli_val, disp_size) = t
259269
# print("T: %s, %s, %s, %s, %s, %s" % (query, col_name, data_type, data_val, cli_val, disp_size))
260-
with cnxn.execute(query) as curs:
261-
self.assertEqual(curs.rowcount, 1)
262-
res = curs.fetchone()[0]
263-
264-
if data_val != cli_val: # INTERVAL tests
265-
assert(query.lower().startswith("select interval"))
266-
# extract the literal value (`INTERVAL -'1 1' -> `-1 1``)
267-
expect = re.match("[^-]*(-?\s*'[^']*').*", query).groups()[0]
268-
expect = expect.replace("'", "")
269-
# filter out tests with fractional seconds:
270-
# https://github.com/elastic/elasticsearch/issues/41635
271-
if re.search("\d*\.\d+", expect):
272-
continue
273-
else: # non-INTERVAL tests
274-
assert(data_type.lower() == data_type)
275-
# Change the value read in the tests to type and format of the result expected to be
276-
# returned by driver.
277-
expect = self._type_to_instance(data_type, data_val)
278-
279-
self.assertEqual(res, expect)
270+
271+
if data_val != cli_val: # INTERVAL tests
272+
assert(query.lower().startswith("select interval"))
273+
# extract the literal value (`INTERVAL -'1 1' -> `-1 1``)
274+
expect = re.match("[^-]*(-?\s*'[^']*').*", query).groups()[0]
275+
expect = expect.replace("'", "")
276+
# filter out tests with fractional seconds:
277+
# https://github.com/elastic/elasticsearch/issues/41635
278+
if re.search("\d*\.\d+", expect):
279+
continue
280+
# intervals not supported as params; PyODBC has no interval type support
281+
# https://github.com/elastic/elasticsearch/issues/45915
282+
params = []
283+
else: # non-INTERVAL tests
284+
assert(data_type.lower() == data_type)
285+
# Change the value read in the tests to type and format of the result expected to be
286+
# returned by driver.
287+
expect = self._type_to_instance(data_type, data_val)
288+
289+
if data_type.lower() == "null":
290+
query += " WHERE ? IS NULL"
291+
params = [expect]
292+
else:
293+
if data_type.lower() == "time":
294+
if col_name.find("+") <= 0:
295+
# ODBC's TIME_STRUCT lacks fractional component -> strip it away
296+
col_name = re.sub(r"(\d{2})\.\d+", "\\1", col_name)
297+
query += " WHERE %s = ?" % col_name
298+
params = [expect]
299+
else: # it's a time with offset
300+
# TIE_STRUCT lacks offset component -> perform the simple SELECT
301+
params = []
302+
else:
303+
query += " WHERE %s = ?" % col_name
304+
params = [expect]
305+
# print("Query: %s" % query)
306+
307+
last_ex = None
308+
with cnxn.execute(query, *params) as curs:
309+
try:
310+
self.assertEqual(curs.rowcount, 1)
311+
res = curs.fetchone()[0]
312+
if data_type == "float":
313+
# PyODBC will fetch a REAL/float as a double => reduce precision
314+
res = ctypes.c_float(res).value
315+
self.assertEqual(res, expect)
316+
except Exception as e:
317+
print(e)
318+
last_ex = e
319+
320+
if last_ex:
321+
raise last_ex
322+
280323
finally:
281324
cnxn.clear_output_converters()
282325

283326
def perform(self):
284327
self._check_info(self._pyodbc.SQL_USER_NAME, UID)
285-
self._check_info(self._pyodbc.SQL_DATABASE_NAME, CATALOG)
328+
self._check_info(self._pyodbc.SQL_DATABASE_NAME, self._catalog)
286329

287330
# simulate catalog querying as apps do in ES/GH#40775 do
288331
self._catalog_tables(no_table_type_as = "")

0 commit comments

Comments
 (0)