Some more stabilization of unittests
Some checks are pending
/ build (macos-latest, 3.8) (push) Waiting to run
/ build (ubuntu-latest, pypy-2.7) (push) Waiting to run
/ build (windows-latest, 3.14) (push) Waiting to run

This commit is contained in:
Miroslav Štampar 2026-07-02 22:52:02 +02:00
parent 71d9c6d0f4
commit d60e95ede7
20 changed files with 122 additions and 33 deletions

View file

@ -86,6 +86,7 @@ class _ApiServerCase(unittest.TestCase):
"""
def setUp(self):
self._saved_batch = conf.batch
conf.batch = True
# snapshot mutated globals
@ -122,6 +123,7 @@ class _ApiServerCase(unittest.TestCase):
api.DataStore.username = self._saved["username"]
api.DataStore.password = self._saved["password"]
api.Database.filepath = self._saved["filepath"]
conf.batch = self._saved_batch
def _new_task(self):
code, parsed, _ = _wsgi_call("GET", "/task/new")

View file

@ -28,14 +28,26 @@ from lib.core.bigarray import BigArray
N = 5000
_SPILLED = []
def _make_spilled():
# tiny chunk_size guarantees many on-disk chunks for N items
ba = BigArray(chunk_size=1024)
for i in range(N):
ba.append("item-%d" % i)
_SPILLED.append(ba) # tracked so tearDownModule closes it (release the on-disk chunk files)
return ba
def tearDownModule():
for ba in _SPILLED:
try:
ba.close()
except Exception:
pass
del _SPILLED[:]
class TestSpill(unittest.TestCase):
def test_actually_spilled_to_disk(self):
ba = _make_spilled()

View file

@ -53,9 +53,10 @@ _CONF_KEYS = (
"notString", "regexp", "regex", "dummy", "offline", "skipWaf", "data",
"hashDB", "cj", "cookie", "dropSetCookie", "httpHeaders", "proxy", "tor",
"tamper", "timeout", "retries", "textOnly", "ignoreCode", "disablePrecon",
"ipv6", "multipleTargets", "level", "base64Parameter", "batch",
"ipv6", "multipleTargets", "level", "base64Parameter", "batch", "code", "titles",
)
_KB_KEYS = (
"pageTemplate", "negativeLogic",
"heavilyDynamic", "dynamicParameter", "originalPage", "originalPageTime",
"originalCode", "ignoreCasted", "heuristicMode", "disableHtmlDecoding",
"heuristicTest", "heuristicPage", "heuristicCode", "pageStable",

View file

@ -1320,10 +1320,9 @@ class TestCommonChunkSplit(unittest.TestCase):
random.choice, random.randint, random.sample, random.seed = _saved
def test_chunk_split_terminator(self):
import random
from lib.core.common import chunkSplitPostData
random.seed(123)
# regardless of content, the chunked stream must end with the zero-length terminator
# (assertion is seed-independent, so don't touch the global RNG)
self.assertTrue(chunkSplitPostData("abc").endswith("0\r\n\r\n"))

View file

@ -25,6 +25,7 @@ logic fails the test. No live target / network / DBMS involved.
import os
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -108,7 +109,7 @@ class TestGenericFilesystem(_FsBase):
def test_fileEncode_reads_then_encodes(self):
# fileEncode must read the file bytes and delegate to fileContentEncode
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_fe_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_fe_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"hello")
try:
@ -138,7 +139,7 @@ class TestGenericFilesystem(_FsBase):
# MySQL builds LENGTH(LOAD_FILE('<remote>')) and compares to local size.
set_dbms("MySQL")
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_cl_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"12345") # 5 bytes
captured = {}
@ -159,7 +160,7 @@ class TestGenericFilesystem(_FsBase):
def test_checkFileLength_size_differs(self):
set_dbms("MySQL")
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl2_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_cl2_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"12345") # local 5
self.patch(self.module.inject, "getValue", lambda q, *a, **k: "9")
@ -176,7 +177,7 @@ class TestGenericFilesystem(_FsBase):
# OPENROWSET-building branch runs in isolation.
set_dbms("Microsoft SQL Server")
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl3_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_cl3_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"ABCD") # 4 bytes
stacked = []
@ -205,7 +206,7 @@ class TestGenericFilesystem(_FsBase):
# non-positive remote size -> treated as "not written" -> sameFile False
set_dbms("MySQL")
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl4_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_cl4_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"x")
self.patch(self.module.inject, "getValue", lambda q, *a, **k: None)
@ -282,7 +283,7 @@ class TestGenericFilesystem(_FsBase):
# stackedWriteFile and return its result.
set_dbms("MySQL")
path = os.path.join(
os.environ.get("TMPDIR", "/tmp"), "sqlmap_wf_%d.bin" % os.getpid())
tempfile.gettempdir(), "sqlmap_wf_%d.bin" % os.getpid())
with open(path, "wb") as f:
f.write(b"data")
calls = {}

View file

@ -16,6 +16,21 @@ bootstrap()
import lib.techniques.ldap.inject as ldap
# several setUps here write these conf keys without restoring them; snapshot/restore at the module
# boundary so they can't leak into later test modules (order-dependent flakiness)
_LDAP_CONF_KEYS = ("parameters", "paramDict", "skipUrlEncode", "cookieDel")
_saved_conf = {}
def setUpModule():
from lib.core.data import conf
for k in _LDAP_CONF_KEYS:
_saved_conf[k] = conf.get(k)
def tearDownModule():
from lib.core.data import conf
for k, v in _saved_conf.items():
conf[k] = v
# --- Helpers ----------------------------------------------------------------
SENTINEL = ldap.SENTINEL

View file

@ -18,6 +18,21 @@ bootstrap()
import lib.techniques.nosql.inject as ni
# several setUps here write these conf keys without restoring them; snapshot/restore at the module
# boundary so they can't leak into later test modules (order-dependent flakiness)
_NOSQL_CONF_KEYS = ("parameters", "paramDict", "timeSec", "cookieDel")
_saved_conf = {}
def setUpModule():
from lib.core.data import conf
for k in _NOSQL_CONF_KEYS:
_saved_conf[k] = conf.get(k)
def tearDownModule():
from lib.core.data import conf
for k, v in _saved_conf.items():
conf[k] = v
SECRET = "S3cr3t_9"
MATCH = "<html><body>Welcome user; rows: alpha, bravo, charlie</body></html>"
NOMATCH = "<html><body>Invalid credentials; no rows</body></html>"

View file

@ -60,6 +60,7 @@ class TestExtractTextTagContent(unittest.TestCase):
class TestParseSqliteTableSchema(unittest.TestCase):
def setUp(self):
self.addCleanup(setattr, kb.data, "cachedColumns", kb.data.get("cachedColumns"))
kb.data.cachedColumns = {}
def _cols(self):

View file

@ -28,6 +28,24 @@ from lib.core.settings import (JSON_RECOGNITION_REGEX, JSON_LIKE_RECOGNITION_REG
# change there is reflected here too.
MARK = CUSTOM_INJECTION_MARK_CHAR
# the _drive_* helpers set sticky conf/kb flags (notably conf.hpp, which changes queryPage
# behaviour) without restoring them; snapshot/restore at the module boundary so they can't leak
_PM_CONF_KEYS = ("hpp", "skipUrlEncode", "method", "paramDel", "url", "data", "parameters", "paramDict")
_PM_KB_KEYS = ("tamperFunctions", "postHint", "customInjectionMark", "postUrlEncode", "postSpaceToPlus", "processUserMarks")
_pm_saved = {}
def setUpModule():
from lib.core.data import conf, kb
for k in _PM_CONF_KEYS:
_pm_saved[("conf", k)] = conf.get(k)
for k in _PM_KB_KEYS:
_pm_saved[("kb", k)] = kb.get(k)
def tearDownModule():
from lib.core.data import conf, kb
for (scope, k), v in _pm_saved.items():
(conf if scope == "conf" else kb)[k] = v
def classify(d):
if re.search(JSON_RECOGNITION_REGEX, d):

View file

@ -83,7 +83,10 @@ class TestPurge(unittest.TestCase):
nonempty = [p for p in survivors if os.path.getsize(p) > 0]
self.assertEqual(nonempty, [], msg="files were not truncated to zero: %r" % nonempty)
blob = b"".join(open(p, "rb").read() for p in survivors)
blob = b""
for p in survivors:
with open(p, "rb") as fh:
blob += fh.read()
for secret in plaintexts.values():
self.assertNotIn(secret.encode("utf-8"), blob,
msg="original plaintext %r survived the purge" % secret)

View file

@ -191,6 +191,7 @@ class TestEntityConversion(unittest.TestCase):
class TestCustomEntitydefs(unittest.TestCase):
def test_custom_entity(self):
p = RecordingParser()
p.entitydefs = dict(p.entitydefs) # shadow the shared SGMLParser class dict so 'copy' doesn't leak process-wide
p.entitydefs["copy"] = "\xa9"
p.feed("&copy;")
p.close()

View file

@ -393,8 +393,7 @@ class TestExecuteCommand(unittest.TestCase):
def tearDown(self):
ssti._send = self.original_send
if self.original_dumper is not None:
ssti.conf.dumper = self.original_dumper
ssti.conf.dumper = self.original_dumper # restore unconditionally (was None -> don't leak the mock dumper)
def test_error_page_skipped(self):
"""RCE payload that triggers a template error is skipped; next payload tried."""

View file

@ -26,6 +26,20 @@ bootstrap()
from lib.core.common import parseTargetUrl
from lib.core.data import conf
_TARGETURL_KEYS = ("url", "hostname", "port", "scheme", "path")
_saved = {}
def setUpModule():
for k in _TARGETURL_KEYS:
_saved[k] = conf.get(k)
def tearDownModule():
# parseTargetUrl() writes these onto the global conf singleton; restore so it can't leak to later modules
for k, v in _saved.items():
conf[k] = v
def _parse(url):
conf.url = url

View file

@ -46,6 +46,7 @@ class TestFilterStringValue(unittest.TestCase):
class TestParseFilePaths(unittest.TestCase):
def setUp(self):
self.addCleanup(setattr, kb, "absFilePaths", kb.get("absFilePaths"))
kb.absFilePaths = set()
def test_unix_paths_from_php_error(self):

View file

@ -38,6 +38,7 @@ class TestThreadData(unittest.TestCase):
# ATTRIBUTE STATE is per-thread. Verify both: same object, independent state.
main = T.getCurrentThreadData()
self.assertIs(main, T.getCurrentThreadData()) # stable within a thread
self.addCleanup(main.reset) # don't leak the main thread's mutated state to later tests
main.retriesCount = 111