pl-e2e-test 0.2.1__tar.gz → 0.2.2__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.
- {pl_e2e_test-0.2.1/src/pl_e2e_test.egg-info → pl_e2e_test-0.2.2}/PKG-INFO +2 -2
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/README.md +1 -1
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/pyproject.toml +1 -1
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/scripts/demo_cli.py +3 -3
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2/src/pl_e2e_test.egg-info}/PKG-INFO +2 -2
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/parallel.py +35 -18
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_demo_cli.py +17 -4
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_parallel.py +92 -4
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/LICENSE +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/MANIFEST.in +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/setup.cfg +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/pl_e2e_test.egg-info/SOURCES.txt +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/pl_e2e_test.egg-info/dependency_links.txt +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/pl_e2e_test.egg-info/entry_points.txt +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/pl_e2e_test.egg-info/requires.txt +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/pl_e2e_test.egg-info/top_level.txt +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/__init__.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/__main__.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/_git.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/_options.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/backend.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/cli.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/config.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/contract.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/discovery.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/errors.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/expected_errors.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/fuzz.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/harness.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/html_checks.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/instances.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/metadata.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/pytest_plugin.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/question_tests.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/regression.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/reporting.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/repository.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/runner.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/runtime.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/server.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/sharding.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/src/prairielearn_e2e/ui.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/__init__.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/conftest.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/stub_git.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_cli.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_config.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_contract.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_discovery.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_fuzz.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_git.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_harness.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_html_checks.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_instances.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_metadata.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_options.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_publish_version.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_pytest_plugin.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_question_tests.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_regression.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_reporting.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_repository.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_runtime.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_server.py +0 -0
- {pl_e2e_test-0.2.1 → pl_e2e_test-0.2.2}/tests/test_sharding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pl-e2e-test
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Fuzz PrairieLearn question lifecycles against the real Python element runtime
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PrairieLearn/pl-e2e-test
|
|
@@ -115,7 +115,7 @@ uv run python scripts/demo_cli.py --list
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
The demo covers help and CLI errors plus passing, failing, skipped, xfailed/xpassed,
|
|
118
|
-
warning, collection-error, empty-selection, quiet, verbose, and
|
|
118
|
+
warning, collection-error, empty-selection, quiet, verbose, and aggregate shard progress
|
|
119
119
|
output.
|
|
120
120
|
|
|
121
121
|
`--fuzz-seeds` creates and reports a random 64-bit master seed. Reproduce the same
|
|
@@ -89,7 +89,7 @@ uv run python scripts/demo_cli.py --list
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
The demo covers help and CLI errors plus passing, failing, skipped, xfailed/xpassed,
|
|
92
|
-
warning, collection-error, empty-selection, quiet, verbose, and
|
|
92
|
+
warning, collection-error, empty-selection, quiet, verbose, and aggregate shard progress
|
|
93
93
|
output.
|
|
94
94
|
|
|
95
95
|
`--fuzz-seeds` creates and reports a random 64-bit master seed. Reproduce the same
|
|
@@ -144,10 +144,10 @@ def _verbose(color: ColorChoice) -> int:
|
|
|
144
144
|
|
|
145
145
|
def _shards(color: ColorChoice) -> int:
|
|
146
146
|
console = make_console(color=color)
|
|
147
|
-
outcomes = (
|
|
147
|
+
outcomes = (True, True, False, True)
|
|
148
148
|
with _shard_progress(console, len(outcomes), disable=False) as complete:
|
|
149
|
-
for index,
|
|
150
|
-
complete(index,
|
|
149
|
+
for index, succeeded in enumerate(outcomes, start=1):
|
|
150
|
+
complete(index, succeeded)
|
|
151
151
|
return 1
|
|
152
152
|
|
|
153
153
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pl-e2e-test
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Fuzz PrairieLearn question lifecycles against the real Python element runtime
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PrairieLearn/pl-e2e-test
|
|
@@ -115,7 +115,7 @@ uv run python scripts/demo_cli.py --list
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
The demo covers help and CLI errors plus passing, failing, skipped, xfailed/xpassed,
|
|
118
|
-
warning, collection-error, empty-selection, quiet, verbose, and
|
|
118
|
+
warning, collection-error, empty-selection, quiet, verbose, and aggregate shard progress
|
|
119
119
|
output.
|
|
120
120
|
|
|
121
121
|
`--fuzz-seeds` creates and reports a random 64-bit master seed. Reproduce the same
|
|
@@ -16,7 +16,7 @@ from tempfile import TemporaryDirectory
|
|
|
16
16
|
from typing import IO
|
|
17
17
|
|
|
18
18
|
from rich.console import Console
|
|
19
|
-
from rich.progress import BarColumn,
|
|
19
|
+
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TextColumn
|
|
20
20
|
from rich.text import Text
|
|
21
21
|
|
|
22
22
|
from ._options import scan_options
|
|
@@ -55,26 +55,45 @@ class _Child:
|
|
|
55
55
|
@contextmanager
|
|
56
56
|
def _shard_progress(
|
|
57
57
|
console: Console, shard_count: int, *, disable: bool
|
|
58
|
-
) -> Generator[Callable[[int,
|
|
59
|
-
"""Render one parent-owned completion bar for
|
|
58
|
+
) -> Generator[Callable[[int, bool], None]]:
|
|
59
|
+
"""Render one parent-owned aggregate completion bar for child shards."""
|
|
60
60
|
|
|
61
|
+
bar = BarColumn(complete_style="green", finished_style="green")
|
|
61
62
|
progress = Progress(
|
|
62
|
-
TextColumn("
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
TextColumn("Shards"),
|
|
64
|
+
bar,
|
|
65
|
+
MofNCompleteColumn(),
|
|
65
66
|
TextColumn("{task.description}"),
|
|
66
67
|
console=console,
|
|
67
68
|
disable=disable,
|
|
68
69
|
)
|
|
69
|
-
|
|
70
|
+
completed: set[int] = set()
|
|
71
|
+
passed = 0
|
|
72
|
+
failed = 0
|
|
70
73
|
with progress:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
task = progress.add_task("running", total=shard_count)
|
|
75
|
+
|
|
76
|
+
def complete(index: int, succeeded: bool) -> None:
|
|
77
|
+
nonlocal failed, passed
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
if index in completed:
|
|
80
|
+
return
|
|
81
|
+
completed.add(index)
|
|
82
|
+
if succeeded:
|
|
83
|
+
passed += 1
|
|
84
|
+
else:
|
|
85
|
+
failed += 1
|
|
86
|
+
bar.complete_style = "red"
|
|
87
|
+
bar.finished_style = "red"
|
|
88
|
+
statuses = [f"{passed} passed"] if passed else []
|
|
89
|
+
if failed:
|
|
90
|
+
statuses.append(f"{failed} failed")
|
|
91
|
+
progress.update(
|
|
92
|
+
task,
|
|
93
|
+
completed=len(completed),
|
|
94
|
+
description=", ".join(statuses),
|
|
95
|
+
refresh=True,
|
|
96
|
+
)
|
|
78
97
|
|
|
79
98
|
yield complete
|
|
80
99
|
|
|
@@ -291,12 +310,10 @@ def run_parallel(
|
|
|
291
310
|
child.returncode = child.process.poll()
|
|
292
311
|
if child.returncode is None:
|
|
293
312
|
continue
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
else f"failed ({child.returncode})"
|
|
313
|
+
complete_shard(
|
|
314
|
+
child.index,
|
|
315
|
+
child.returncode == 0 and child.report.is_file(),
|
|
298
316
|
)
|
|
299
|
-
complete_shard(child.index, status)
|
|
300
317
|
pending.remove(child)
|
|
301
318
|
if pending:
|
|
302
319
|
time.sleep(0.05)
|
|
@@ -37,7 +37,7 @@ def test_mixed_demo_uses_native_reporter() -> None:
|
|
|
37
37
|
assert result.stderr == ""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def test_shard_demo_has_one_aggregate_progress_bar() -> None:
|
|
41
41
|
result = subprocess.run(
|
|
42
42
|
[sys.executable, SCRIPT, "--color", "never", "shards"],
|
|
43
43
|
check=True,
|
|
@@ -45,7 +45,20 @@ def test_shard_demo_has_one_progress_bar_per_shard() -> None:
|
|
|
45
45
|
text=True,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
assert "
|
|
48
|
+
assert "Shards" in result.stdout
|
|
49
|
+
assert "4/4" in result.stdout
|
|
50
|
+
assert "3 passed, 1 failed" in result.stdout
|
|
51
|
+
assert all(f"Shard {index}/4" not in result.stdout for index in range(1, 5))
|
|
51
52
|
assert "exit status: 1" in result.stdout
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_failed_shard_demo_uses_a_red_aggregate_bar() -> None:
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
[sys.executable, SCRIPT, "--color", "always", "shards"],
|
|
58
|
+
check=True,
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
assert "\x1b[31m" in result.stdout
|
|
64
|
+
assert "3 passed, 1 failed" in result.stdout
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
import xml.etree.ElementTree as ET
|
|
10
10
|
from collections.abc import Sequence
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Any, cast
|
|
12
|
+
from typing import Any, Self, cast
|
|
13
13
|
|
|
14
14
|
import pytest
|
|
15
15
|
from rich.console import Console
|
|
@@ -87,6 +87,87 @@ def test_invalid_count(count: int) -> None:
|
|
|
87
87
|
run_parallel([], shard_count=count)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
@pytest.mark.parametrize(
|
|
91
|
+
("completions", "descriptions", "styles"),
|
|
92
|
+
[
|
|
93
|
+
(
|
|
94
|
+
[(2, True), (1, True)],
|
|
95
|
+
["1 passed", "2 passed"],
|
|
96
|
+
["green", "green"],
|
|
97
|
+
),
|
|
98
|
+
(
|
|
99
|
+
[(3, True), (1, False), (2, True)],
|
|
100
|
+
["1 passed", "1 passed, 1 failed", "2 passed, 1 failed"],
|
|
101
|
+
["green", "red", "red"],
|
|
102
|
+
),
|
|
103
|
+
],
|
|
104
|
+
)
|
|
105
|
+
def test_shard_progress_aggregates_and_refreshes_each_completion(
|
|
106
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
107
|
+
completions: list[tuple[int, bool]],
|
|
108
|
+
descriptions: list[str],
|
|
109
|
+
styles: list[str],
|
|
110
|
+
) -> None:
|
|
111
|
+
created: list[FakeProgress] = []
|
|
112
|
+
|
|
113
|
+
class FakeProgress:
|
|
114
|
+
def __init__(self, *columns: object, **kwargs: object) -> None:
|
|
115
|
+
self.columns = columns
|
|
116
|
+
self.kwargs = kwargs
|
|
117
|
+
self.added: list[tuple[str, dict[str, object]]] = []
|
|
118
|
+
self.updates: list[tuple[dict[str, object], object, object]] = []
|
|
119
|
+
created.append(self)
|
|
120
|
+
|
|
121
|
+
def __enter__(self) -> Self:
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
def __exit__(self, *args: object) -> None:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def add_task(self, description: str, **kwargs: object) -> int:
|
|
128
|
+
self.added.append((description, kwargs))
|
|
129
|
+
return 42
|
|
130
|
+
|
|
131
|
+
def update(self, task: int, **kwargs: object) -> None:
|
|
132
|
+
assert task == 42
|
|
133
|
+
bar = next(
|
|
134
|
+
column
|
|
135
|
+
for column in self.columns
|
|
136
|
+
if isinstance(column, parallel.BarColumn)
|
|
137
|
+
)
|
|
138
|
+
self.updates.append((kwargs, bar.complete_style, bar.finished_style))
|
|
139
|
+
|
|
140
|
+
monkeypatch.setattr(parallel, "Progress", FakeProgress)
|
|
141
|
+
|
|
142
|
+
with parallel._shard_progress(
|
|
143
|
+
Console(file=io.StringIO()), len(completions), disable=False
|
|
144
|
+
) as complete:
|
|
145
|
+
for index, succeeded in completions:
|
|
146
|
+
complete(index, succeeded)
|
|
147
|
+
|
|
148
|
+
assert len(created) == 1
|
|
149
|
+
progress = created[0]
|
|
150
|
+
assert progress.added == [("running", {"total": len(completions)})]
|
|
151
|
+
assert [update[0]["completed"] for update in progress.updates] == list(
|
|
152
|
+
range(1, len(completions) + 1)
|
|
153
|
+
)
|
|
154
|
+
assert [update[0]["description"] for update in progress.updates] == descriptions
|
|
155
|
+
assert all(update[0]["refresh"] is True for update in progress.updates)
|
|
156
|
+
assert [update[1] for update in progress.updates] == styles
|
|
157
|
+
assert [update[2] for update in progress.updates] == styles
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_shard_progress_can_be_disabled() -> None:
|
|
161
|
+
output = io.StringIO()
|
|
162
|
+
console = Console(file=output, force_terminal=True)
|
|
163
|
+
|
|
164
|
+
with parallel._shard_progress(console, 2, disable=True) as complete:
|
|
165
|
+
complete(1, True)
|
|
166
|
+
complete(2, False)
|
|
167
|
+
|
|
168
|
+
assert output.getvalue() == ""
|
|
169
|
+
|
|
170
|
+
|
|
90
171
|
class FinishedProcess:
|
|
91
172
|
def __init__(self, status: int):
|
|
92
173
|
self.status = status
|
|
@@ -167,10 +248,17 @@ def test_orchestration_and_diagnostics(
|
|
|
167
248
|
assert all(not path.exists() for path in reports)
|
|
168
249
|
assert "a warning" in output.getvalue()
|
|
169
250
|
assert "hidden success output" not in output.getvalue()
|
|
251
|
+
rendered = output.getvalue()
|
|
170
252
|
if not terminal:
|
|
171
|
-
assert "Running 3 shards in parallel" in
|
|
172
|
-
|
|
173
|
-
|
|
253
|
+
assert "Running 3 shards in parallel" in rendered
|
|
254
|
+
assert ("Shards" in rendered) is terminal
|
|
255
|
+
if terminal:
|
|
256
|
+
assert "3/3" in rendered
|
|
257
|
+
if failure in {"missing", "launch", "exit"}:
|
|
258
|
+
assert "2 passed, 1 failed" in rendered
|
|
259
|
+
else:
|
|
260
|
+
assert "3 passed" in rendered
|
|
261
|
+
assert all(f"Shard {index}/3" not in rendered for index in range(1, 4))
|
|
174
262
|
if failure in {"none", "exit"}:
|
|
175
263
|
assert (tmp_path / "combined.xml").is_file()
|
|
176
264
|
if failure == "launch":
|
|
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
|