Files
qlib/tests/test_tra_example.py
5435523246 fix(security): constrain config-driven code execution (#2340)
* security: constrain config-driven code execution

* fix: address security test and lint failures

* ci: restore dependency and title lint compatibility

* ci: handle current MLflow and Windows OSQP changes

* ci: use lxml release with cross-platform wheels

* fix: defer optional CVXPY solver imports

* style: document intentional lazy import

* ci: skip broken released OSQP on Windows Python 3.8

* test: make MLflow client timing check robust

* ci: retry nondeterministic benchmark smoke test

* fix: cap filelock before fork safety regression

* fix: use filelock release compatible with multiprocessing

* fix(security): restrict operator lookup and validate trusted roots

* fix(security): preserve numeric expressions and legacy model imports

* fix: preserve TRA initialization and document config migration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: preserve safe expression syntax and cache semantics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use explicit scoped trust for file modules

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: satisfy CI lint without weakening trust checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: handle escaped file paths in module refusal errors

Match the repr-formatted module path in refusal messages and cover Windows, UNC, quoted, and newline-containing paths on every platform.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: preserve expression parser error logging

Log ExpressionSyntaxError with its field and traceback before re-raising, while preserving custom operator NameError and SyntaxError handling. Add five regression cases covering logging, exception identity, and failed cache entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: isolate expression logger capture

Capture parser logs directly on qlib.data without propagating to parent and root capture handlers. Restore logger state with monkeypatch and retain exact single-record, traceback, and exception-identity assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): preserve malformed feature boundaries and reject unsupported literals

* test(security): cover malformed dollar-prefixed expressions

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Yang Liu <DG-git-dev@users.noreply.github.com>
2026-10-08 18:15:27 +08:00

52 lines
1.6 KiB
Python

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from pathlib import Path
import runpy
from unittest.mock import Mock, call
import pytest
from ruamel.yaml import YAML
import qlib
TRA_DIR = Path(__file__).resolve().parents[1] / "examples" / "benchmarks" / "TRA"
@pytest.mark.parametrize(
"config_name",
[
"config_alstm.yaml",
"config_alstm_tra.yaml",
"config_alstm_tra_init.yaml",
"config_transformer.yaml",
"config_transformer_tra.yaml",
"config_transformer_tra_init.yaml",
],
)
def test_tra_example_forwards_initialization_config(monkeypatch, config_name):
monkeypatch.chdir(TRA_DIR)
config_path = Path("configs") / config_name
with config_path.open() as stream:
config = YAML(typ="safe", pure=True).load(stream)
assert "trusted" not in config["qlib_init"]
assert "trusted_module_roots" not in config["qlib_init"]
for component in ("model", "dataset"):
assert config["task"][component]["trusted"] is True
main = runpy.run_path(str(TRA_DIR / "example.py"))["main"]
initialize = Mock()
dataset, model = Mock(), Mock()
factory = Mock(side_effect=[dataset, model])
monkeypatch.setattr(qlib, "init", initialize)
monkeypatch.setitem(main.__globals__, "init_instance_by_config", factory)
main(seed=42, config_file=str(config_path))
initialize.assert_called_once_with(**config["qlib_init"])
config["task"]["model"]["kwargs"]["seed"] = 42
assert factory.call_args_list == [call(config["task"]["dataset"]), call(config["task"]["model"])]
model.fit.assert_called_once_with(dataset)