PythonTestHelper
Namespace: UTILSLIB · Library: Utilities Library
#include <utils/python_test_helper.h>
class UTILSLIB::PythonTestHelper
Test-frame convenience class for cross-validating MNE-CPP outputs against MNE-Python reference implementations.
Usage inside a QTest test:
PythonTestHelper py;
if (!py.isAvailable()) {
QSKIP("Python/MNE-Python not available");
}
// Run a Python snippet that computes a reference value
auto result = py.eval("import mne; print(mne.io.read_info('test.fif')['nchan'])");
QVERIFY(result.success);
QCOMPARE(result.stdOut.trimmed().toInt(), expectedNchan);
// Or run a script and parse a numeric output
double pyValue = py.evalDouble("import numpy as np; print(np.linalg.norm(np.ones(10)))");
QVERIFY(qAbs(cppValue - pyValue) < 1e-6);
When MNE-Python is not installed, tests should QSKIP gracefully — but never silently. Use the GUARD_PYTHON / GUARD_PYTHON_PACKAGE macros to ensure skips are intentional and visible. When the environment variable MNE_REQUIRE_PYTHON=true is set (e.g. in CI), those macros QFAIL instead of QSKIP, so a missing Python installation is treated as a hard error.
Test helper for MNE-Python cross-validation.
PythonTestHelper helper; // oracle access for tests
const MatrixXd fromNumpy = helper.evalMatrix("print('1 2'); print('3 4')"); // one line per row
Public Methods
PythonTestHelper()
Constructs a PythonTestHelper with auto-detected Python.
isAvailable()
Check if Python is available and MNE-Python is importable.
Returns:
- bool — True if both python3 and the 'mne' package are available.
isPythonAvailable()
Check if Python is available (without checking for mne package).
Returns:
- bool — True if python3 is reachable.
hasPackage(packageName)
Check if a specific Python package is importable.
Parameters:
- packageName : const QString & Package name (e.g. "numpy", "scipy", "mne").
Returns:
- bool — True if the import succeeds.
eval(code, timeoutMs)
Run inline Python code and return the result.
Parameters:
-
code : const QString & Python code to execute via
python -c. -
timeoutMs : int Timeout in milliseconds (-1 = no limit).
Returns:
- PythonRunnerResult —
PythonRunnerResultwith captured stdout/stderr.
evalDouble(code, ok, timeoutMs)
Run inline Python code that prints a single double value, and parse it.
Parameters:
-
code : const QString & Python code whose stdout is a single number.
-
ok : *bool ** Set to true on success, false on parse failure.
-
timeoutMs : int Timeout in milliseconds.
Returns:
- double — The parsed double value, or 0.0 on failure.
evalVector(code, ok, timeoutMs)
Run inline Python code that prints a flat array of doubles (one per line or space-separated), and parse it into an Eigen VectorXd.
Parameters:
-
code : const QString & Python code whose stdout is numeric values.
-
ok : *bool ** Set to true on success.
-
timeoutMs : int Timeout in milliseconds.
Returns:
- Eigen::VectorXd — Eigen VectorXd, or empty vector on failure.
evalMatrix(code, ok, timeoutMs)
Run inline Python code that prints a matrix (one row per line, space-separated values), and parse it into an Eigen MatrixXd.
Parameters:
-
code : const QString & Python code whose stdout is a space-separated matrix.
-
ok : *bool ** Set to true on success.
-
timeoutMs : int Timeout in milliseconds.
Returns:
- Eigen::MatrixXd — Eigen MatrixXd, or empty matrix on failure.
runScript(scriptPath, args, timeoutMs)
Run a Python script file and return the result.
Parameters:
-
scriptPath : const QString & Path to the .py file.
-
args : const QStringList & Arguments forwarded to the script.
-
timeoutMs : int Timeout in milliseconds.
Returns:
evalMatrixViaFile(code, outputFilePath, ok, timeoutMs)
Run Python code that writes a matrix to outputFilePath (e.g.
via numpy.savetxt), then read the result back as an Eigen MatrixXd.
This is preferred over evalMatrix() for large matrices or when full double precision is required, because it avoids stdout parsing.
Example:
QString code = QString(
"import numpy as np\n"
"cov = np.eye(3)\n"
"np.savetxt('%1', cov, fmt='%%.17e')\n"
).arg(outPath);
bool ok;
auto mat = helper.evalMatrixViaFile(code, outPath, &ok);
Parameters:
-
code : const QString & Python code to execute.
-
outputFilePath : const QString & Path where Python writes the matrix.
-
ok : *bool ** Set to true on success.
-
timeoutMs : int Timeout in milliseconds.
Returns:
- Eigen::MatrixXd — Parsed MatrixXd, or empty matrix on failure.
Static Methods
testDataPath()
Get the standard path to mne-cpp-test-data from the test binary location.
Returns:
- QString — Absolute path to the test data directory.
isPythonRequired()
Check whether the environment demands Python availability.
When MNE_REQUIRE_PYTHON=true is set, test guards should QFAIL instead of QSKIP, ensuring skips are never silent in CI.
Returns:
- bool — True if MNE_REQUIRE_PYTHON environment variable is "true" (or "1").
writeMatrix(filePath, mat)
Write an Eigen matrix to a text file with full double precision.
The format is one row per line, space-separated values, using %.17e notation — compatible with Python's numpy.loadtxt().
Parameters:
-
filePath : const QString & Output file path.
-
mat : const Eigen::MatrixXd & Matrix to write.
Returns:
- bool — True on success.
readMatrix(filePath, ok)
Read an Eigen matrix from a text file (space-separated values, one row per line).
Compatible with numpy.savetxt() output.
Parameters:
-
filePath : const QString & Input file path.
-
ok : *bool ** Set to true on success, false on failure.
Returns:
- Eigen::MatrixXd — Parsed MatrixXd, or empty matrix on failure.
Example
- Full source:
src/examples/ex_utils/main.cpp - Build target:
ex_utils(runs under CTest)
Used in examples
ex_utils
Utilities library: buffers, observers, logging, file formats, montages, layouts and reports.
const PythonRunnerResult run = python.runCode("import sys\nprint('[progress] 50% half')\nprint(sum(map(int, sys.argv[1:])))", {"2", "3"});
PythonTestHelper helper; // oracle access for tests
const MatrixXd fromNumpy = helper.evalMatrix("print('1 2'); print('3 4')"); // one line per row
if (python.isPythonAvailable()) {
ok &= expect(run.success && run.stdOut.trimmed().endsWith("5") && progress == QStringList({"50 half"}) && fromNumpy == (Matrix2d() << 1, 2, 3, 4).finished(),
"PythonRunner passes arguments and parses [progress] lines; PythonTestHelper reads a matrix");
} else {
qInfo().noquote() << " skip PythonRunner (python3 not found)";
}
Authors of this file
- Christoph Dinh <christoph.dinh@mne-cpp.org>