From 73b555bca77fb12205aed65f046805a8bbea067f Mon Sep 17 00:00:00 2001 From: Arnaud Date: Tue, 26 May 2026 14:28:09 +0400 Subject: [PATCH 1/2] Add tests --- tests/Dockerfile | 12 ++-- tests/test_plum.nim | 155 +++++++++++++++++++++++++++++++------------- vendor/libplum | 2 +- 3 files changed, 117 insertions(+), 52 deletions(-) diff --git a/tests/Dockerfile b/tests/Dockerfile index 11ab8a9..e4b21ef 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -49,12 +49,12 @@ RUN rm -rf vendor/libplum/build && \ # Compile test binaries (protocol x memory manager) COPY tests/ tests/ -RUN nim c -d:miniupnp_protocol=pcp --mm:orc -o:tests/test_pcp_orc tests/test_plum.nim && \ - nim c -d:miniupnp_protocol=pcp --mm:refc -o:tests/test_pcp_refc tests/test_plum.nim && \ - nim c -d:miniupnp_protocol=upnp --mm:orc -o:tests/test_upnp_orc tests/test_plum.nim && \ - nim c -d:miniupnp_protocol=upnp --mm:refc -o:tests/test_upnp_refc tests/test_plum.nim && \ - nim c -d:miniupnp_protocol=natpmp --mm:orc -o:tests/test_natpmp_orc tests/test_plum.nim && \ - nim c -d:miniupnp_protocol=natpmp --mm:refc -o:tests/test_natpmp_refc tests/test_plum.nim +RUN nim c -d:miniupnp_protocol=pcp --mm:orc --threads:on -o:tests/test_pcp_orc tests/test_plum.nim && \ + nim c -d:miniupnp_protocol=pcp --mm:refc --threads:on -o:tests/test_pcp_refc tests/test_plum.nim && \ + nim c -d:miniupnp_protocol=upnp --mm:orc --threads:on -o:tests/test_upnp_orc tests/test_plum.nim && \ + nim c -d:miniupnp_protocol=upnp --mm:refc --threads:on -o:tests/test_upnp_refc tests/test_plum.nim && \ + nim c -d:miniupnp_protocol=natpmp --mm:orc --threads:on -o:tests/test_natpmp_orc tests/test_plum.nim && \ + nim c -d:miniupnp_protocol=natpmp --mm:refc --threads:on -o:tests/test_natpmp_refc tests/test_plum.nim COPY tests/docker-entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/tests/test_plum.nim b/tests/test_plum.nim index fc302fc..a5fdb6e 100644 --- a/tests/test_plum.nim +++ b/tests/test_plum.nim @@ -7,7 +7,11 @@ # those terms. import std/os +import std/osproc +import std/strutils +import posix import unittest2 +import std/atomics import chronos import libplum/plum import libplum/libplum @@ -36,59 +40,120 @@ suite "plum": check not hasMapping(999) const miniupnp_protocol {.strdefine.} = "" + # The flag is passed by the Docker / Podman container. -# natpmp: miniupnpd compiled without PCP — PCP probes time out and libplum -# must fall back to NAT-PMP on its own (tests the silent-timeout fallback fix). when miniupnp_protocol != "": - const expectedMappingProtocol = - when miniupnp_protocol == "pcp": - PCP - elif miniupnp_protocol == "natpmp": - NatPmp - else: - UPnP + const + discoverMs = 2000 + recheckMs = 500 + upnpRestartMs = discoverMs + 2000 + natpmpEpochMs = 3000 + renewalPollMs = 100 + renewalTimeoutMs = 10_000 + + let mappingTimeout = seconds(40) + + let logLevel = + if getEnv("LIBPLUM_VERBOSE") == "1": PLUM_LOG_LEVEL_VERBOSE + else: PLUM_LOG_LEVEL_NONE + + var gRenewed: Atomic[bool] + + proc onMappingRenewal(state: PlumState, mapping: PlumMapping) {.cdecl, raises: [], gcsafe.} = + if state == Success: + gRenewed.store(true) + + proc stopMiniupnpd(proto: string) = + let pidFile = "/tmp/plum-test/miniupnpd-" & proto & ".pid" + let pid = readFile(pidFile).strip().parseInt() + + discard posix.kill(pid.Pid, SIGKILL) + + proc waitRenewal(): bool = + for _ in 0 ..< (renewalTimeoutMs div renewalPollMs): + if gRenewed.load(): + return true + sleep(renewalPollMs) + false + + proc startMiniupnpd(proto: string) = + let pidFile = "/tmp/plum-test/miniupnpd-" & proto & ".pid" + let confFile = "/tmp/plum-test/miniupnpd-" & proto & ".conf" + let logFile = "/tmp/plum-test/miniupnpd-" & proto & ".log" + let binary = + if proto == "natpmp": "miniupnpd-natpmponly" else: "miniupnpd" + + discard execShellCmd( + binary & " -d -f " & confFile & " >> " & logFile & " 2>&1 & echo $! > " & pidFile + ) suite "plum - " & miniupnp_protocol & " using miniupnp": test "createMapping TCP and destroyMapping": - let logLevel = - if getEnv("LIBPLUM_VERBOSE") == "1": - PLUM_LOG_LEVEL_VERBOSE - else: - PLUM_LOG_LEVEL_NONE - check init(discoverTimeout = 15000, logLevel = logLevel).isOk() + require init(discoverTimeout = discoverMs, logLevel = logLevel).isOk() + defer: discard cleanup() - let r = waitFor createMapping(TCP, 8101, timeout = seconds(40)) - check r.isOk() - if r.isOk(): - let res = r.value - check res.mapping.externalPort > 0 - check res.mapping.externalHost.len > 0 - check res.mapping.mappingProtocol == expectedMappingProtocol - check hasMapping(res.id) - if getEnv("TEST_VERBOSE") == "1": - echo miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" & - $res.mapping.externalPort - destroyMapping(res.id) + let r = waitFor createMapping(TCP, 8101, timeout = mappingTimeout) + require r.isOk() + let res = r.value + defer: destroyMapping(res.id) - discard cleanup() + checkpoint miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort + check res.mapping.externalPort > 0 + check res.mapping.externalHost.len > 0 + check hasMapping(res.id) + + when miniupnp_protocol == "upnp": + check res.mapping.mappingProtocol == UPnP + elif miniupnp_protocol == "natpmp": + check res.mapping.mappingProtocol == NatPmp + else: + check res.mapping.mappingProtocol == PCP test "createMapping UDP and destroying": - let logLevel = - if getEnv("LIBPLUM_VERBOSE") == "1": - PLUM_LOG_LEVEL_VERBOSE - else: - PLUM_LOG_LEVEL_NONE - check init(discoverTimeout = 2000, logLevel = logLevel).isOk() + require init(discoverTimeout = discoverMs, logLevel = logLevel).isOk() + defer: discard cleanup() - let r = waitFor createMapping(UDP, 8090, timeout = seconds(40)) - check r.isOk() - if r.isOk(): - let res = r.value - check res.mapping.externalPort > 0 - check res.mapping.mappingProtocol == expectedMappingProtocol - if getEnv("TEST_VERBOSE") == "1": - echo miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" & - $res.mapping.externalPort - destroyMapping(res.id) + let r = waitFor createMapping(UDP, 8090, timeout = mappingTimeout) + require r.isOk() - discard cleanup() + let res = r.value + defer: destroyMapping(res.id) + + checkpoint miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort + check res.mapping.externalPort > 0 + + when miniupnp_protocol == "upnp": + check res.mapping.mappingProtocol == UPnP + elif miniupnp_protocol == "natpmp": + check res.mapping.mappingProtocol == NatPmp + else: + check res.mapping.mappingProtocol == PCP + + test "mapping is renewed after miniupnpd restart": + require init( + discoverTimeout = discoverMs, logLevel = logLevel, + recheckPeriod = recheckMs, + ).isOk() + defer: discard cleanup() + + gRenewed.store(false) + + let r = waitFor createMapping( + TCP, 8301, timeout = mappingTimeout, onStateChange = onMappingRenewal + ) + require r.isOk() + let res = r.value + defer: destroyMapping(res.id) + + when miniupnp_protocol == "natpmp": + # Let the server epoch advance so the restart is detectable + sleep(natpmpEpochMs) + + stopMiniupnpd(miniupnp_protocol) + + when miniupnp_protocol == "upnp": + sleep(upnpRestartMs) + + startMiniupnpd(miniupnp_protocol) + + check waitRenewal() diff --git a/vendor/libplum b/vendor/libplum index f6d2c12..b74757f 160000 --- a/vendor/libplum +++ b/vendor/libplum @@ -1 +1 @@ -Subproject commit f6d2c12a9f347dcd6958b97c9df6418c43d975f2 +Subproject commit b74757f88c2a98ea905f5b81ad040232bc019c35 From 5c28d21f208215bb6a9a25ac8da2ea4e6046dfc2 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Tue, 26 May 2026 14:47:35 +0400 Subject: [PATCH 2/2] Format code --- tests/test_plum.nim | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/test_plum.nim b/tests/test_plum.nim index a5fdb6e..e8dfc12 100644 --- a/tests/test_plum.nim +++ b/tests/test_plum.nim @@ -54,12 +54,13 @@ when miniupnp_protocol != "": let mappingTimeout = seconds(40) let logLevel = - if getEnv("LIBPLUM_VERBOSE") == "1": PLUM_LOG_LEVEL_VERBOSE - else: PLUM_LOG_LEVEL_NONE + if getEnv("LIBPLUM_VERBOSE") == "1": PLUM_LOG_LEVEL_VERBOSE else: PLUM_LOG_LEVEL_NONE var gRenewed: Atomic[bool] - proc onMappingRenewal(state: PlumState, mapping: PlumMapping) {.cdecl, raises: [], gcsafe.} = + proc onMappingRenewal( + state: PlumState, mapping: PlumMapping + ) {.cdecl, raises: [], gcsafe.} = if state == Success: gRenewed.store(true) @@ -80,8 +81,7 @@ when miniupnp_protocol != "": let pidFile = "/tmp/plum-test/miniupnpd-" & proto & ".pid" let confFile = "/tmp/plum-test/miniupnpd-" & proto & ".conf" let logFile = "/tmp/plum-test/miniupnpd-" & proto & ".log" - let binary = - if proto == "natpmp": "miniupnpd-natpmponly" else: "miniupnpd" + let binary = if proto == "natpmp": "miniupnpd-natpmponly" else: "miniupnpd" discard execShellCmd( binary & " -d -f " & confFile & " >> " & logFile & " 2>&1 & echo $! > " & pidFile @@ -90,14 +90,17 @@ when miniupnp_protocol != "": suite "plum - " & miniupnp_protocol & " using miniupnp": test "createMapping TCP and destroyMapping": require init(discoverTimeout = discoverMs, logLevel = logLevel).isOk() - defer: discard cleanup() + defer: + discard cleanup() let r = waitFor createMapping(TCP, 8101, timeout = mappingTimeout) require r.isOk() let res = r.value - defer: destroyMapping(res.id) + defer: + destroyMapping(res.id) - checkpoint miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort + checkpoint miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" & + $res.mapping.externalPort check res.mapping.externalPort > 0 check res.mapping.externalHost.len > 0 check hasMapping(res.id) @@ -111,15 +114,18 @@ when miniupnp_protocol != "": test "createMapping UDP and destroying": require init(discoverTimeout = discoverMs, logLevel = logLevel).isOk() - defer: discard cleanup() + defer: + discard cleanup() let r = waitFor createMapping(UDP, 8090, timeout = mappingTimeout) require r.isOk() let res = r.value - defer: destroyMapping(res.id) + defer: + destroyMapping(res.id) - checkpoint miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort + checkpoint miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" & + $res.mapping.externalPort check res.mapping.externalPort > 0 when miniupnp_protocol == "upnp": @@ -131,10 +137,11 @@ when miniupnp_protocol != "": test "mapping is renewed after miniupnpd restart": require init( - discoverTimeout = discoverMs, logLevel = logLevel, - recheckPeriod = recheckMs, - ).isOk() - defer: discard cleanup() + discoverTimeout = discoverMs, logLevel = logLevel, recheckPeriod = recheckMs + ) + .isOk() + defer: + discard cleanup() gRenewed.store(false) @@ -143,7 +150,8 @@ when miniupnp_protocol != "": ) require r.isOk() let res = r.value - defer: destroyMapping(res.id) + defer: + destroyMapping(res.id) when miniupnp_protocol == "natpmp": # Let the server epoch advance so the restart is detectable