From 050a1acea100ae045994c37c751fc71eaa364c03 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Fri, 23 Aug 2024 13:36:19 -0700 Subject: [PATCH] SNOW-1362666: improve random string generation (#2034) --- DESCRIPTION.md | 3 +++ src/snowflake/connector/util_text.py | 2 +- test/unit/test_text_util.py | 37 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/unit/test_text_util.py diff --git a/DESCRIPTION.md b/DESCRIPTION.md index 46f2966ea..f17ad6829 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -8,6 +8,9 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne # Release Notes +- v3.12.2 (TBD) + - Improved implementation of `snowflake.connector.util_text.random_string` to avoid collisions. + - v3.12.1(August 20,2024) - Fixed a bug that logged the session token when renewing a session. - Fixed a bug where disabling client telemetry did not work. diff --git a/src/snowflake/connector/util_text.py b/src/snowflake/connector/util_text.py index 0aef1bba2..bff19ca1b 100644 --- a/src/snowflake/connector/util_text.py +++ b/src/snowflake/connector/util_text.py @@ -282,5 +282,5 @@ def random_string( suffix: Suffix to add to random string generated. choices: A generator of things to choose from. """ - random_part = "".join([random.choice(choices) for _ in range(length)]) + random_part = "".join([random.Random().choice(choices) for _ in range(length)]) return "".join([prefix, random_part, suffix]) diff --git a/test/unit/test_text_util.py b/test/unit/test_text_util.py new file mode 100644 index 000000000..69895b019 --- /dev/null +++ b/test/unit/test_text_util.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. +# + +import concurrent.futures +import random + +import pytest + +try: + from snowflake.connector.util_text import random_string +except ImportError: + pass + +pytestmark = pytest.mark.skipolddriver # old test driver tests won't run this module + + +def test_random_string_generation_with_same_global_seed(): + random.seed(42) + random_string1 = random_string() + random.seed(42) + random_string2 = random_string() + assert ( + isinstance(random_string1, str) + and isinstance(random_string2, str) + and random_string1 != random_string2 + ) + + def get_random_string(): + random.seed(42) + return random_string() + + with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: + # Submit tasks to the pool and get future objects + futures = [executor.submit(get_random_string) for _ in range(5)] + res = [f.result() for f in futures] + assert len(set(res)) == 5 # no duplicate string