Files

132 lines
6.0 KiB
Python

# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Exercise native logger configuration in fresh processes through SWIG."""
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
class NativeLoggerTests(unittest.TestCase):
def run_logger(self, variables, body=''):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'native-é.log'
env = {key: value for key, value in os.environ.items()
if not key.startswith(('OPENSHOT_LOG_', 'LIBOPENSHOT_LOG_'))
and key != 'LIBOPENSHOT_DEBUG'}
env.update(variables, LIBOPENSHOT_LOG_FILE=str(path))
module_dir = env.get('OPENSHOT_TEST_MODULE_DIR')
if module_dir:
env['PYTHONPATH'] = module_dir + os.pathsep + env.get('PYTHONPATH', '')
if os.name == 'nt':
# Older MinGW Python uses PATH and ignores add_dll_directory().
dll_dirs = [env[key] for key in (
'OPENSHOT_TEST_DLL_DIR', 'OPENSHOT_TEST_AUDIO_DLL_DIR')
if env.get(key)]
env['PATH'] = os.pathsep.join(dll_dirs + [env.get('PATH', '')])
result = subprocess.run([sys.executable, '-c', '''
import os
_dll_handles = []
if os.name == 'nt' and hasattr(os, 'add_dll_directory'):
# Modern Python does not search PATH for extension-module dependencies.
# Include the project DLLs and the dependency directories supplied by CI.
_dll_dirs = [os.environ.get('OPENSHOT_TEST_DLL_DIR', ''),
os.environ.get('OPENSHOT_TEST_AUDIO_DLL_DIR', '')]
_dll_dirs.extend(os.environ.get('PATH', '').split(os.pathsep))
_seen = set()
for _directory in _dll_dirs:
_directory = _directory.strip('"')
if not _directory or not os.path.isdir(_directory):
continue
_directory = os.path.abspath(_directory)
_key = os.path.normcase(_directory)
if _key not in _seen:
_dll_handles.append(os.add_dll_directory(_directory))
_seen.add(_key)
import openshot
logger = openshot.Logger.Instance()
assert openshot.ZmqLogger is openshot.Logger
''' + body + '''
logger.Log("native-debug-record", openshot.Logger.LevelDebug)
logger.Log("native-info-record", openshot.Logger.LevelInfo)
logger.Log("native-warning-record", openshot.Logger.LevelWarning)
logger.Log("native-error-record", openshot.Logger.LevelError)
logger.Log("native-critical-record", openshot.Logger.LevelCritical)
logger.Close()
'''], env=env, text=True, capture_output=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stderr)
return path.read_text(), result.stderr
def test_default_and_legacy_console(self):
for variables, debug_console in (({}, False), ({'LIBOPENSHOT_DEBUG': '0'}, True)):
with self.subTest(variables=variables):
file, console = self.run_logger(variables)
self.assertNotIn('native-debug-record', file)
self.assertEqual('native-debug-record' in console, debug_console)
self.assertIn('native-info-record', file)
self.assertIn('native-info-record', console)
def test_component_and_destination_precedence(self):
file, console = self.run_logger({
'OPENSHOT_LOG_FILE_LEVEL': 'off',
'LIBOPENSHOT_LOG_LEVEL': 'debug',
'LIBOPENSHOT_LOG_CONSOLE_LEVEL': 'error'})
self.assertIn('native-debug-record', file)
self.assertNotIn('native-info-record', console)
self.assertIn('native-error-record', console)
def test_invalid_level_fallback(self):
file, console = self.run_logger({
'OPENSHOT_LOG_LEVEL': 'error',
'LIBOPENSHOT_LOG_FILE_LEVEL': 'invalid',
'LIBOPENSHOT_DEBUG': '1'})
self.assertNotIn('native-info-record', file)
self.assertNotIn('native-debug-record', console)
self.assertIn('native-error-record', file)
self.assertEqual(console.count('ignoring invalid'), 1)
def test_every_environment_variable_filters_the_correct_output(self):
for prefix in ('OPENSHOT', 'LIBOPENSHOT'):
for suffix, debug_file, debug_console in (
('LEVEL', True, True), ('FILE_LEVEL', True, False),
('CONSOLE_LEVEL', False, True)):
variable = prefix + '_LOG_' + suffix
with self.subTest(variable=variable):
file, console = self.run_logger({variable: 'debug'})
self.assertEqual('native-debug-record' in file, debug_file)
self.assertEqual('native-debug-record' in console, debug_console)
def test_all_level_thresholds(self):
levels = ('debug', 'info', 'warning', 'error', 'critical', 'off')
for threshold, level in enumerate(levels):
with self.subTest(level=level):
file, console = self.run_logger({'LIBOPENSHOT_LOG_LEVEL': level})
for index, message_level in enumerate(levels[:-1]):
message = 'native-' + message_level + '-record'
self.assertEqual(message in file, index >= threshold)
self.assertEqual(message in console, index >= threshold)
def test_api_overrides_environment_and_preserves_crash_output(self):
file, console = self.run_logger({'LIBOPENSHOT_LOG_LEVEL': 'off'}, '''
logger.SetFileLevel("debug")
logger.SetConsoleLevel("error")
logger.LogToFile("---- Unhandled Exception: Stack Trace ----\\ncrash-evidence\\n---- End of Stack Trace ----\\n")
try:
logger.SetFileLevel("invalid")
except RuntimeError:
pass
else:
raise AssertionError("invalid level accepted")
''')
self.assertIn('native-debug-record', file)
self.assertIn('crash-evidence', file)
self.assertIn('native-error-record', console)
self.assertNotIn('native-debug-record', console)
if __name__ == '__main__':
unittest.main()