From 2c78aaa89fffd3bd010ac487b2b65ace2aad8fd1 Mon Sep 17 00:00:00 2001 From: Dor-bl <59066376+Dor-bl@users.noreply.github.com> Date: Sun, 13 Sep 2026 21:40:20 +0000 Subject: [PATCH] perf: optimize string formatting in format_stacktrace Construct file and meth strings directly with conditional expressions to avoid intermediate string object allocations and re-allocations inside the stack trace loop in MobileErrorHandler. --- appium/webdriver/errorhandler.py | 12 ++-- test/unit/webdriver/errorhandler_test.py | 74 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 test/unit/webdriver/errorhandler_test.py diff --git a/appium/webdriver/errorhandler.py b/appium/webdriver/errorhandler.py index d074b01a..7f8fc435 100644 --- a/appium/webdriver/errorhandler.py +++ b/appium/webdriver/errorhandler.py @@ -69,12 +69,12 @@ def format_stacktrace(original: None | str | Sequence) -> list[str]: continue line = frame.get('lineNumber', '') - file = frame.get('fileName', '') - if line: - file = f'{file}:{line}' - meth = frame.get('methodName', '') - if 'className' in frame: - meth = f'{frame["className"]}.{meth}' + file = f'{frame.get("fileName", "")}:{line}' if line else frame.get('fileName', '') + meth = ( + f'{frame["className"]}.{frame.get("methodName", "")}' + if 'className' in frame + else frame.get('methodName', '') + ) result.append(f' at {meth} ({file})') except TypeError: pass diff --git a/test/unit/webdriver/errorhandler_test.py b/test/unit/webdriver/errorhandler_test.py new file mode 100644 index 00000000..b24ccf70 --- /dev/null +++ b/test/unit/webdriver/errorhandler_test.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from appium.webdriver.errorhandler import format_stacktrace + + +def test_format_stacktrace_empty() -> None: + assert format_stacktrace(None) == [] + assert format_stacktrace('') == [] + assert format_stacktrace([]) == [] + + +def test_format_stacktrace_string() -> None: + assert format_stacktrace('line1\nline2\nline3') == ['line1', 'line2', 'line3'] + + +def test_format_stacktrace_frames() -> None: + frames = [ + { + 'fileName': 'App.js', + 'lineNumber': 100, + 'methodName': 'handleClick', + 'className': 'AppComponent', + }, + { + 'fileName': 'Server.py', + 'lineNumber': 42, + 'methodName': 'process', + }, + { + 'methodName': 'anonymous_func', + 'className': 'Util', + }, + { + 'fileName': 'index.js', + }, + {}, + ] + + expected = [ + ' at AppComponent.handleClick (App.js:100)', + ' at process (Server.py:42)', + ' at Util.anonymous_func ()', + ' at (index.js)', + ' at ()', + ] + + assert format_stacktrace(frames) == expected + + +def test_format_stacktrace_non_dict_elements() -> None: + frames = [ + 'not a dict', + 123, + {'fileName': 'test.py', 'lineNumber': 10, 'methodName': 'foo'}, + None, + ] + expected = [' at foo (test.py:10)'] + assert format_stacktrace(frames) == expected + + +def test_format_stacktrace_invalid_type() -> None: + assert format_stacktrace(123) == []