eval_proc was started before -d/GUI defines reached gd, so ``-d python_bin=...`` and the GUI ``python_bin`` preference were silently ignored by the very subprocess that runs ``<| ... |>`` evals (and only took effect for later items once the discovery cache had already been seeded with the system interpreter). apply_overrides() is now applied before eval_process_init(), and bins._resolve()'s cache is keyed by (name, override) so a later param.yaml change re-resolves on the next lookup. The validation suite now ships a wrapper (run.sh / run.bat) that creates a dedicated venv in the system temp dir and pins it via ``-d python_bin=...``. A new ``venv`` item asserts the override took effect for both eval_proc and py_func paths, with a ``sys.prefix != sys.base_prefix`` marker to catch the case where the override happens to be a system interpreter (path-equality alone would miss it, the venv's ``bin/python3`` being a symlink to the host). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
287 lines
9.7 KiB
Python
287 lines
9.7 KiB
Python
import os
|
|
from pathlib import Path
|
|
import datetime
|
|
from socket import gethostname
|
|
import ast
|
|
import yaml
|
|
import copy
|
|
|
|
from interpreter.utils.constants import TestItemType as cst
|
|
import api.testium as tm
|
|
import interpreter.utils.globdict as globdict
|
|
import interpreter.utils.settings as prefs
|
|
from interpreter.utils.paths import testium_path
|
|
from interpreter.utils.yaml_load import yaml_load
|
|
from interpreter.utils import clear_recursively
|
|
from runtime.tum_except import ETUMSyntaxError
|
|
from interpreter.utils.params import expanse, eval_func_init
|
|
from interpreter.utils.eval import evaluate
|
|
from interpreter.utils.version import (
|
|
get_version,
|
|
get_testium_version,
|
|
get_modifications,
|
|
)
|
|
|
|
from interpreter.test_items.test_item import TestItem
|
|
from interpreter.test_items.test_item_sleep import TestItemSleep
|
|
from interpreter.test_items.test_item_unittest import TestItemUnittestFile
|
|
from interpreter.test_items.test_item_cycle import TestItemCycle
|
|
from interpreter.test_items.test_item_runtime_plot import TestItemPlot
|
|
from interpreter.test_items.test_item_group import TestItemGroup
|
|
from interpreter.test_items.test_item_git import TestItemGit
|
|
from interpreter.test_items.test_item_py_func import TestItemPyFunc
|
|
from interpreter.test_items.test_item_lua_func import TestItemLuaFunc
|
|
from interpreter.test_items.test_item_let import TestItemLet
|
|
from interpreter.test_items.test_item_check import TestItemCheckValue
|
|
from interpreter.test_items.test_item_json_rpc import TestItemJSON_RPC
|
|
from interpreter.test_items.test_item_value_dialog import TestItemValueDialog
|
|
from interpreter.test_items.test_item_note_dialog import TestItemNoteDialog
|
|
from interpreter.test_items.test_item_image_dialog import TestItemImageDialog
|
|
from interpreter.test_items.test_item_msg_dialog import TestItemMsgDialog
|
|
from interpreter.test_items.test_item_question_dialog import TestItemQuestionDialog
|
|
from interpreter.test_items.test_item_tested_references import TestItemTestedRefsDialog
|
|
from interpreter.test_items.test_item_choices_dialog import TestItemChoicesDialog
|
|
from interpreter.test_items.test_item_console import TestItemConsole
|
|
from interpreter.test_items.test_item_run import TestItemRun
|
|
from interpreter.test_items.test_item_report import TestItemReport
|
|
from interpreter.test_items.test_item_parallel import TestItemParallel, TestItemParallelBranch
|
|
|
|
|
|
def _constants_init():
|
|
cst.TYPE_CONSOLE.item_class = TestItemConsole
|
|
cst.TYPE_CYCLE.item_class = TestItemCycle
|
|
cst.TYPE_PY_FUNCTION.item_class = TestItemPyFunc
|
|
cst.TYPE_LUA_FUNCTION.item_class = TestItemLuaFunc
|
|
cst.TYPE_GIT.item_class = TestItemGit
|
|
cst.TYPE_GRAPH.item_class = TestItemPlot
|
|
cst.TYPE_GROUP.item_class = TestItemGroup
|
|
cst.TYPE_IMAGE_DLG.item_class = TestItemImageDialog
|
|
cst.TYPE_JSON_RPC.item_class = TestItemJSON_RPC
|
|
cst.TYPE_LET.item_class = TestItemLet
|
|
cst.TYPE_CHECK.item_class = TestItemCheckValue
|
|
cst.TYPE_MESSAGE_DLG.item_class = TestItemMsgDialog
|
|
cst.TYPE_NOTE_DLG.item_class = TestItemNoteDialog
|
|
cst.TYPE_QUESTION_DLG.item_class = TestItemQuestionDialog
|
|
cst.TYPE_REFERENCE_DLG.item_class = TestItemTestedRefsDialog
|
|
cst.TYPE_CHOICES_DLG.item_class = TestItemChoicesDialog
|
|
cst.TYPE_REPORT.item_class = TestItemReport
|
|
cst.TYPE_ROOT.item_class = TestItem
|
|
cst.TYPE_RUN.item_class = TestItemRun
|
|
cst.TYPE_SLEEP.item_class = TestItemSleep
|
|
cst.TYPE_UNITTEST.item_class = TestItemUnittestFile
|
|
cst.TYPE_VALUE_DLG.item_class = TestItemValueDialog
|
|
cst.TYPE_PARALLEL.item_class = TestItemParallel
|
|
cst.TYPE_PARALLEL_BRANCH.item_class = TestItemParallelBranch
|
|
|
|
|
|
def locate_report_file(rep_file):
|
|
# report file name treatment
|
|
if rep_file != "":
|
|
if not os.path.isabs(rep_file):
|
|
rep_file = os.path.join(os.getcwd(), rep_file)
|
|
rep_file = os.path.normpath(rep_file)
|
|
if not os.path.exists(os.path.dirname(rep_file)):
|
|
os.makedirs(os.path.dirname(rep_file))
|
|
|
|
return rep_file
|
|
|
|
|
|
def yamltodict(param_file, silent=True):
|
|
# load of the file
|
|
with open(param_file, "r") as fd:
|
|
dp = yaml_load(fd, param_file, yaml.Loader)
|
|
|
|
if dp is None:
|
|
tm.print_info(f"The YAML file '{param_file}' is empty.")
|
|
return
|
|
|
|
# update the global dict with raw data
|
|
globdict.global_dict.update(dp)
|
|
|
|
# Apply variables expansion
|
|
for i in range(10):
|
|
for key, val in dp.items():
|
|
val = expanse(val)
|
|
dp.update({key: val})
|
|
|
|
if not silent:
|
|
if not tm.debug_enabled():
|
|
tm.print_info(f'"{param_file}" loaded.')
|
|
else:
|
|
tm.print_debug(f'"{param_file}" loading:')
|
|
for k, v in dp.items():
|
|
tm.print_debug(f" {k}: {v}")
|
|
tm.print_debug(f"done.")
|
|
|
|
# Finalize the global dict update
|
|
globdict.global_dict.update(dp)
|
|
|
|
|
|
def _feed_gd_with_params(param_file, silent=True):
|
|
test_dir = tm.gd("test_directory")
|
|
# param files pre-processing
|
|
files = []
|
|
for p in param_file:
|
|
if isinstance(p, str):
|
|
files.append(p)
|
|
elif isinstance(p, list):
|
|
for pp in p:
|
|
files.append(pp)
|
|
for p in files:
|
|
if p is None:
|
|
continue
|
|
if not isinstance(p, str):
|
|
raise ETUMSyntaxError(f'Parameter file "{p}" not a file path.')
|
|
p = expanse(p)
|
|
pf = p
|
|
if not os.path.isabs(pf):
|
|
pf = os.path.normpath(os.path.join(test_dir, pf))
|
|
if not os.path.isfile(pf):
|
|
raise ETUMSyntaxError(f'Parameter file "{pf}" not found')
|
|
|
|
ext = os.path.splitext(pf)[1]
|
|
if (ext == ".yaml") or (ext == ".yml"):
|
|
yamltodict(pf, silent)
|
|
else:
|
|
raise ETUMSyntaxError('config files must be "*.yaml" or "*.yml"')
|
|
|
|
|
|
def set_standard_gd_keys(test_name, test_dir, test_file, config_files):
|
|
tm.setgd("testium_version", get_testium_version())
|
|
tm.setgd("testium_path", testium_path())
|
|
tm.setgd("test_name", test_name)
|
|
tm.setgd("test_directory", test_dir)
|
|
tm.setgd("test_main_file", test_file)
|
|
tm.setgd("config_files", config_files)
|
|
tm.setgd("host_name", gethostname())
|
|
tm.setgd("home", str(Path.home()))
|
|
tm.setgd("os", tm.OS())
|
|
|
|
|
|
def env_init():
|
|
if not hasattr(prefs, "settings"):
|
|
prefs.init()
|
|
eval_func_init(evaluate)
|
|
_constants_init()
|
|
|
|
|
|
def apply_overrides(defines, gui_defaults):
|
|
"""Push GUI defaults then CLI defines into the global dict.
|
|
|
|
Extracted from update_global so it can be called *before* eval_proc
|
|
starts: interpreter overrides (python_bin, lua_bin) must be visible
|
|
to bins.python_bin() on its first lookup, which happens during
|
|
eval_process_init.
|
|
"""
|
|
for k, v in gui_defaults.items():
|
|
try:
|
|
val = ast.literal_eval(v)
|
|
except:
|
|
val = v
|
|
tm.setgd(k, val)
|
|
|
|
for k, v in defines.items():
|
|
try:
|
|
val = ast.literal_eval(v)
|
|
except:
|
|
val = v
|
|
tm.setgd(k, val)
|
|
|
|
|
|
def update_global(config_files, defines, gui_defaults, silent=False):
|
|
"""Global dict updated with the content of the config file and a dict provided.
|
|
this function returns the resulting dict.
|
|
"""
|
|
# GUI preferences applied first, then command line defines
|
|
apply_overrides(defines, gui_defaults)
|
|
|
|
# Then the configuration files
|
|
# load global dic before test item
|
|
_feed_gd_with_params(config_files, silent)
|
|
|
|
# Re-apply command line defines to ensure it has not been
|
|
# overloaded by the configuration files
|
|
for k, v in defines.items():
|
|
try:
|
|
val = ast.literal_eval(v)
|
|
except:
|
|
val = v
|
|
|
|
conf_val = tm.gd(k)
|
|
if val != conf_val:
|
|
if not silent:
|
|
tm.print_info(
|
|
f'Variable $({k}) overloaded by command line arg --> "{val}".'
|
|
)
|
|
tm.setgd(k, val)
|
|
|
|
return globdict.global_dict
|
|
|
|
|
|
def prepare_global():
|
|
# Global dict setup
|
|
globdict.cleargd()
|
|
|
|
|
|
def backup_gd():
|
|
return copy.deepcopy(globdict.global_dict)
|
|
|
|
|
|
def restore_gd(dict):
|
|
clear_recursively(globdict.global_dict)
|
|
globdict.global_dict.update(dict)
|
|
|
|
|
|
def test_run_init():
|
|
tm.init_timestamp()
|
|
|
|
test_dir = tm.gd("test_directory")
|
|
tm.setgd("test_version", get_version(test_dir))
|
|
tm.setgd("test_modifs", get_modifications(test_dir))
|
|
|
|
start_test_date = datetime.datetime.now()
|
|
tm.setgd("start_test_date", start_test_date)
|
|
tm.setgd("testrun_date", start_test_date.strftime("%Y-%m-%d"))
|
|
tm.setgd("testrun_time", start_test_date.strftime("%H:%M:%S"))
|
|
|
|
|
|
def test_run_header():
|
|
tool_version = tm.gd("testium_version")
|
|
test_file = tm.gd("test_main_file", "")
|
|
has_test_file = tm.gd("test_main_file") != ""
|
|
|
|
s = ""
|
|
s += (80 * "=") + "\n"
|
|
s += "====== Test overview" + "\n"
|
|
s += (80 * "=") + "\n"
|
|
if has_test_file:
|
|
s += ("Executed test file : " + test_file) + "\n"
|
|
for cf in tm.gd("config_files"):
|
|
s += ("With param file : {}".format(cf)) + "\n"
|
|
s += (
|
|
"Test started : "
|
|
+ tm.gd("testrun_date")
|
|
+ " "
|
|
+ tm.gd("testrun_time")
|
|
) + "\n"
|
|
|
|
s += (80 * "=") + "\n"
|
|
s += ("====== Test configuration") + "\n"
|
|
s += (80 * "=") + "\n"
|
|
s += ("Test executed with testium : " + tool_version.splitlines()[0]) + "\n"
|
|
for l in tool_version.splitlines()[1:]:
|
|
s += (32 * " " + ": " + l) + "\n"
|
|
s += " \n"
|
|
if has_test_file:
|
|
test_version = tm.gd("test_version")
|
|
test_modifs = tm.gd("test_modifs")
|
|
s += (
|
|
"Test scripts revision : " + test_version.splitlines()[0]
|
|
) + "\n"
|
|
|
|
for l in test_version.splitlines()[1:]:
|
|
s += (32 * " " + ": " + l) + "\n"
|
|
for l in test_modifs.splitlines():
|
|
s += (" " + l) + "\n"
|
|
return s
|