From ecdea295724f3637479addec97974590b99649cf Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Fri, 6 Mar 2026 23:28:43 +0000 Subject: [PATCH] Experimental parallel ctest support --- .github/workflows/mosquitto-cmake.yml | 2 +- test/apps/ctrl/CMakeLists.txt | 12 +++++ test/apps/db_dump/CMakeLists.txt | 11 ++++ test/apps/passwd/CMakeLists.txt | 11 ++++ test/broker/09-plugin-delayed-auth.py | 2 + test/broker/CMakeLists.txt | 74 +++++++++++++++++++++++++++ test/client/CMakeLists.txt | 18 +++++++ test/lib/CMakeLists.txt | 1 + test/mosq_test.py | 32 ++++++++---- test/resource.json | 52 +++++++++++++++++++ 10 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 test/resource.json diff --git a/.github/workflows/mosquitto-cmake.yml b/.github/workflows/mosquitto-cmake.yml index 07dcd853..3ad7c5c2 100644 --- a/.github/workflows/mosquitto-cmake.yml +++ b/.github/workflows/mosquitto-cmake.yml @@ -58,4 +58,4 @@ jobs: - run: cmake --build build --parallel $(nproc) - working-directory: build/ - run: ctest --output-on-failure --repeat until-pass:5 + run: ctest -j20 --resource-spec-file ../test/resource.json --output-on-failure --repeat until-pass:5 diff --git a/test/apps/ctrl/CMakeLists.txt b/test/apps/ctrl/CMakeLists.txt index 45c4c636..3dc86437 100644 --- a/test/apps/ctrl/CMakeLists.txt +++ b/test/apps/ctrl/CMakeLists.txt @@ -4,6 +4,11 @@ set(EXCLUDE_LIST # none ) +set(PORT_REQ_2_LIST + ctrl-broker + ctrl-dynsec +) + foreach(PY_TEST_FILE ${PY_TEST_FILES}) get_filename_component(PY_TEST_NAME ${PY_TEST_FILE} NAME_WE) if(${PY_TEST_NAME} IN_LIST EXCLUDE_LIST) @@ -16,6 +21,13 @@ foreach(PY_TEST_FILE ${PY_TEST_FILES}) PROPERTIES ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) + if(${PY_TEST_NAME} IN_LIST PORT_REQ_2_LIST) + set(PORTREQ "ports:1,ports:1") + set_tests_properties(apps-${PY_TEST_NAME} + PROPERTIES + RESOURCE_GROUPS ${PORTREQ} + ) + endif() endforeach() diff --git a/test/apps/db_dump/CMakeLists.txt b/test/apps/db_dump/CMakeLists.txt index 2c899b0d..cd7f88a0 100644 --- a/test/apps/db_dump/CMakeLists.txt +++ b/test/apps/db_dump/CMakeLists.txt @@ -4,6 +4,10 @@ set(EXCLUDE_LIST # none ) +set(PORT_REQ_1_LIST + db-dump-stats-current +) + foreach(PY_TEST_FILE ${PY_TEST_FILES}) get_filename_component(PY_TEST_NAME ${PY_TEST_FILE} NAME_WE) if(${PY_TEST_NAME} IN_LIST EXCLUDE_LIST) @@ -16,4 +20,11 @@ foreach(PY_TEST_FILE ${PY_TEST_FILES}) PROPERTIES ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) + if(${PY_TEST_NAME} IN_LIST PORT_REQ_1_LIST) + set(PORTREQ "ports:1") + set_tests_properties(apps-${PY_TEST_NAME} + PROPERTIES + RESOURCE_GROUPS ${PORTREQ} + ) + endif() endforeach() diff --git a/test/apps/passwd/CMakeLists.txt b/test/apps/passwd/CMakeLists.txt index 59a8b274..2a3a5cd8 100644 --- a/test/apps/passwd/CMakeLists.txt +++ b/test/apps/passwd/CMakeLists.txt @@ -4,6 +4,10 @@ set(EXCLUDE_LIST # none ) +set(PORT_REQ_1_LIST + passwd-changes +) + foreach(PY_TEST_FILE ${PY_TEST_FILES}) get_filename_component(PY_TEST_NAME ${PY_TEST_FILE} NAME_WE) if(${PY_TEST_NAME} IN_LIST EXCLUDE_LIST) @@ -16,4 +20,11 @@ foreach(PY_TEST_FILE ${PY_TEST_FILES}) PROPERTIES ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) + if(${PY_TEST_NAME} IN_LIST PORT_REQ_1_LIST) + set(PORTREQ "ports:1") + set_tests_properties(apps-${PY_TEST_NAME} + PROPERTIES + RESOURCE_GROUPS ${PORTREQ} + ) + endif() endforeach() diff --git a/test/broker/09-plugin-delayed-auth.py b/test/broker/09-plugin-delayed-auth.py index 968237e3..01c1322a 100755 --- a/test/broker/09-plugin-delayed-auth.py +++ b/test/broker/09-plugin-delayed-auth.py @@ -49,6 +49,8 @@ def do_test(proto_ver): rc = 0 + except Exception as e: + print(e) except mosq_test.TestError: pass finally: diff --git a/test/broker/CMakeLists.txt b/test/broker/CMakeLists.txt index 642c505e..6149a029 100644 --- a/test/broker/CMakeLists.txt +++ b/test/broker/CMakeLists.txt @@ -22,6 +22,68 @@ set(EXCLUDE_LIST 06-bridge-clean-session-core ) +set(PORT_REQ_4_LIST + 16-config-huge + 17-control-list-listeners +) + +set(PORT_REQ_3_LIST + 08-tls-psk-bridge + 09-plugin-load-basic-auth + 22-http-api-api +) + +set(PORT_REQ_2_LIST + 01-connect-auto-id + 01-connect-listener-allow-anonymous + 01-connect-zero-length-id + 04-retain-check-source-persist-diff-port + 06-bridge-b2br-disconnect-qos1 + 06-bridge-b2br-disconnect-qos2 + 06-bridge-b2br-late-connection-retain + 06-bridge-b2br-late-connection + 06-bridge-b2br-remapping + 06-bridge-br2b-disconnect-qos1 + 06-bridge-br2b-disconnect-qos2 + 06-bridge-br2b-remapping + 06-bridge-clean-session-csF-lcsF + 06-bridge-clean-session-csF-lcsN + 06-bridge-clean-session-csF-lcsT + 06-bridge-clean-session-csT-lcsF + 06-bridge-clean-session-csT-lcsN + 06-bridge-clean-session-csT-lcsT + 06-bridge-config-reload + 06-bridge-fail-persist-resend-qos1 + 06-bridge-fail-persist-resend-qos2 + 06-bridge-outgoing-retain + 06-bridge-per-listener-settings + 06-bridge-reconnect-local-out + 06-bridge-remap-receive-wildcard + 06-bridge-remote-shutdown + 08-ssl-bridge + 08-ssl-connect-cert-auth-crl + 08-ssl-connect-cert-auth-expired-allowed + 08-ssl-connect-cert-auth-expired + 08-ssl-connect-cert-auth-revoked + 08-ssl-connect-cert-auth-without + 08-ssl-connect-cert-auth + 08-ssl-connect-dhparam + 08-ssl-connect-identity + 08-ssl-connect-no-auth-wrong-ca + 08-ssl-connect-no-auth + 08-ssl-connect-no-identity + 08-tls-psk-pub + 09-plugin-load-acl + 09-plugin-load-extended-auth + 10-listener-mount-point + 15-persist-bridge-queue + 22-http-api-acl + 22-http-api-auth + 22-http-api-file + 22-http-api-tls +) + + if(NOT MICROHTTPD_LIBRARY) foreach(PY_HTTP_API_TEST_FILE ${PY_HTTP_API_TEST_FILES}) get_filename_component(PY_HTTP_API_TEST_NAME ${PY_HTTP_API_TEST_FILE} NAME_WE) @@ -39,11 +101,21 @@ foreach(PY_TEST_FILE ${PY_TEST_FILES}) if(${PY_TEST_NAME} IN_LIST EXCLUDE_LIST OR ${PY_TEST_NAME} IN_LIST SQLITE_LIST) continue() endif() + if(${PY_TEST_NAME} IN_LIST PORT_REQ_4_LIST) + set(PORTREQ "ports:1,ports:1,ports:1,ports:1") + elseif(${PY_TEST_NAME} IN_LIST PORT_REQ_3_LIST) + set(PORTREQ "ports:1,ports:1,ports:1") + elseif(${PY_TEST_NAME} IN_LIST PORT_REQ_2_LIST) + set(PORTREQ "ports:1,ports:1") + else() + set(PORTREQ "ports:1") + endif() add_test(NAME broker-${PY_TEST_NAME} COMMAND ${PY_TEST_FILE} ) set_tests_properties(broker-${PY_TEST_NAME} PROPERTIES + RESOURCE_GROUPS ${PORTREQ} ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) endforeach() @@ -54,8 +126,10 @@ foreach(PERSIST_TYPE ${PERSIST_LIST}) add_test(NAME broker-${PY_TEST_NAME}-${PERSIST_TYPE} COMMAND ${PY_TEST_FILE} ${PERSIST_TYPE} ) + set(PORTREQ "ports:1") set_tests_properties(broker-${PY_TEST_NAME}-${PERSIST_TYPE} PROPERTIES + RESOURCE_GROUPS ${PORTREQ} ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) endforeach() diff --git a/test/client/CMakeLists.txt b/test/client/CMakeLists.txt index 0fd8f455..9ffb3077 100644 --- a/test/client/CMakeLists.txt +++ b/test/client/CMakeLists.txt @@ -4,16 +4,34 @@ set(EXCLUDE_LIST # none ) + +set(PORT_REQ_2_LIST + 02-subscribe-qos1-ws + 03-publish-qos1-ws-large + 03-publish-qos1-ws + 03-publish-socks-auth-failed + 03-publish-socks-no-auth + 03-publish-socks + 04-rr-qos1-ws +) + + foreach(PY_TEST_FILE ${PY_TEST_FILES}) get_filename_component(PY_TEST_NAME ${PY_TEST_FILE} NAME_WE) if(${PY_TEST_NAME} IN_LIST EXCLUDE_LIST) continue() endif() + if(${PY_TEST_NAME} IN_LIST PORT_REQ_2_LIST) + set(PORTREQ "ports:1,ports:1") + else() + set(PORTREQ "ports:1") + endif() add_test(NAME client-${PY_TEST_NAME} COMMAND ${PY_TEST_FILE} ) set_tests_properties(client-${PY_TEST_NAME} PROPERTIES + RESOURCE_GROUPS ${PORTREQ} ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) endforeach() diff --git a/test/lib/CMakeLists.txt b/test/lib/CMakeLists.txt index 2e63347a..520cdf42 100644 --- a/test/lib/CMakeLists.txt +++ b/test/lib/CMakeLists.txt @@ -20,6 +20,7 @@ foreach(PY_TEST_FILE ${PY_TEST_FILES}) ) set_tests_properties(lib-${PY_TEST_NAME} PROPERTIES + RESOURCE_GROUPS "ports:1" ENVIRONMENT "BUILD_ROOT=${CMAKE_BINARY_DIR}" ) endforeach() diff --git a/test/mosq_test.py b/test/mosq_test.py index 42da1b9d..49590245 100644 --- a/test/mosq_test.py +++ b/test/mosq_test.py @@ -880,19 +880,31 @@ def pack_remaining_length(remaining_length): def get_port(count=1): - if count == 1: - if len(sys.argv) == 2: - return int(sys.argv[1]) + ports_def = os.environ.get('CTEST_RESOURCE_GROUP_0_PORTS') + if ports_def is not None: + ports = ports_def.split(";") + p = () + for port in ports: + (pid, slots) = port.split(",") + p = p + (int(pid.split(":")[1]),) + if len(p) == 1: + return p[0] else: - return 1888 - else: - if len(sys.argv) >= 1+count: - p = () - for i in range(0, count): - p = p + (int(sys.argv[1+i]),) return p + else: + if count == 1: + if len(sys.argv) == 2: + return int(sys.argv[1]) + else: + return 1888 else: - return tuple(range(1888, 1888+count)) + if len(sys.argv) >= 1+count: + p = () + for i in range(0, count): + p = p + (int(sys.argv[1+i]),) + return p + else: + return tuple(range(1888, 1888+count)) def do_ping(sock, error_string="pingresp"): diff --git a/test/resource.json b/test/resource.json new file mode 100644 index 00000000..c9d178fc --- /dev/null +++ b/test/resource.json @@ -0,0 +1,52 @@ +{ + "version": { + "major": 1, + "minor": 0 +}, + "local": [ + { + "ports": [ + { "id": "1888" }, + { "id": "1889" }, + { "id": "1890" }, + { "id": "1891" }, + { "id": "1892" }, + { "id": "1893" }, + { "id": "1894" }, + { "id": "1895" }, + { "id": "1896" }, + { "id": "1897" }, + { "id": "1898" }, + { "id": "1899" }, + { "id": "1900" }, + { "id": "1901" }, + { "id": "1902" }, + { "id": "1903" }, + { "id": "1904" }, + { "id": "1905" }, + { "id": "1906" }, + { "id": "1907" }, + { "id": "1908" }, + { "id": "1909" }, + { "id": "1910" }, + { "id": "1911" }, + { "id": "1912" }, + { "id": "1913" }, + { "id": "1914" }, + { "id": "1915" }, + { "id": "1916" }, + { "id": "1917" }, + { "id": "1918" }, + { "id": "1919" }, + { "id": "1920" }, + { "id": "1921" }, + { "id": "1922" }, + { "id": "1923" }, + { "id": "1924" }, + { "id": "1925" }, + { "id": "1926" }, + { "id": "1927" } + ] + } + ] +}