|
13 | 13 | import socket
|
14 | 14 | import threading
|
15 | 15 | import typing
|
| 16 | +import uuid |
16 | 17 |
|
17 | 18 | from testgres import InvalidOperationException
|
18 | 19 | from testgres import ExecUtilException
|
19 | 20 |
|
| 21 | +from concurrent.futures import ThreadPoolExecutor |
| 22 | +from concurrent.futures import Future as ThreadFuture |
| 23 | + |
20 | 24 |
|
21 | 25 | class TestOsOpsCommon:
|
22 | 26 | sm_os_ops_descrs: typing.List[OsOpsDescr] = [
|
@@ -812,3 +816,267 @@ def LOCAL_server(s: socket.socket):
|
812 | 816 |
|
813 | 817 | if ok_count == 0:
|
814 | 818 | raise RuntimeError("No one free port was found.")
|
| 819 | + |
| 820 | + class tagData_OS_OPS__NUMS: |
| 821 | + os_ops_descr: OsOpsDescr |
| 822 | + nums: int |
| 823 | + |
| 824 | + def __init__(self, os_ops_descr: OsOpsDescr, nums: int): |
| 825 | + assert isinstance(os_ops_descr, OsOpsDescr) |
| 826 | + assert type(nums) == int # noqa: E721 |
| 827 | + |
| 828 | + self.os_ops_descr = os_ops_descr |
| 829 | + self.nums = nums |
| 830 | + |
| 831 | + sm_test_exclusive_creation__mt__data = [ |
| 832 | + tagData_OS_OPS__NUMS(OsOpsDescrs.sm_local_os_ops_descr, 100000), |
| 833 | + tagData_OS_OPS__NUMS(OsOpsDescrs.sm_remote_os_ops_descr, 120), |
| 834 | + ] |
| 835 | + |
| 836 | + @pytest.fixture( |
| 837 | + params=sm_test_exclusive_creation__mt__data, |
| 838 | + ids=[x.os_ops_descr.sign for x in sm_test_exclusive_creation__mt__data] |
| 839 | + ) |
| 840 | + def data001(self, request: pytest.FixtureRequest) -> tagData_OS_OPS__NUMS: |
| 841 | + assert isinstance(request, pytest.FixtureRequest) |
| 842 | + return request.param |
| 843 | + |
| 844 | + def test_mkdir__mt(self, data001: tagData_OS_OPS__NUMS): |
| 845 | + assert type(data001) == __class__.tagData_OS_OPS__NUMS # noqa: E721 |
| 846 | + |
| 847 | + N_WORKERS = 4 |
| 848 | + N_NUMBERS = data001.nums |
| 849 | + assert type(N_NUMBERS) == int # noqa: E721 |
| 850 | + |
| 851 | + os_ops = data001.os_ops_descr.os_ops |
| 852 | + assert isinstance(os_ops, OsOperations) |
| 853 | + |
| 854 | + lock_dir_prefix = "test_mkdir_mt--" + uuid.uuid4().hex |
| 855 | + |
| 856 | + lock_dir = os_ops.mkdtemp(prefix=lock_dir_prefix) |
| 857 | + |
| 858 | + logging.info("A lock file [{}] is creating ...".format(lock_dir)) |
| 859 | + |
| 860 | + assert os.path.exists(lock_dir) |
| 861 | + |
| 862 | + def MAKE_PATH(lock_dir: str, num: int) -> str: |
| 863 | + assert type(lock_dir) == str # noqa: E721 |
| 864 | + assert type(num) == int # noqa: E721 |
| 865 | + return os.path.join(lock_dir, str(num) + ".lock") |
| 866 | + |
| 867 | + def LOCAL_WORKER(os_ops: OsOperations, |
| 868 | + workerID: int, |
| 869 | + lock_dir: str, |
| 870 | + cNumbers: int, |
| 871 | + reservedNumbers: typing.Set[int]) -> None: |
| 872 | + assert isinstance(os_ops, OsOperations) |
| 873 | + assert type(workerID) == int # noqa: E721 |
| 874 | + assert type(lock_dir) == str # noqa: E721 |
| 875 | + assert type(cNumbers) == int # noqa: E721 |
| 876 | + assert type(reservedNumbers) == set # noqa: E721 |
| 877 | + assert cNumbers > 0 |
| 878 | + assert len(reservedNumbers) == 0 |
| 879 | + |
| 880 | + assert os.path.exists(lock_dir) |
| 881 | + |
| 882 | + def LOG_INFO(template: str, *args: list) -> None: |
| 883 | + assert type(template) == str # noqa: E721 |
| 884 | + assert type(args) == tuple # noqa: E721 |
| 885 | + |
| 886 | + msg = template.format(*args) |
| 887 | + assert type(msg) == str # noqa: E721 |
| 888 | + |
| 889 | + logging.info("[Worker #{}] {}".format(workerID, msg)) |
| 890 | + return |
| 891 | + |
| 892 | + LOG_INFO("HELLO! I am here!") |
| 893 | + |
| 894 | + for num in range(cNumbers): |
| 895 | + assert not (num in reservedNumbers) |
| 896 | + |
| 897 | + file_path = MAKE_PATH(lock_dir, num) |
| 898 | + |
| 899 | + try: |
| 900 | + os_ops.makedir(file_path) |
| 901 | + except Exception as e: |
| 902 | + LOG_INFO( |
| 903 | + "Can't reserve {}. Error ({}): {}", |
| 904 | + num, |
| 905 | + type(e).__name__, |
| 906 | + str(e) |
| 907 | + ) |
| 908 | + continue |
| 909 | + |
| 910 | + LOG_INFO("Number {} is reserved!", num) |
| 911 | + assert os_ops.path_exists(file_path) |
| 912 | + reservedNumbers.add(num) |
| 913 | + continue |
| 914 | + |
| 915 | + n_total = cNumbers |
| 916 | + n_ok = len(reservedNumbers) |
| 917 | + assert n_ok <= n_total |
| 918 | + |
| 919 | + LOG_INFO("Finish! OK: {}. FAILED: {}.", n_ok, n_total - n_ok) |
| 920 | + return |
| 921 | + |
| 922 | + # ----------------------- |
| 923 | + logging.info("Worker are creating ...") |
| 924 | + |
| 925 | + threadPool = ThreadPoolExecutor( |
| 926 | + max_workers=N_WORKERS, |
| 927 | + thread_name_prefix="ex_creator" |
| 928 | + ) |
| 929 | + |
| 930 | + class tadWorkerData: |
| 931 | + future: ThreadFuture |
| 932 | + reservedNumbers: typing.Set[int] |
| 933 | + |
| 934 | + workerDatas: typing.List[tadWorkerData] = list() |
| 935 | + |
| 936 | + nErrors = 0 |
| 937 | + |
| 938 | + try: |
| 939 | + for n in range(N_WORKERS): |
| 940 | + logging.info("worker #{} is creating ...".format(n)) |
| 941 | + |
| 942 | + workerDatas.append(tadWorkerData()) |
| 943 | + |
| 944 | + workerDatas[n].reservedNumbers = set() |
| 945 | + |
| 946 | + workerDatas[n].future = threadPool.submit( |
| 947 | + LOCAL_WORKER, |
| 948 | + os_ops, |
| 949 | + n, |
| 950 | + lock_dir, |
| 951 | + N_NUMBERS, |
| 952 | + workerDatas[n].reservedNumbers |
| 953 | + ) |
| 954 | + |
| 955 | + assert workerDatas[n].future is not None |
| 956 | + |
| 957 | + logging.info("OK. All the workers were created!") |
| 958 | + except Exception as e: |
| 959 | + nErrors += 1 |
| 960 | + logging.error("A problem is detected ({}): {}".format(type(e).__name__, str(e))) |
| 961 | + |
| 962 | + logging.info("Will wait for stop of all the workers...") |
| 963 | + |
| 964 | + nWorkers = 0 |
| 965 | + |
| 966 | + assert type(workerDatas) == list # noqa: E721 |
| 967 | + |
| 968 | + for i in range(len(workerDatas)): |
| 969 | + worker = workerDatas[i].future |
| 970 | + |
| 971 | + if worker is None: |
| 972 | + continue |
| 973 | + |
| 974 | + nWorkers += 1 |
| 975 | + |
| 976 | + assert isinstance(worker, ThreadFuture) |
| 977 | + |
| 978 | + try: |
| 979 | + logging.info("Wait for worker #{}".format(i)) |
| 980 | + worker.result() |
| 981 | + except Exception as e: |
| 982 | + nErrors += 1 |
| 983 | + logging.error("Worker #{} finished with error ({}): {}".format( |
| 984 | + i, |
| 985 | + type(e).__name__, |
| 986 | + str(e), |
| 987 | + )) |
| 988 | + continue |
| 989 | + |
| 990 | + assert nWorkers == N_WORKERS |
| 991 | + |
| 992 | + if nErrors != 0: |
| 993 | + raise RuntimeError("Some problems were detected. Please examine the log messages.") |
| 994 | + |
| 995 | + logging.info("OK. Let's check worker results!") |
| 996 | + |
| 997 | + reservedNumbers: typing.Dict[int, int] = dict() |
| 998 | + |
| 999 | + for i in range(N_WORKERS): |
| 1000 | + logging.info("Worker #{} is checked ...".format(i)) |
| 1001 | + |
| 1002 | + workerNumbers = workerDatas[i].reservedNumbers |
| 1003 | + assert type(workerNumbers) == set # noqa: E721 |
| 1004 | + |
| 1005 | + for n in workerNumbers: |
| 1006 | + if n < 0 or n >= N_NUMBERS: |
| 1007 | + nErrors += 1 |
| 1008 | + logging.error("Unexpected number {}".format(n)) |
| 1009 | + continue |
| 1010 | + |
| 1011 | + if n in reservedNumbers.keys(): |
| 1012 | + nErrors += 1 |
| 1013 | + logging.error("Number {} was already reserved by worker #{}".format( |
| 1014 | + n, |
| 1015 | + reservedNumbers[n] |
| 1016 | + )) |
| 1017 | + else: |
| 1018 | + reservedNumbers[n] = i |
| 1019 | + |
| 1020 | + file_path = MAKE_PATH(lock_dir, n) |
| 1021 | + if not os_ops.path_exists(file_path): |
| 1022 | + nErrors += 1 |
| 1023 | + logging.error("File {} is not found!".format(file_path)) |
| 1024 | + continue |
| 1025 | + |
| 1026 | + continue |
| 1027 | + |
| 1028 | + logging.info("OK. Let's check reservedNumbers!") |
| 1029 | + |
| 1030 | + for n in range(N_NUMBERS): |
| 1031 | + if not (n in reservedNumbers.keys()): |
| 1032 | + nErrors += 1 |
| 1033 | + logging.error("Number {} is not reserved!".format(n)) |
| 1034 | + continue |
| 1035 | + |
| 1036 | + file_path = MAKE_PATH(lock_dir, n) |
| 1037 | + if not os_ops.path_exists(file_path): |
| 1038 | + nErrors += 1 |
| 1039 | + logging.error("File {} is not found!".format(file_path)) |
| 1040 | + continue |
| 1041 | + |
| 1042 | + # OK! |
| 1043 | + continue |
| 1044 | + |
| 1045 | + logging.info("Verification is finished! Total error count is {}.".format(nErrors)) |
| 1046 | + |
| 1047 | + if nErrors == 0: |
| 1048 | + logging.info("Root lock-directory [{}] will be deleted.".format( |
| 1049 | + lock_dir |
| 1050 | + )) |
| 1051 | + |
| 1052 | + for n in range(N_NUMBERS): |
| 1053 | + file_path = MAKE_PATH(lock_dir, n) |
| 1054 | + try: |
| 1055 | + os_ops.rmdir(file_path) |
| 1056 | + except Exception as e: |
| 1057 | + nErrors += 1 |
| 1058 | + logging.error("Cannot delete directory [{}]. Error ({}): {}".format( |
| 1059 | + file_path, |
| 1060 | + type(e).__name__, |
| 1061 | + str(e) |
| 1062 | + )) |
| 1063 | + continue |
| 1064 | + |
| 1065 | + if os_ops.path_exists(file_path): |
| 1066 | + nErrors += 1 |
| 1067 | + logging.error("Directory {} is not deleted!".format(file_path)) |
| 1068 | + continue |
| 1069 | + |
| 1070 | + if nErrors == 0: |
| 1071 | + try: |
| 1072 | + os_ops.rmdir(lock_dir) |
| 1073 | + except Exception as e: |
| 1074 | + nErrors += 1 |
| 1075 | + logging.error("Cannot delete directory [{}]. Error ({}): {}".format( |
| 1076 | + lock_dir, |
| 1077 | + type(e).__name__, |
| 1078 | + str(e) |
| 1079 | + )) |
| 1080 | + |
| 1081 | + logging.info("Test is finished! Total error count is {}.".format(nErrors)) |
| 1082 | + return |
0 commit comments