dogtail 2.0.dev10__tar.gz → 2.0.dev11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/CHANGES +1 -1
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/PKG-INFO +1 -1
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/config.ini +0 -2
- dogtail-2.0.dev11/dogtail/config.py +315 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/dogtail_create_config.py +6 -12
- dogtail-2.0.dev11/dogtail/scripts/dogtail_get_config.py +38 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/PKG-INFO +1 -1
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/pyproject.toml +1 -0
- dogtail-2.0.dev10/dogtail/config.py +0 -255
- dogtail-2.0.dev10/dogtail/scripts/dogtail_get_config.py +0 -16
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.fmf/version +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.gitignore +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.gitlab-ci.yml +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.packit.yaml +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.vscode/settings.json +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/COPYING +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/MAINTAINERS +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/Makefile +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/NEWS +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/README.md +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/configure +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/__init__.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/__init__.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_actions.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_component.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_editable_text.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_hypertext.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_image.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_object.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_root.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_selection.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_state.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_table.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_table_cell.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_text.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_utilities.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_value.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/distro.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/dump.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/errors.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/logging.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/ponytail_helper.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/predicate.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/rawinput.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/__init__.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/dogtail_headless.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/tree.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/utils.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/version.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/SOURCES.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/dependency_links.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/entry_points.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/requires.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/top_level.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.spec +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/epydoc.conf +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/atspi_events_listener_script.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/data/UTF-8-demo.txt +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/gnome_shell_stress_test_tree_api.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/text_editor_utf8_tree_api.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-head-48.png +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-head.svg +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-tail-48.png +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-tail.svg +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/setup.cfg +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/__init__.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/main.fmf +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_config.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_gtk_demo.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_predicate.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_drag.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_keyboard_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_keyboard_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_mouse_buttons_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_mouse_buttons_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_search_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_search_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_selection_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_selection_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_states_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_states_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_string_representation_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_string_representation_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_value_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_value_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_accessibility.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_delay.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_lock.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_run_gtk3.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_run_gtk4.py +0 -0
- {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dogtail
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.dev11
|
|
4
4
|
Summary: GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications.
|
|
5
5
|
Author-email: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vitezslav Humpa <vhumpa@redhat.com>, Michal Odehnal <modehnal@redhat.com>
|
|
6
6
|
License-Expression: GPL-2.0
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
Experimental dogtail changes for 2.0 release.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# pylint: disable=line-too-long
|
|
7
|
+
# ruff: noqa: E501
|
|
8
|
+
|
|
9
|
+
import configparser
|
|
10
|
+
|
|
11
|
+
from typing import get_origin, get_args
|
|
12
|
+
|
|
13
|
+
import copy
|
|
14
|
+
|
|
15
|
+
from dogtail.logging import logging_class
|
|
16
|
+
LOGGING = logging_class.logger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
YES_VALUES = ["y", "yes", "t", "true", "True", "1"]
|
|
20
|
+
NO_VALUES = ["", "n", "no", "f", "false", "False", "0"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CONFIG_SCHEMA = {
|
|
24
|
+
"config": {
|
|
25
|
+
"action_delay": ([float, int], 1),
|
|
26
|
+
"typing_delay": ([float, int], 0.1),
|
|
27
|
+
"default_delay":([float, int], 0.5),
|
|
28
|
+
"double_click_delay": ([float, int], 0.1),
|
|
29
|
+
"search_back_off_delay": ([float, int], 0.5),
|
|
30
|
+
"search_warning_threshold": (int, 3),
|
|
31
|
+
"search_cut_off_limit": (int, 20),
|
|
32
|
+
"search_showing_only": (bool, False),
|
|
33
|
+
"children_limit": (int, 100),
|
|
34
|
+
"run_interval": ([float, int], 0.5),
|
|
35
|
+
"run_timeout": ([float, int], 30),
|
|
36
|
+
"gtk4_offset": ([list[int], tuple[int]], [12, 12]),
|
|
37
|
+
"debug_dogtail": (bool, False),
|
|
38
|
+
"debug_file": (str, "/tmp/dogtail_debug.log"),
|
|
39
|
+
"debug_searching": (bool, False),
|
|
40
|
+
"debug_sleep": (bool, False),
|
|
41
|
+
"debug_search_paths": (bool, False),
|
|
42
|
+
"absolute_node_paths": (bool, False),
|
|
43
|
+
"ensure_sensitivity": (bool, False),
|
|
44
|
+
"fatal_errors": (bool, False),
|
|
45
|
+
"check_for_a11y": (bool, True),
|
|
46
|
+
},
|
|
47
|
+
"user_config": {
|
|
48
|
+
"user_value_x": (object, "user_value_x"),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ConfigValidationError(Exception):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cast_basic(value: str, expected_type):
|
|
58
|
+
"""
|
|
59
|
+
Casts primitive types.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if isinstance(value, str) and expected_type is bool:
|
|
63
|
+
lower = value.lower()
|
|
64
|
+
|
|
65
|
+
if lower in YES_VALUES:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
if lower in NO_VALUES:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
raise ValueError(f"Invalid boolean: {value}")
|
|
72
|
+
|
|
73
|
+
return expected_type(value)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cast_sequence(value: str, expected_container):
|
|
77
|
+
"""
|
|
78
|
+
Casts comma-separated values into list[...] or tuple[...].
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
origin = get_origin(expected_container)
|
|
82
|
+
(item_type,) = get_args(expected_container)
|
|
83
|
+
|
|
84
|
+
# Strip optional list/tuple brackets like [1,2,3] or (1,2,3).
|
|
85
|
+
cleaned = value.strip()
|
|
86
|
+
if (cleaned.startswith("[") and cleaned.endswith("]")) or \
|
|
87
|
+
(cleaned.startswith("(") and cleaned.endswith(")")):
|
|
88
|
+
cleaned = cleaned[1:-1].strip()
|
|
89
|
+
|
|
90
|
+
# Split by comma.
|
|
91
|
+
items = [x.strip() for x in cleaned.split(",") if x.strip()]
|
|
92
|
+
|
|
93
|
+
casted = [cast_value(x, item_type) for x in items]
|
|
94
|
+
|
|
95
|
+
if origin is list:
|
|
96
|
+
return casted
|
|
97
|
+
|
|
98
|
+
if origin is tuple:
|
|
99
|
+
return tuple(casted)
|
|
100
|
+
|
|
101
|
+
raise ValueError(f"Unsupported sequence type: {origin}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cast_value(value, expected_type):
|
|
105
|
+
# --- Union-of-types support ---
|
|
106
|
+
if isinstance(expected_type, list):
|
|
107
|
+
errors = []
|
|
108
|
+
for t in expected_type:
|
|
109
|
+
try:
|
|
110
|
+
return cast_value(value, t)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
errors.append(str(e))
|
|
113
|
+
raise ValueError(" | ".join(errors))
|
|
114
|
+
|
|
115
|
+
origin = get_origin(expected_type)
|
|
116
|
+
args = get_args(expected_type)
|
|
117
|
+
|
|
118
|
+
# --- Handle sequences ---
|
|
119
|
+
if origin in (list, tuple):
|
|
120
|
+
# If value is already the correct container type, just validate inner types
|
|
121
|
+
if isinstance(value, origin):
|
|
122
|
+
if args:
|
|
123
|
+
item_type = args[0]
|
|
124
|
+
for i, item in enumerate(value):
|
|
125
|
+
if not isinstance(item, item_type):
|
|
126
|
+
raise ValueError(f"Element '{i}' in '{value}' is not of type '{item_type}'.")
|
|
127
|
+
return value
|
|
128
|
+
# Otherwise parse string into sequence
|
|
129
|
+
return cast_sequence(value, expected_type)
|
|
130
|
+
|
|
131
|
+
# --- Handle simple types ---
|
|
132
|
+
# If value is already of correct type, return.
|
|
133
|
+
if isinstance(value, expected_type):
|
|
134
|
+
return value
|
|
135
|
+
|
|
136
|
+
# If the value is object.
|
|
137
|
+
if expected_type is object:
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
# Otherwise, cast string to type
|
|
141
|
+
return cast_basic(value, expected_type)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def load_and_validate_config(path: str, schema: dict):
|
|
145
|
+
config = configparser.ConfigParser()
|
|
146
|
+
config.read(path)
|
|
147
|
+
|
|
148
|
+
validated = {}
|
|
149
|
+
|
|
150
|
+
# We do not care about config sections in dogtail, just set the variables to a dict.
|
|
151
|
+
|
|
152
|
+
# Step 1: Process all sections found in the file, even if not in schema.
|
|
153
|
+
LOGGING.debug("Process all variables.")
|
|
154
|
+
for section in config.sections():
|
|
155
|
+
|
|
156
|
+
for key, raw_value in config[section].items():
|
|
157
|
+
# If schema defines type → validate
|
|
158
|
+
if section in schema and key in schema[section]:
|
|
159
|
+
expected_type, default_value = schema[section][key]
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
value = cast_value(raw_value, expected_type)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
raise ConfigValidationError(
|
|
165
|
+
f"Type error in [{section}].{key}: {e}"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
validated[key] = value
|
|
169
|
+
LOGGING.debug(f"Set Validated key '{key}' to value '{value}'")
|
|
170
|
+
|
|
171
|
+
else:
|
|
172
|
+
# Unknown key → keep raw value as string
|
|
173
|
+
validated[key] = raw_value
|
|
174
|
+
LOGGING.debug(f"Set Validated key '{key}' to raw value '{raw_value}'")
|
|
175
|
+
|
|
176
|
+
LOGGING.debug("Process default variables.")
|
|
177
|
+
# Step 2: Ensure missing schema sections & keys get default values.
|
|
178
|
+
for section, fields in schema.items():
|
|
179
|
+
for key, (expected_type, default_value) in fields.items():
|
|
180
|
+
if key not in validated:
|
|
181
|
+
validated[key] = default_value
|
|
182
|
+
LOGGING.debug(f"Set Default key '{key}' to value '{default_value}'")
|
|
183
|
+
|
|
184
|
+
return validated
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class _Config:
|
|
188
|
+
"""
|
|
189
|
+
Config class to keep backwards compatibility and to have getters and setters.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
validated_config = load_and_validate_config("dogtail_config.ini", CONFIG_SCHEMA)
|
|
193
|
+
LOGGING.debug(f"Validated config: '{validated_config}'")
|
|
194
|
+
|
|
195
|
+
# Create a deep copy instead of shallow copy to keep values for reset method.
|
|
196
|
+
_default_values_storage = copy.deepcopy(validated_config)
|
|
197
|
+
|
|
198
|
+
# Handle dogtail debug logging.
|
|
199
|
+
if validated_config["debug_dogtail"]:
|
|
200
|
+
LOGGING.info("Debugging dogtail to console.")
|
|
201
|
+
logging_class.debug_to_console()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def reset_configuration(self):
|
|
205
|
+
"""
|
|
206
|
+
Reset configuration to default values.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
210
|
+
|
|
211
|
+
self.validated_config.update(self._default_values_storage)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def adjust_casing(self, string_to_fix):
|
|
215
|
+
"""
|
|
216
|
+
Transforms a string to snake_case.
|
|
217
|
+
|
|
218
|
+
:param string_to_fix: String to transform to snake_case
|
|
219
|
+
:type string_to_fix: str
|
|
220
|
+
|
|
221
|
+
:return: Transformed string.
|
|
222
|
+
:rtype: str
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
226
|
+
|
|
227
|
+
string_in_snake_case = ""
|
|
228
|
+
for character in string_to_fix:
|
|
229
|
+
if character.isalpha and character.isupper():
|
|
230
|
+
string_in_snake_case += "_" + character.lower()
|
|
231
|
+
else:
|
|
232
|
+
string_in_snake_case += character
|
|
233
|
+
|
|
234
|
+
return string_in_snake_case
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def __setattr__(self, option_id, value_to_set):
|
|
238
|
+
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
239
|
+
|
|
240
|
+
# Set a variable to use for the setter logic.
|
|
241
|
+
set_option_id = option_id
|
|
242
|
+
|
|
243
|
+
# Attempt to have some backwards compatibility and support camelCase.
|
|
244
|
+
if any(char.isupper() for char in set_option_id):
|
|
245
|
+
set_option_id = self.adjust_casing(set_option_id)
|
|
246
|
+
LOGGING.debug(f"Config variable was transformed to '{set_option_id}'.")
|
|
247
|
+
|
|
248
|
+
# Set custom user value to use in dogtail run.
|
|
249
|
+
# Current logic will fail on any attempt not set in config.ini.
|
|
250
|
+
# Should we allow setting values during a dogtail execution?
|
|
251
|
+
# if "custom_" in set_option_id:
|
|
252
|
+
# LOGGING.info(f"Setting a custom user value '{set_option_id}'.")
|
|
253
|
+
# self.options[set_option_id] = value_to_set
|
|
254
|
+
|
|
255
|
+
# In other cases check that the value exists.
|
|
256
|
+
if set_option_id not in self.validated_config:
|
|
257
|
+
raise AttributeError(f"Attempt to use invalid option '{set_option_id}'.")
|
|
258
|
+
# LOGGING.info(f"Attempt to use invalid option '{set_option_id}'.")
|
|
259
|
+
# return
|
|
260
|
+
|
|
261
|
+
# Set the value if the value is not already present.
|
|
262
|
+
if self.validated_config[set_option_id] != value_to_set:
|
|
263
|
+
|
|
264
|
+
validated_expected_type = None
|
|
265
|
+
|
|
266
|
+
# First attempt to get defaults from the schema.
|
|
267
|
+
for section in CONFIG_SCHEMA.keys():
|
|
268
|
+
if set_option_id in CONFIG_SCHEMA[section]:
|
|
269
|
+
validated_expected_type, default_value = CONFIG_SCHEMA[section][set_option_id]
|
|
270
|
+
|
|
271
|
+
# If unsuccessful mark the expected type as anything.
|
|
272
|
+
if not validated_expected_type:
|
|
273
|
+
LOGGING.debug("Setting expected type to object.")
|
|
274
|
+
validated_expected_type = object
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
# Set the value.
|
|
278
|
+
self.validated_config[set_option_id] = cast_value(value_to_set, validated_expected_type)
|
|
279
|
+
except Exception:
|
|
280
|
+
# Log the message but do not end, use the default.
|
|
281
|
+
LOGGING.info(" ".join((
|
|
282
|
+
f"Attempt to set value of type '{type(value_to_set)}'",
|
|
283
|
+
f"to key with accepted types '{validated_expected_type}'",
|
|
284
|
+
)))
|
|
285
|
+
# Setting the value.
|
|
286
|
+
LOGGING.info(f"Fallback of config option '{set_option_id}' to '{default_value}'.")
|
|
287
|
+
self.validated_config[set_option_id] = default_value
|
|
288
|
+
|
|
289
|
+
if set_option_id == "debug_dogtail" and self.validated_config[set_option_id]:
|
|
290
|
+
LOGGING.info("Debugging dogtail to console.")
|
|
291
|
+
logging_class.debug_to_console()
|
|
292
|
+
|
|
293
|
+
if set_option_id == "debug_dogtail" and not self.validated_config[set_option_id]:
|
|
294
|
+
LOGGING.info("Disabling debugging dogtail to console.")
|
|
295
|
+
logging_class.disable_debug_to_console()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def __getattr__(self, option_id):
|
|
299
|
+
# Set a variable to use for the getter logic.
|
|
300
|
+
get_option_id = option_id
|
|
301
|
+
|
|
302
|
+
# Attempt to have some backwards compatibility and support camelCase.
|
|
303
|
+
if any(char.isupper() for char in get_option_id):
|
|
304
|
+
get_option_id = self.adjust_casing(get_option_id)
|
|
305
|
+
LOGGING.debug(f"Config variable was transformed to '{get_option_id}'.")
|
|
306
|
+
LOGGING.debug(f"Its value is '{self.validated_config[get_option_id]}'.")
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
return self.validated_config[get_option_id]
|
|
310
|
+
except KeyError as error:
|
|
311
|
+
raise AttributeError(f"Attempt to use invalid option '{get_option_id}'.") from error
|
|
312
|
+
# LOGGING.info(f"Attempt to use invalid option '{get_option_id}'.")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
config = _Config()
|
|
@@ -7,24 +7,18 @@ Create configuration in your current directory.
|
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
|
-
from dogtail.config import
|
|
10
|
+
from dogtail.config import CONFIG_SCHEMA as schema
|
|
11
11
|
|
|
12
12
|
def main():
|
|
13
13
|
directory = os.path.realpath(os.getcwd())
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
# Print the retrieved values
|
|
17
|
-
|
|
18
15
|
configuration = ""
|
|
19
|
-
configuration += "[config]\n"
|
|
20
|
-
|
|
21
|
-
for key, value in config.options.items():
|
|
22
|
-
configuration += f"{key} = {value.value}\n"
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
for section in schema.keys():
|
|
18
|
+
configuration += f"[{section}]\n"
|
|
19
|
+
for key, scheme_value in schema[section].items():
|
|
20
|
+
_, raw_value = scheme_value
|
|
21
|
+
configuration += f"{key} = {raw_value}\n"
|
|
28
22
|
|
|
29
23
|
try:
|
|
30
24
|
if os.path.isfile("dogtail_config.ini"):
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Print default configuration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dogtail.config import config, CONFIG_SCHEMA
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
# Print the retrieved values
|
|
10
|
+
|
|
11
|
+
print("Configuration.")
|
|
12
|
+
|
|
13
|
+
expected_types = {}
|
|
14
|
+
header_options = "Config Options"
|
|
15
|
+
header_value = "Value"
|
|
16
|
+
header_type = "Acceptable Types"
|
|
17
|
+
|
|
18
|
+
max_string_options_length = len(header_options)
|
|
19
|
+
max_string_value_length = len(header_value)
|
|
20
|
+
|
|
21
|
+
for section, fields in CONFIG_SCHEMA.items():
|
|
22
|
+
for key, (expected_type, default_value) in fields.items():
|
|
23
|
+
expected_types[key] = (expected_type, default_value)
|
|
24
|
+
|
|
25
|
+
for key, value in config.validated_config.items():
|
|
26
|
+
max_string_options_length = max(max_string_options_length, len(key))
|
|
27
|
+
max_string_value_length = max(max_string_value_length, len(str(value)))
|
|
28
|
+
|
|
29
|
+
print(f"{header_options:<{max_string_options_length+2}} {header_value:<{max_string_value_length+1}} {header_type}")
|
|
30
|
+
|
|
31
|
+
for key, value in config.validated_config.items():
|
|
32
|
+
expected_type = "object"
|
|
33
|
+
if key in expected_types.keys():
|
|
34
|
+
expected_type = str(expected_types[key][0])
|
|
35
|
+
print(f"{key:<{max_string_options_length + 1}}: {str(value):<{max_string_value_length + 1}} '{expected_type}'")
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dogtail
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.dev11
|
|
4
4
|
Summary: GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications.
|
|
5
5
|
Author-email: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vitezslav Humpa <vhumpa@redhat.com>, Michal Odehnal <modehnal@redhat.com>
|
|
6
6
|
License-Expression: GPL-2.0
|
|
@@ -4,6 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dogtail"
|
|
7
|
+
#version = "v2.0.dev0"
|
|
7
8
|
dynamic = ["version"]
|
|
8
9
|
description = "GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications."
|
|
9
10
|
license = "GPL-2.0"
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
"""
|
|
3
|
-
Experimental dogtail changes for 2.0 release.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
# pylint: disable=line-too-long
|
|
7
|
-
# ruff: noqa: E501
|
|
8
|
-
|
|
9
|
-
import configparser
|
|
10
|
-
from configparser import NoSectionError
|
|
11
|
-
|
|
12
|
-
import copy
|
|
13
|
-
|
|
14
|
-
from dogtail.logging import logging_class
|
|
15
|
-
LOGGING = logging_class.logger
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
NO_VALUES = ["", "n", "no", "f", "false", "False", "0"]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class Value:
|
|
22
|
-
"""
|
|
23
|
-
Value class to keep value and its types.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, set_value, defined_types=list):
|
|
27
|
-
self.value = set_value
|
|
28
|
-
self.defined_types = defined_types
|
|
29
|
-
|
|
30
|
-
def __str__(self):
|
|
31
|
-
return f"Default Value: '{self.value}'. Defined Types: '{str(self.defined_types)}'"
|
|
32
|
-
|
|
33
|
-
def __repr__(self):
|
|
34
|
-
return self.__str__()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class LoadDefaultConfiguration:
|
|
38
|
-
"""
|
|
39
|
-
Configuration class for default values.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(self, config_parser):
|
|
43
|
-
# Access values from the configuration file.
|
|
44
|
-
self.cfg = config_parser
|
|
45
|
-
|
|
46
|
-
# Timing Delay section.
|
|
47
|
-
section = "config"
|
|
48
|
-
self.action_delay = float(self.cfg.get(section, "action_delay", fallback=1))
|
|
49
|
-
self.typing_delay = float(self.cfg.get(section, "typing_delay", fallback=0.1))
|
|
50
|
-
self.default_delay = float(self.cfg.get(section, "default_delay", fallback=0.5))
|
|
51
|
-
self.double_click_delay = float(self.cfg.get(section, "double_click_delay", fallback=0.1))
|
|
52
|
-
self.search_back_off_delay = float(self.cfg.get(section, "search_back_off_delay", fallback=0.5))
|
|
53
|
-
|
|
54
|
-
# Searching section.
|
|
55
|
-
self.search_warning_threshold = int(self.cfg.get(section, "search_warning_threshold", fallback=3))
|
|
56
|
-
self.search_cut_off_limit = int(self.cfg.get(section, "search_cut_off_limit", fallback=20))
|
|
57
|
-
self.search_showing_only = self.cfg.get(section, "search_showing_only", fallback="False") not in NO_VALUES
|
|
58
|
-
|
|
59
|
-
# Children Limit section.
|
|
60
|
-
self.children_limit = int(self.cfg.get(section, "children_limit", fallback=100))
|
|
61
|
-
|
|
62
|
-
# Util Scripts section.
|
|
63
|
-
self.run_interval = float(self.cfg.get(section, "run_interval", fallback=0.5))
|
|
64
|
-
self.run_timeout = int(self.cfg.get(section, "run_timeout", fallback=30))
|
|
65
|
-
|
|
66
|
-
# GTK4Offset section.
|
|
67
|
-
self.gtk4_offset = list(self.cfg.get(section, "gtk4_offset", fallback=(12, 12)))
|
|
68
|
-
|
|
69
|
-
# Debug section.
|
|
70
|
-
self.debug_dogtail = self.cfg.get(section, "debug_dogtail", fallback="False") not in NO_VALUES
|
|
71
|
-
self.debug_file = self.cfg.get(section, "debug_file", fallback="/tmp/dogtail_debug.log")
|
|
72
|
-
|
|
73
|
-
# Other debug section.
|
|
74
|
-
self.debug_searching = self.cfg.get(section, "debug_searching", fallback="False") not in NO_VALUES
|
|
75
|
-
self.debug_sleep = self.cfg.get(section, "debug_sleep", fallback="False") not in NO_VALUES
|
|
76
|
-
self.debug_search_paths = self.cfg.get(section, "debug_search_paths", fallback="False") not in NO_VALUES
|
|
77
|
-
|
|
78
|
-
# Debug section. ?
|
|
79
|
-
#self.log_debug_to_std_out = self.cfg.get(section, "log_debug_to_std_out", fallback="True") not in NO_VALUES
|
|
80
|
-
self.absolute_node_paths = self.cfg.get(section, "absolute_node_paths", fallback="False") not in NO_VALUES
|
|
81
|
-
self.ensure_sensitivity = self.cfg.get(section, "ensure_sensitivity", fallback="False") not in NO_VALUES
|
|
82
|
-
self.fatal_errors = self.cfg.get(section, "fatal_errors", fallback="False") not in NO_VALUES
|
|
83
|
-
self.check_for_a11y = self.cfg.get(section, "check_for_a11y", fallback="True") not in NO_VALUES
|
|
84
|
-
|
|
85
|
-
if self.debug_dogtail:
|
|
86
|
-
LOGGING.info("Debugging dogtail to console.")
|
|
87
|
-
logging_class.debug_to_console()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class _Config:
|
|
91
|
-
"""
|
|
92
|
-
Config class to keep backwards compatibility and to have getters and setters.
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
# Create a ConfigParser object.
|
|
96
|
-
config_parser = configparser.ConfigParser()
|
|
97
|
-
|
|
98
|
-
# Read the configuration file.
|
|
99
|
-
successful_file_parsed = config_parser.read("dogtail_config.ini")
|
|
100
|
-
_default = LoadDefaultConfiguration(config_parser)
|
|
101
|
-
|
|
102
|
-
# Return a dictionary with the retrieved values.
|
|
103
|
-
# Define allowed type so that user cannot by mistake set different type than allowed.
|
|
104
|
-
options = {
|
|
105
|
-
"action_delay": Value(_default.action_delay, [float, int]),
|
|
106
|
-
"typing_delay": Value(_default.typing_delay, [float, int]),
|
|
107
|
-
"default_delay": Value(_default.default_delay, [float, int]),
|
|
108
|
-
"double_click_delay": Value(_default.double_click_delay, [float, int]),
|
|
109
|
-
"search_back_off_delay": Value(_default.search_back_off_delay, [float, int]),
|
|
110
|
-
|
|
111
|
-
"search_warning_threshold": Value(_default.search_warning_threshold, [int]),
|
|
112
|
-
"search_cut_off_limit": Value(_default.search_cut_off_limit, [int]),
|
|
113
|
-
"search_showing_only": Value(_default.search_showing_only, [bool]),
|
|
114
|
-
|
|
115
|
-
"children_limit": Value(_default.children_limit, [int]),
|
|
116
|
-
|
|
117
|
-
"run_interval": Value(_default.run_interval, [float, int]),
|
|
118
|
-
"run_timeout": Value(_default.run_timeout, [float, int]),
|
|
119
|
-
|
|
120
|
-
"gtk4_offset": Value(_default.gtk4_offset, [list, tuple]),
|
|
121
|
-
|
|
122
|
-
"debug_dogtail": Value(_default.debug_dogtail, [bool]),
|
|
123
|
-
"debug_file": Value(_default.debug_file, [str]),
|
|
124
|
-
|
|
125
|
-
"debug_searching": Value(_default.debug_searching, [bool]),
|
|
126
|
-
"debug_sleep": Value(_default.debug_sleep, [bool]),
|
|
127
|
-
"debug_search_paths": Value(_default.debug_search_paths, [bool]),
|
|
128
|
-
|
|
129
|
-
#"log_debug_to_std_out": Value(_default.log_debug_to_std_out, [bool]),
|
|
130
|
-
"absolute_node_paths": Value(_default.absolute_node_paths, [bool]),
|
|
131
|
-
"ensure_sensitivity": Value(_default.ensure_sensitivity, [bool]),
|
|
132
|
-
"fatal_errors": Value(_default.fatal_errors, [bool]),
|
|
133
|
-
"check_for_a11y": Value(_default.check_for_a11y, [bool]),
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
# Create a deep copy instead of shallow copy to keep values for reset method.
|
|
137
|
-
_default_values_storage = copy.deepcopy(options)
|
|
138
|
-
|
|
139
|
-
# User configuration.
|
|
140
|
-
section = "user_config"
|
|
141
|
-
try:
|
|
142
|
-
user_setting_dictionary = dict(_default.cfg.items(section))
|
|
143
|
-
|
|
144
|
-
# Set all user setup.
|
|
145
|
-
for key, value in user_setting_dictionary.items():
|
|
146
|
-
# Should we force 'custom_' prefix on users?
|
|
147
|
-
# As of now I opted to not do that, users can define any option they want.
|
|
148
|
-
options[key] = Value(value, [type(value)])
|
|
149
|
-
|
|
150
|
-
except NoSectionError:
|
|
151
|
-
# Do nothing, no user setting defined, which is ok.
|
|
152
|
-
pass
|
|
153
|
-
|
|
154
|
-
except Exception as error:
|
|
155
|
-
raise RuntimeError("Unexpected exception caught.") from error
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def reset_configuration(self):
|
|
159
|
-
"""
|
|
160
|
-
Reset configuration to default values.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
164
|
-
|
|
165
|
-
self.options.update(self._default_values_storage)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def adjust_casing(self, string_to_fix):
|
|
169
|
-
"""
|
|
170
|
-
Transforms a string to snake_case.
|
|
171
|
-
|
|
172
|
-
:param string_to_fix: String to transform to snake_case
|
|
173
|
-
:type string_to_fix: str
|
|
174
|
-
|
|
175
|
-
:return: Transformed string.
|
|
176
|
-
:rtype: str
|
|
177
|
-
"""
|
|
178
|
-
|
|
179
|
-
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
180
|
-
|
|
181
|
-
string_in_snake_case = ""
|
|
182
|
-
for character in string_to_fix:
|
|
183
|
-
if character.isalpha and character.isupper():
|
|
184
|
-
string_in_snake_case += "_" + character.lower()
|
|
185
|
-
else:
|
|
186
|
-
string_in_snake_case += character
|
|
187
|
-
|
|
188
|
-
return string_in_snake_case
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def __setattr__(self, option_id, value_to_set):
|
|
192
|
-
LOGGING.debug(logging_class.get_func_params_and_values())
|
|
193
|
-
|
|
194
|
-
# Set a variable to use for the setter logic.
|
|
195
|
-
set_option_id = option_id
|
|
196
|
-
|
|
197
|
-
# Attempt to have some backwards compatibility and support camelCase.
|
|
198
|
-
if any(char.isupper() for char in set_option_id):
|
|
199
|
-
set_option_id = self.adjust_casing(set_option_id)
|
|
200
|
-
LOGGING.debug(f"Value was transformed to '{set_option_id}'.")
|
|
201
|
-
|
|
202
|
-
# Set custom user value to use in dogtail run.
|
|
203
|
-
# Current logic will fail on any attempt not set in config.ini.
|
|
204
|
-
# Should we allow setting values during a dogtail execution?
|
|
205
|
-
# if "custom_" in set_option_id:
|
|
206
|
-
# LOGGING.info(f"Setting a custom user value '{set_option_id}'.")
|
|
207
|
-
# self.options[set_option_id] = value_to_set
|
|
208
|
-
|
|
209
|
-
# In other cases check that the value exists.
|
|
210
|
-
if set_option_id not in self.options:
|
|
211
|
-
raise AttributeError(f"Attempt to use invalid option '{set_option_id}'.")
|
|
212
|
-
# LOGGING.info(f"Attempt to use invalid option '{set_option_id}'.")
|
|
213
|
-
# return
|
|
214
|
-
|
|
215
|
-
# Set the value if the value is not already present.
|
|
216
|
-
if self.options[set_option_id].value != value_to_set:
|
|
217
|
-
# Defined types variable.
|
|
218
|
-
_defined_types = self.options[set_option_id].defined_types
|
|
219
|
-
|
|
220
|
-
# Type checking.
|
|
221
|
-
if type(value_to_set) not in _defined_types:
|
|
222
|
-
raise ValueError(" ".join((
|
|
223
|
-
f"Attempt to set value of type '{type(value_to_set)}'",
|
|
224
|
-
f"to key with accepted types '{_defined_types}'",
|
|
225
|
-
)))
|
|
226
|
-
|
|
227
|
-
# Type was checked, keep the defined types.
|
|
228
|
-
self.options[set_option_id] = Value(value_to_set, _defined_types)
|
|
229
|
-
|
|
230
|
-
if set_option_id == "debug_dogtail" and self.options[set_option_id].value:
|
|
231
|
-
LOGGING.info("Debugging dogtail to console.")
|
|
232
|
-
logging_class.debug_to_console()
|
|
233
|
-
|
|
234
|
-
if set_option_id == "debug_dogtail" and not self.options[set_option_id].value:
|
|
235
|
-
LOGGING.info("Disabling debugging dogtail to console.")
|
|
236
|
-
logging_class.disable_debug_to_console()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def __getattr__(self, option_id):
|
|
240
|
-
# Set a variable to use for the getter logic.
|
|
241
|
-
get_option_id = option_id
|
|
242
|
-
|
|
243
|
-
# Attempt to have some backwards compatibility and support camelCase.
|
|
244
|
-
if any(char.isupper() for char in get_option_id):
|
|
245
|
-
get_option_id = self.adjust_casing(get_option_id)
|
|
246
|
-
LOGGING.debug(f"Value was transformed to '{get_option_id}'.")
|
|
247
|
-
|
|
248
|
-
try:
|
|
249
|
-
return self.options[get_option_id].value
|
|
250
|
-
except KeyError as error:
|
|
251
|
-
raise AttributeError(f"Attempt to use invalid option '{get_option_id}'.") from error
|
|
252
|
-
# LOGGING.info(f"Attempt to use invalid option '{get_option_id}'.")
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
config = _Config()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Print default configuration.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from dogtail.config import config
|
|
7
|
-
|
|
8
|
-
def main():
|
|
9
|
-
# Print the retrieved values
|
|
10
|
-
|
|
11
|
-
print("Configuration.")
|
|
12
|
-
for key, value in config.options.items():
|
|
13
|
-
print(f"{key:25}: {value.value} {value.defined_types}")
|
|
14
|
-
|
|
15
|
-
if __name__ == "__main__":
|
|
16
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|