From 8dc964e0d1dadcbdedf30079c07d8f761ebe9204 Mon Sep 17 00:00:00 2001 From: LiranAbir Date: Thu, 24 Sep 2026 16:02:36 +0300 Subject: [PATCH] Unescape escaped semicolons in a plain moduleArgs string fix_modulesArgs computes the unescaped parts, then discards them and reuses the raw input for the single-part case, so the backslash of an escaped semicolon reaches the module verbatim. A module that takes a semicolon- separated value then gets an invalid one and fails to load, which stops the server: the caller sees only "Connection refused" against the Env it just built, and a config rejected this early leaves an empty logfile behind. Regression from #243, which introduced the plain-string branch. The split already returns the unescaped value, so take it. Co-Authored-By: Claude Opus 5 --- RLTest/utils.py | 6 ++++-- tests/unit/test_utils.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/RLTest/utils.py b/RLTest/utils.py index 9ed3f8d..011bb61 100644 --- a/RLTest/utils.py +++ b/RLTest/utils.py @@ -145,9 +145,11 @@ def fix_modulesArgs(modules, modulesArgs, defaultArgs=None, haveSeqs=True): if type(modulesArgs) == str: parts = split_by_semicolon(modulesArgs) if len(parts) == 1: - # No semicolons - keep as plain string + # No unescaped semicolons - keep as a plain string, but take the split's + # single part rather than the input: the input still carries the backslashes + # of any escaped semicolon, and passing those on to the module is fatal. is_plain_str = True - modulesArgs = [[modulesArgs.strip()]] + modulesArgs = [[parts[0]]] else: # Has semicolons - already split modulesArgs = [parts] diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index af01ad0..f92ed00 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -108,3 +108,15 @@ def test_no_substring_match(self): defaults = [['GC enabled']] result = fix_modulesArgs(['/mod.so'], 'nogc TIMEOUT 80', defaults) self.assertEqual(result, [['nogc TIMEOUT 80 GC enabled']]) + + # 13. Escaped semicolons in a plain string are unescaped, as the module needs the + # bare separator - a backslash reaching it makes the value invalid (MOD-18913) + def test_escaped_semicolons_plain_string(self): + result = fix_modulesArgs(['/mod.so'], r'COMPACTION_POLICY max:1m:1d\;min:10s:1h') + self.assertEqual(result, [['COMPACTION_POLICY max:1m:1d;min:10s:1h']]) + + # 13b. ...and also when defaults are merged into it + def test_escaped_semicolons_plain_string_with_defaults(self): + defaults = [['TIMEOUT 60']] + result = fix_modulesArgs(['/mod.so'], r'COMPACTION_POLICY max:1m:1d\;min:10s:1h', defaults) + self.assertEqual(result, [['COMPACTION_POLICY max:1m:1d;min:10s:1h TIMEOUT 60']])