Source code for test_swigMemberLeak

#
#  ISC License
#
#  Copyright (c) 2026, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#
#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

#
#   Unit Test Script
#   Module Name:        utilitiesSelfCheck (SWIG member wrapping)
#   Author:             robotrocketscience (https://github.com/robotrocketscience)
#   Creation Date:      July 1, 2026
#
#   Purpose:
#   Regression test for issue #422. Several modules exposed internal C++ members
#   (a std::pair-keyed map, the BSKLogger, thruster operating state, a
#   message-reader vector, IMU noise-model helpers, an Eigen-typed struct member)
#   that SWIG wrapped without a visible destructor, so reading them from Python
#   leaked an un-destructed proxy ("swig/python detected a memory leak of type
#   ...").  Each affected module is now instantiated in an isolated subprocess and
#   all of its attributes are read; the test fails if SWIG reports any such leak.
#

import subprocess
import sys

import pytest

# (import path, class name) for fixed module classes and affected wrapped data classes.
FIXED_MODULES = [
    ("Basilisk.simulation.spacecraft", "Spacecraft"),                                 # DynParamManager::sharedNoiseMap, bskLogger
    ("Basilisk.simulation.imuSensor", "ImuSensor"),                                   # Discretize/Saturate helpers
    ("Basilisk.simulation.sphericalHarmonicsGravityModel", "SphericalHarmonicsGravityModel"),  # bskLogger
    ("Basilisk.simulation.THRSimConfig", "THRSimConfig"),                             # THROperation ThrustOps
    ("Basilisk.simulation.dataFileToViz", "DataFileToViz"),                           # dataFileToViz module class
    ("Basilisk.simulation.dataFileToViz", "ThrClusterMap"),                           # Eigen::Vector3d thrOffset
    ("Basilisk.simulation.dataFileToViz", "RWConfigLogMsgPayload"),                   # RWModels enum in payload
    ("Basilisk.fswAlgorithms.smallBodyNavEKF", "SmallBodyNavEKF"),                    # ReadFunctor vector
    # BSpline data sets
    (
        "Basilisk.fswAlgorithms.constrainedAttitudeManeuver",
        "ConstrainedAttitudeManeuver",
    ),
    ("Basilisk.simulation.ReactionWheelPower", "RWConfigLogMsgPayload"),              # RWModels enum in payload
    ("Basilisk.simulation.motorThermal", "RWConfigLogMsgPayload"),                    # RWModels enum in payload
    ("Basilisk.simulation.linkBudget", "AttenuationLookupTable"),                     # std::vector<double> in lookup struct
    ("Basilisk.simulation.simpleInstrument", "DataStorageStatusMsgPayload"),          # std::vector<double> storedData
]

# Worker: import the module, instantiate the class, read every public attribute,
# force garbage collection. SWIG prints the leak warning to stdout/stderr when an
# un-destructed proxy is collected. Runs in a fresh interpreter per module so the
# parent test is immune to any process-level fault.
_WORKER = (
    "import gc, importlib, sys\n"
    "mod = importlib.import_module(sys.argv[1])\n"
    "obj = getattr(mod, sys.argv[2])()\n"
    "for a in dir(obj):\n"
    "    if not a.startswith('__'):\n"
    "        try:\n"
    "            v = getattr(obj, a); del v\n"
    "        except Exception:\n"
    "            pass\n"
    "del obj; gc.collect()\n"
)


[docs] @pytest.mark.parametrize("module_path,class_name", FIXED_MODULES) def test_no_swig_member_leak(module_path, class_name): """Instantiating the module and reading its attributes must not leak a SWIG proxy.""" proc = subprocess.run( [sys.executable, "-c", _WORKER, module_path, class_name], capture_output=True, text=True, timeout=120, ) combined = proc.stdout + proc.stderr assert "detected a memory leak" not in combined, ( f"{module_path}.{class_name} leaked a SWIG proxy:\n{combined}" ) assert proc.returncode == 0, ( f"{module_path}.{class_name} worker exited abnormally (rc={proc.returncode}):\n{combined}" )