codeflash 0.7.2__tar.gz → 0.7.3__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.
- {codeflash-0.7.2 → codeflash-0.7.3}/PKG-INFO +1 -1
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cli.py +1 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/instrument_existing_tests.py +3 -3
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/discover_unit_tests.py +14 -4
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/optimizer.py +6 -5
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/parse_test_output.py +23 -10
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/verification_utils.py +4 -2
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/verifier.py +4 -15
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/version.py +2 -2
- {codeflash-0.7.2 → codeflash-0.7.3}/pyproject.toml +1 -1
- {codeflash-0.7.2 → codeflash-0.7.3}/README.md +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/.env.example +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/LICENSE +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/aiservice.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/cfapi.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cli_common.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cmd_init.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/console.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/logging_config.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/workflows/codeflash-optimize.yaml +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_extractor.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_replacer.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/compat.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/config_consts.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/config_parser.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/env_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/formatter.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/git_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/github_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/remove_generated_tests.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/shell_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/time_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/functions_to_optimize.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/github/PrComment.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/github/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/main.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/ExperimentMetadata.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/models.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/function_context.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/create_pr.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/critic.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/explanation.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/posthog_cf.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/sentry.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracer.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/profile_stats.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/replay_test.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/tracing_utils.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/update_license_version.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/__init__.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/comparator.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/equivalence.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/pytest_plugin.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/test_results.py +0 -0
- {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/test_runner.py +0 -0
|
@@ -170,6 +170,7 @@ def process_pyproject_config(args: Namespace) -> Namespace:
|
|
|
170
170
|
# in this case, the ".." becomes outside project scope, causing issues with un-importable paths
|
|
171
171
|
args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path)
|
|
172
172
|
args.tests_root = Path(args.tests_root).resolve()
|
|
173
|
+
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
|
|
173
174
|
return handle_optimize_all_arg_parsing(args)
|
|
174
175
|
|
|
175
176
|
|
|
@@ -323,7 +323,7 @@ def inject_profiling_into_existing_test(
|
|
|
323
323
|
test_path: Path,
|
|
324
324
|
call_positions: list[CodePosition],
|
|
325
325
|
function_to_optimize: FunctionToOptimize,
|
|
326
|
-
|
|
326
|
+
tests_project_root: Path,
|
|
327
327
|
test_framework: str,
|
|
328
328
|
) -> tuple[bool, str | None]:
|
|
329
329
|
with test_path.open(encoding="utf8") as f:
|
|
@@ -334,12 +334,12 @@ def inject_profiling_into_existing_test(
|
|
|
334
334
|
logger.exception(f"Syntax error in code in file - {test_path}")
|
|
335
335
|
return False, None
|
|
336
336
|
# TODO: Pass the full name of function here, otherwise we can run into namespace clashes
|
|
337
|
-
|
|
337
|
+
test_module_path = module_name_from_file_path(test_path, tests_project_root)
|
|
338
338
|
import_visitor = FunctionImportedAsVisitor(function_to_optimize)
|
|
339
339
|
import_visitor.visit(tree)
|
|
340
340
|
func = import_visitor.imported_as
|
|
341
341
|
|
|
342
|
-
tree = InjectPerfOnly(func,
|
|
342
|
+
tree = InjectPerfOnly(func, test_module_path, test_framework, call_positions).visit(tree)
|
|
343
343
|
new_imports = [
|
|
344
344
|
ast.Import(names=[ast.alias(name="time")]),
|
|
345
345
|
ast.Import(names=[ast.alias(name="gc")]),
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import sys
|
|
5
6
|
import unittest
|
|
6
7
|
from collections import defaultdict
|
|
7
8
|
from multiprocessing import Process, Queue
|
|
@@ -65,11 +66,15 @@ def run_pytest_discovery_new_process(queue: Queue, cwd: str, tests_root: str) ->
|
|
|
65
66
|
|
|
66
67
|
os.chdir(cwd)
|
|
67
68
|
collected_tests = []
|
|
69
|
+
pytest_rootdir: Path | None = None
|
|
68
70
|
tests: list[TestsInFile] = []
|
|
71
|
+
sys.path.insert(1, str(cwd))
|
|
69
72
|
|
|
70
73
|
class PytestCollectionPlugin:
|
|
71
74
|
def pytest_collection_finish(self, session) -> None:
|
|
75
|
+
nonlocal pytest_rootdir
|
|
72
76
|
collected_tests.extend(session.items)
|
|
77
|
+
pytest_rootdir = Path(session.config.rootdir)
|
|
73
78
|
|
|
74
79
|
try:
|
|
75
80
|
exitcode = pytest.main(
|
|
@@ -79,9 +84,9 @@ def run_pytest_discovery_new_process(queue: Queue, cwd: str, tests_root: str) ->
|
|
|
79
84
|
except Exception as e:
|
|
80
85
|
logger.exception(f"Failed to collect tests: {e!s}")
|
|
81
86
|
exitcode = -1
|
|
82
|
-
queue.put((exitcode, tests))
|
|
87
|
+
queue.put((exitcode, tests, pytest_rootdir))
|
|
83
88
|
tests = parse_pytest_collection_results(collected_tests)
|
|
84
|
-
queue.put((exitcode, tests))
|
|
89
|
+
queue.put((exitcode, tests, pytest_rootdir))
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
def parse_pytest_collection_results(
|
|
@@ -116,10 +121,15 @@ def discover_tests_pytest(
|
|
|
116
121
|
q: Queue = Queue()
|
|
117
122
|
p: Process = Process(target=run_pytest_discovery_new_process, args=(q, project_root, tests_root))
|
|
118
123
|
p.start()
|
|
119
|
-
exitcode, tests = q.get()
|
|
124
|
+
exitcode, tests, pytest_rootdir = q.get()
|
|
120
125
|
p.join()
|
|
121
|
-
logger.debug(f"Pytest collection exit code: {exitcode}")
|
|
122
126
|
|
|
127
|
+
if exitcode != 0:
|
|
128
|
+
logger.warning(f"Failed to collect tests. Pytest Exit code: {exitcode}")
|
|
129
|
+
else:
|
|
130
|
+
logger.debug(f"Pytest collection exit code: {exitcode}")
|
|
131
|
+
if pytest_rootdir is not None:
|
|
132
|
+
cfg.tests_project_rootdir = pytest_rootdir
|
|
123
133
|
file_to_test_map = defaultdict(list)
|
|
124
134
|
for test in tests:
|
|
125
135
|
if discover_only_these_tests and test.test_file not in discover_only_these_tests:
|
|
@@ -100,6 +100,7 @@ class Optimizer:
|
|
|
100
100
|
|
|
101
101
|
self.test_cfg = TestConfig(
|
|
102
102
|
tests_root=args.tests_root,
|
|
103
|
+
tests_project_rootdir=args.test_project_root,
|
|
103
104
|
project_root_path=args.project_root,
|
|
104
105
|
test_framework=args.test_framework,
|
|
105
106
|
pytest_cmd=args.pytest_cmd,
|
|
@@ -751,11 +752,11 @@ class Optimizer:
|
|
|
751
752
|
path_obj_test_file = Path(test_file)
|
|
752
753
|
relevant_test_files_count += 1
|
|
753
754
|
success, injected_test = inject_profiling_into_existing_test(
|
|
754
|
-
path_obj_test_file,
|
|
755
|
-
positions,
|
|
756
|
-
function_to_optimize,
|
|
757
|
-
self.
|
|
758
|
-
self.args.test_framework,
|
|
755
|
+
test_path=path_obj_test_file,
|
|
756
|
+
call_positions=positions,
|
|
757
|
+
function_to_optimize=function_to_optimize,
|
|
758
|
+
tests_project_root=self.test_cfg.tests_project_rootdir,
|
|
759
|
+
test_framework=self.args.test_framework,
|
|
759
760
|
)
|
|
760
761
|
if not success:
|
|
761
762
|
continue
|
|
@@ -67,7 +67,7 @@ def parse_test_return_values_bin(
|
|
|
67
67
|
invocation_id_object = InvocationId.from_str_id(encoded_test_name, invocation_id)
|
|
68
68
|
test_file_path = file_path_from_module_name(
|
|
69
69
|
invocation_id_object.test_module_path,
|
|
70
|
-
test_config.
|
|
70
|
+
test_config.tests_project_rootdir,
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path)
|
|
@@ -111,7 +111,7 @@ def parse_sqlite_test_results(
|
|
|
111
111
|
for val in data:
|
|
112
112
|
try:
|
|
113
113
|
test_module_path = val[0]
|
|
114
|
-
test_file_path = file_path_from_module_name(test_module_path, test_config.
|
|
114
|
+
test_file_path = file_path_from_module_name(test_module_path, test_config.tests_project_rootdir)
|
|
115
115
|
# TODO : this is because sqlite writes original file module path. Should make it consistent
|
|
116
116
|
test_type = test_files.get_test_type_by_original_file_path(test_file_path)
|
|
117
117
|
loop_index = val[4]
|
|
@@ -159,7 +159,11 @@ def parse_test_xml(
|
|
|
159
159
|
f"Failed to parse {test_xml_file_path} as JUnitXml. Exception: {e}",
|
|
160
160
|
)
|
|
161
161
|
return test_results
|
|
162
|
-
|
|
162
|
+
base_dir = (
|
|
163
|
+
test_config.tests_project_rootdir
|
|
164
|
+
if test_config.test_framework == "pytest"
|
|
165
|
+
else test_config.project_root_path
|
|
166
|
+
)
|
|
163
167
|
for suite in xml:
|
|
164
168
|
for testcase in suite:
|
|
165
169
|
class_name = testcase.classname
|
|
@@ -173,9 +177,14 @@ def parse_test_xml(
|
|
|
173
177
|
# This means that the test failed to load, so we don't want to crash on it
|
|
174
178
|
logger.info("Test failed to load, skipping it.")
|
|
175
179
|
if run_result is not None:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
if isinstance(run_result.stdout, str) and isinstance(run_result.stderr, str):
|
|
181
|
+
logger.info(
|
|
182
|
+
f"Test log - STDOUT : {run_result.stdout} \n STDERR : {run_result.stderr}",
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Test log - STDOUT : {run_result.stdout.decode()} \n STDERR : {run_result.stderr.decode()}",
|
|
187
|
+
)
|
|
179
188
|
return test_results
|
|
180
189
|
|
|
181
190
|
test_class_path = testcase.classname
|
|
@@ -185,18 +194,22 @@ def parse_test_xml(
|
|
|
185
194
|
# TODO : This might not be true if the test is organized under a class
|
|
186
195
|
test_file_path = file_path_from_module_name(
|
|
187
196
|
test_class_path,
|
|
188
|
-
|
|
197
|
+
base_dir,
|
|
189
198
|
)
|
|
190
199
|
else:
|
|
191
|
-
test_file_path = file_path_from_module_name(
|
|
200
|
+
test_file_path = file_path_from_module_name(
|
|
201
|
+
test_function,
|
|
202
|
+
base_dir,
|
|
203
|
+
)
|
|
192
204
|
else:
|
|
193
|
-
|
|
205
|
+
# TODO: not sure which root path fits better here
|
|
206
|
+
test_file_path = base_dir / test_file_name
|
|
194
207
|
if not test_file_path.exists():
|
|
195
208
|
logger.warning(f"Could not find the test for file name - {test_file_path} ")
|
|
196
209
|
continue
|
|
197
210
|
test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path)
|
|
198
211
|
assert test_type is not None, f"Test type not found for {test_file_path}"
|
|
199
|
-
test_module_path = module_name_from_file_path(test_file_path, test_config.
|
|
212
|
+
test_module_path = module_name_from_file_path(test_file_path, test_config.tests_project_rootdir)
|
|
200
213
|
result = testcase.is_passed # TODO: See for the cases of ERROR and SKIPPED
|
|
201
214
|
test_class = None
|
|
202
215
|
if class_name is not None and class_name.startswith(test_module_path):
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
import os
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from pydantic.dataclasses import dataclass
|
|
@@ -67,9 +66,12 @@ class ModifyInspiredTests(ast.NodeTransformer):
|
|
|
67
66
|
return node
|
|
68
67
|
|
|
69
68
|
|
|
70
|
-
@dataclass
|
|
69
|
+
@dataclass
|
|
71
70
|
class TestConfig:
|
|
72
71
|
tests_root: Path
|
|
73
72
|
project_root_path: Path
|
|
74
73
|
test_framework: str
|
|
74
|
+
tests_project_rootdir: Path
|
|
75
|
+
# tests_project_rootdir corresponds to pytest rootdir,
|
|
76
|
+
# or for unittest - project_root_from_module_root(args.tests_root, pyproject_file_path)
|
|
75
77
|
pytest_cmd: str = "pytest"
|
|
@@ -38,7 +38,7 @@ def generate_tests(
|
|
|
38
38
|
module = importlib.import_module(str(module_path))
|
|
39
39
|
generated_test_source = module.CACHED_TESTS
|
|
40
40
|
instrumented_test_source = module.CACHED_INSTRUMENTED_TESTS
|
|
41
|
-
temp_run_dir = get_run_tmp_file(Path(
|
|
41
|
+
temp_run_dir = get_run_tmp_file(Path())
|
|
42
42
|
path = str(temp_run_dir).replace("\\", "\\\\") # Escape backslash for windows paths
|
|
43
43
|
instrumented_test_source = instrumented_test_source.replace(
|
|
44
44
|
"{codeflash_run_tmp_dir_client_side}",
|
|
@@ -46,12 +46,8 @@ def generate_tests(
|
|
|
46
46
|
)
|
|
47
47
|
logger.info(f"Using cached tests from {module_path}.CACHED_TESTS")
|
|
48
48
|
else:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
get_test_file_path(test_cfg.tests_root, function_to_optimize.function_name, 0),
|
|
52
|
-
test_cfg.project_root_path,
|
|
53
|
-
),
|
|
54
|
-
)
|
|
49
|
+
test_file_path = get_test_file_path(test_cfg.tests_root, function_to_optimize.function_name, 0)
|
|
50
|
+
test_module_path = Path(module_name_from_file_path(test_file_path, test_cfg.tests_project_rootdir))
|
|
55
51
|
response = aiservice_client.generate_regression_tests(
|
|
56
52
|
source_code_being_tested=source_code_being_tested,
|
|
57
53
|
function_to_optimize=function_to_optimize,
|
|
@@ -65,7 +61,7 @@ def generate_tests(
|
|
|
65
61
|
)
|
|
66
62
|
if response and isinstance(response, tuple) and len(response) == 2:
|
|
67
63
|
generated_test_source, instrumented_test_source = response
|
|
68
|
-
temp_run_dir = get_run_tmp_file(Path(
|
|
64
|
+
temp_run_dir = get_run_tmp_file(Path())
|
|
69
65
|
path = str(temp_run_dir).replace("\\", "\\\\")
|
|
70
66
|
instrumented_test_source = instrumented_test_source.replace(
|
|
71
67
|
"{codeflash_run_tmp_dir_client_side}",
|
|
@@ -77,13 +73,6 @@ def generate_tests(
|
|
|
77
73
|
)
|
|
78
74
|
return None
|
|
79
75
|
|
|
80
|
-
# TODO: Add support for inspired tests
|
|
81
|
-
# inspired_unit_tests = ""
|
|
82
|
-
|
|
83
|
-
# merged_test_source = merge_unit_tests(
|
|
84
|
-
# instrumented_test_source, inspired_unit_tests, test_cfg.test_framework
|
|
85
|
-
# )
|
|
86
|
-
|
|
87
76
|
return generated_test_source, instrumented_test_source
|
|
88
77
|
|
|
89
78
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# These version placeholders will be replaced by poetry-dynamic-versioning during `poetry build`.
|
|
2
|
-
__version__ = "0.7.
|
|
3
|
-
__version_tuple__ = (0, 7,
|
|
2
|
+
__version__ = "0.7.3"
|
|
3
|
+
__version_tuple__ = (0, 7, 3)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool]
|
|
2
2
|
[tool.poetry]
|
|
3
3
|
name = "codeflash"
|
|
4
|
-
version = "0.7.
|
|
4
|
+
version = "0.7.3" # Determined by poetry-dynamic-versioning during `poetry build`
|
|
5
5
|
description = "Client for codeflash.ai - automatic code performance optimization, powered by AI"
|
|
6
6
|
license = "BSL-1.1"
|
|
7
7
|
authors = ["CodeFlash Inc. <contact@codeflash.ai>"]
|
|
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
|