nim-libplum/tests/test_plum.nim

215 lines
6.1 KiB
Nim
Raw Normal View History

2026-05-14 12:23:10 +04:00
# Copyright (c) 2026 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
2026-05-18 11:43:28 +04:00
import std/os
2026-05-26 14:28:09 +04:00
import std/osproc
import std/strutils
import posix
2026-05-14 12:23:10 +04:00
import unittest2
2026-05-26 14:28:09 +04:00
import std/atomics
2026-05-14 12:23:10 +04:00
import chronos
import libplum/plum
2026-05-18 15:16:27 +04:00
import libplum/libplum
2026-05-14 12:23:10 +04:00
2026-06-01 17:22:10 +04:00
const miniupnp_protocol {.strdefine.} = ""
2026-05-14 12:23:10 +04:00
suite "plum":
test "init and cleanup":
let r = init()
check r.isOk()
let c = cleanup()
check c.isOk()
test "double cleanup returns error":
discard init()
discard cleanup()
let c = cleanup()
check c.isErr()
test "getLocalAddress after init":
discard init()
let r = getLocalAddress()
check r.isOk()
check r.value.len > 0
discard cleanup()
2026-05-14 16:40:10 +04:00
test "hasMapping returns false for unknown id":
check not hasMapping(999)
2026-06-01 17:22:10 +04:00
# Only valid where no NAT device answers; the integration container runs
# miniupnpd, which would make the mapping succeed before the timeout.
when miniupnp_protocol == "":
test "createMapping times out without a NAT device":
require init().isOk()
defer:
discard cleanup()
let r = waitFor createMapping(TCP, 8101, timeout = milliseconds(50))
check r.isErr()
2026-06-01 18:55:06 +04:00
test "cleanup while a createMapping is pending completes cleanly":
require init().isOk()
defer:
discard cleanup()
# No NAT device answers, so the mapping stays PENDING until we cleanup.
let fut = createMapping(TCP, 8501, timeout = milliseconds(200))
waitFor sleepAsync(50.milliseconds)
discard cleanup()
let r = waitFor fut
check r.isErr()
check activeMappingCount() == 0
2026-06-01 17:22:10 +04:00
test "destroyMapping is a no-op on an unknown id":
require init().isOk()
defer:
discard cleanup()
destroyMapping(999.cint)
check not hasMapping(999)
2026-05-26 14:28:09 +04:00
2026-05-18 15:29:14 +04:00
# The flag is passed by the Docker / Podman container.
when miniupnp_protocol != "":
2026-05-26 14:28:09 +04:00
const
discoverMs = 2000
recheckMs = 500
upnpRestartMs = discoverMs + 2000
natpmpEpochMs = 3000
renewalPollMs = 100
renewalTimeoutMs = 10_000
let mappingTimeout = seconds(40)
let logLevel =
2026-06-01 18:33:20 +04:00
if getEnv("LIBPLUM_VERBOSE") == "1": PlumLogLevel.Verbose else: PlumLogLevel.None
2026-05-26 14:28:09 +04:00
var gRenewed: Atomic[bool]
2026-05-26 14:47:35 +04:00
proc onMappingRenewal(
state: PlumState, mapping: PlumMapping
) {.cdecl, raises: [], gcsafe.} =
2026-05-26 14:28:09 +04:00
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"
2026-05-26 14:47:35 +04:00
let binary = if proto == "natpmp": "miniupnpd-natpmponly" else: "miniupnpd"
2026-05-26 14:28:09 +04:00
discard execShellCmd(
binary & " -d -f " & confFile & " >> " & logFile & " 2>&1 & echo $! > " & pidFile
)
2026-05-18 15:16:27 +04:00
2026-05-18 11:43:28 +04:00
suite "plum - " & miniupnp_protocol & " using miniupnp":
test "createMapping TCP and destroyMapping":
2026-06-01 18:33:20 +04:00
require init(discoverTimeout = discoverMs.int32, logLevel = logLevel).isOk()
2026-05-26 14:47:35 +04:00
defer:
discard cleanup()
2026-05-26 14:28:09 +04:00
let r = waitFor createMapping(TCP, 8101, timeout = mappingTimeout)
require r.isOk()
let res = r.value
2026-05-26 14:47:35 +04:00
checkpoint miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" &
$res.mapping.externalPort
2026-05-26 14:28:09 +04:00
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
2026-05-14 12:23:10 +04:00
2026-06-01 17:22:10 +04:00
destroyMapping(res.id)
check not hasMapping(res.id)
# second destroy on a real id must be a safe no-op
destroyMapping(res.id)
2026-05-18 11:43:28 +04:00
test "createMapping UDP and destroying":
2026-06-01 18:33:20 +04:00
require init(discoverTimeout = discoverMs.int32, logLevel = logLevel).isOk()
2026-05-26 14:47:35 +04:00
defer:
discard cleanup()
2026-05-26 14:28:09 +04:00
let r = waitFor createMapping(UDP, 8090, timeout = mappingTimeout)
require r.isOk()
let res = r.value
2026-05-26 14:47:35 +04:00
defer:
destroyMapping(res.id)
2026-05-26 14:28:09 +04:00
2026-05-26 14:47:35 +04:00
checkpoint miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" &
$res.mapping.externalPort
2026-05-26 14:28:09 +04:00
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(
2026-06-01 18:38:47 +04:00
discoverTimeout = discoverMs.int32,
logLevel = logLevel,
recheckPeriod = recheckMs.int32,
2026-05-26 14:47:35 +04:00
)
.isOk()
defer:
discard cleanup()
2026-05-26 14:28:09 +04:00
gRenewed.store(false)
let r = waitFor createMapping(
TCP, 8301, timeout = mappingTimeout, onStateChange = onMappingRenewal
)
require r.isOk()
let res = r.value
2026-05-26 14:47:35 +04:00
defer:
destroyMapping(res.id)
2026-05-26 14:28:09 +04:00
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()
2026-06-01 17:22:10 +04:00
test "cleanup releases active mappings":
2026-06-01 18:33:20 +04:00
require init(discoverTimeout = discoverMs.int32, logLevel = logLevel).isOk()
2026-06-01 17:22:10 +04:00
let r = waitFor createMapping(TCP, 8401, timeout = mappingTimeout)
require r.isOk()
# no destroyMapping on purpose: cleanup must release everything
check cleanup().isOk()
check activeMappingCount() == 0