strobengine 0.6.0__tar.gz → 0.6.1__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.
- {strobengine-0.6.0 → strobengine-0.6.1}/CHANGELOG.md +18 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/Cargo.lock +1 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/Cargo.toml +1 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/PKG-INFO +1 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/pyproject.toml +1 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/src/lib.rs +0 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/src/metrics.rs +12 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/report/schema.rs +25 -9
- {strobengine-0.6.0 → strobengine-0.6.1}/src/report/writer.rs +2 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/_strobengine.pyi +1 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/engine.py +6 -5
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporter.py +10 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/test_engine.py +8 -6
- {strobengine-0.6.0 → strobengine-0.6.1}/uv.lock +1 -1
- {strobengine-0.6.0 → strobengine-0.6.1}/.dockerignore +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.github/workflows/ci.yml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.github/workflows/docker-release.yml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.github/workflows/publish-test.yml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.github/workflows/publish.yml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.gitignore +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.pre-commit-config.yaml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/.python-version +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/Dockerfile +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/LICENSE +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/Makefile +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/README.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/cliff.toml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/benchmarks.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/cli.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/dependencies.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/docker.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/grpc.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/http3.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/http_methods.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/infrastructure.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/install.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/quickstart.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/release.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/reports.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/roadmap.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/sse.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/table_of_contents.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/testing.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/docs/websockets.md +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/grpc.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/http3.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/http_methods.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/load_test.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/spike_test.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/sse.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/stress_test.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/examples/websocket.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/rust-toolchain.toml +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/scripts/release.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/chaos.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/config.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/logging.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/progress.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/grpc.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/grpc_parser.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/grpc_reflection.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/http.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/http3.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/mod.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/sse.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/protocols/websocket.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/report/mod.rs +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/__init__.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/cli.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/constants.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/py.typed +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/__init__.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/assets/chart.min.js +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/assets/report_template.html +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/baseline.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/csv_report.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/html_report.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/junit_report.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/markdown_report.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/__init__.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/conftest.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/__init__.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/conftest.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/mock_server.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_cli_interface.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_grpc_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_http3_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_http_scenarios.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_latency_stats_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_performance_sanity.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_persistence_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_sse_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/e2e/test_websocket_e2e.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/fixtures/service.proto +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/test_cli.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/test_logging.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/test_reporter.py +0 -0
- {strobengine-0.6.0 → strobengine-0.6.1}/tests/test_reporting.py +0 -0
|
@@ -4,6 +4,20 @@ All notable changes to `strobengine` will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.1] - 2026-09-13
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- *(lib)* Remove unnecessary clippy::too_many_arguments suppression
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- *(reporter)* Use dynamic terminal width for histogram bar rendering
|
|
16
|
+
|
|
17
|
+
### Refactoring
|
|
18
|
+
|
|
19
|
+
- *(schema)* Derive LatencyPercentiles from TestSummary via From impl
|
|
20
|
+
- *(engine)* Clone TestSummary before enrichment to prevent in-place mutation
|
|
7
21
|
## [0.6.0] - 2026-09-09
|
|
8
22
|
|
|
9
23
|
### Bug Fixes
|
|
@@ -32,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
46
|
- *(metrics)* Add latency jitter, p99.99, and histogram bucketing
|
|
33
47
|
- *(reporter)* Expose extended latency stats in terminal and artifact output
|
|
34
48
|
|
|
49
|
+
### Miscellaneous Tasks
|
|
50
|
+
|
|
51
|
+
- *(release)* Bump version to 0.6.0
|
|
52
|
+
|
|
35
53
|
### Refactoring
|
|
36
54
|
|
|
37
55
|
- *(lib)* Extract spawn_worker helper from execute_test
|
|
@@ -177,6 +177,18 @@ pub struct TestSummary {
|
|
|
177
177
|
|
|
178
178
|
#[pymethods]
|
|
179
179
|
impl TestSummary {
|
|
180
|
+
pub fn clone(&self) -> Self {
|
|
181
|
+
Clone::clone(self)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pub fn __copy__(&self) -> Self {
|
|
185
|
+
self.clone()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pub fn __deepcopy__(&self, _py: Python<'_>) -> Self {
|
|
189
|
+
self.clone()
|
|
190
|
+
}
|
|
191
|
+
|
|
180
192
|
#[pyo3(signature = (indent=None))]
|
|
181
193
|
pub fn to_json(&self, _py: Python<'_>, indent: Option<usize>) -> PyResult<String> {
|
|
182
194
|
let json_str = if indent.is_some() {
|
|
@@ -69,9 +69,27 @@ pub struct LatencyPercentiles {
|
|
|
69
69
|
pub p90_us: f64,
|
|
70
70
|
pub p95_us: f64,
|
|
71
71
|
pub p99_us: f64,
|
|
72
|
+
pub p99_99_us: f64,
|
|
72
73
|
pub min_us: f64,
|
|
73
74
|
pub max_us: f64,
|
|
74
75
|
pub mean_us: f64,
|
|
76
|
+
pub std_dev_us: f64,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl From<&crate::metrics::TestSummary> for LatencyPercentiles {
|
|
80
|
+
fn from(s: &crate::metrics::TestSummary) -> Self {
|
|
81
|
+
Self {
|
|
82
|
+
p50_us: s.p50_latency_ms * 1000.0,
|
|
83
|
+
p90_us: s.p90_latency_ms * 1000.0,
|
|
84
|
+
p95_us: s.p95_latency_ms * 1000.0,
|
|
85
|
+
p99_us: s.p99_latency_ms * 1000.0,
|
|
86
|
+
p99_99_us: s.p99_99_latency_ms * 1000.0,
|
|
87
|
+
min_us: s.min_latency_ms * 1000.0,
|
|
88
|
+
max_us: s.max_latency_ms * 1000.0,
|
|
89
|
+
mean_us: s.average_latency_ms * 1000.0,
|
|
90
|
+
std_dev_us: s.std_dev_latency_ms * 1000.0,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
fn get_system_info() -> SystemInfo {
|
|
@@ -129,15 +147,7 @@ impl ReportArtifact {
|
|
|
129
147
|
rps,
|
|
130
148
|
bytes_transferred: summary.total_bytes_received,
|
|
131
149
|
},
|
|
132
|
-
latency_percentiles: LatencyPercentiles
|
|
133
|
-
p50_us: summary.p50_latency_ms * 1000.0,
|
|
134
|
-
p90_us: summary.p90_latency_ms * 1000.0,
|
|
135
|
-
p95_us: summary.p95_latency_ms * 1000.0,
|
|
136
|
-
p99_us: summary.p99_latency_ms * 1000.0,
|
|
137
|
-
min_us: summary.min_latency_ms * 1000.0,
|
|
138
|
-
max_us: summary.max_latency_ms * 1000.0,
|
|
139
|
-
mean_us: summary.average_latency_ms * 1000.0,
|
|
140
|
-
},
|
|
150
|
+
latency_percentiles: LatencyPercentiles::from(summary),
|
|
141
151
|
error_breakdown,
|
|
142
152
|
avg_connection_latency_us: if summary.avg_connection_latency_us > 0.0 {
|
|
143
153
|
Some(summary.avg_connection_latency_us)
|
|
@@ -202,9 +212,11 @@ mod tests {
|
|
|
202
212
|
p90_us: 3000.0,
|
|
203
213
|
p95_us: 4500.0,
|
|
204
214
|
p99_us: 9000.0,
|
|
215
|
+
p99_99_us: 14000.0,
|
|
205
216
|
min_us: 100.0,
|
|
206
217
|
max_us: 15000.0,
|
|
207
218
|
mean_us: 2200.0,
|
|
219
|
+
std_dev_us: 3500.0,
|
|
208
220
|
},
|
|
209
221
|
error_breakdown,
|
|
210
222
|
avg_connection_latency_us: None,
|
|
@@ -274,18 +286,22 @@ mod tests {
|
|
|
274
286
|
p90_us: 3000.0,
|
|
275
287
|
p95_us: 4500.0,
|
|
276
288
|
p99_us: 9000.0,
|
|
289
|
+
p99_99_us: 14000.0,
|
|
277
290
|
min_us: 100.0,
|
|
278
291
|
max_us: 15000.0,
|
|
279
292
|
mean_us: 2200.0,
|
|
293
|
+
std_dev_us: 3500.0,
|
|
280
294
|
};
|
|
281
295
|
let json = serde_json::to_value(&lp).unwrap();
|
|
282
296
|
assert_eq!(json["p50_us"], 1500.0);
|
|
283
297
|
assert_eq!(json["p90_us"], 3000.0);
|
|
284
298
|
assert_eq!(json["p95_us"], 4500.0);
|
|
285
299
|
assert_eq!(json["p99_us"], 9000.0);
|
|
300
|
+
assert_eq!(json["p99_99_us"], 14000.0);
|
|
286
301
|
assert_eq!(json["min_us"], 100.0);
|
|
287
302
|
assert_eq!(json["max_us"], 15000.0);
|
|
288
303
|
assert_eq!(json["mean_us"], 2200.0);
|
|
304
|
+
assert_eq!(json["std_dev_us"], 3500.0);
|
|
289
305
|
}
|
|
290
306
|
|
|
291
307
|
#[test]
|
|
@@ -129,9 +129,11 @@ mod tests {
|
|
|
129
129
|
p90_us: 3000.0,
|
|
130
130
|
p95_us: 4500.0,
|
|
131
131
|
p99_us: 9000.0,
|
|
132
|
+
p99_99_us: 14000.0,
|
|
132
133
|
min_us: 100.0,
|
|
133
134
|
max_us: 15000.0,
|
|
134
135
|
mean_us: 2200.0,
|
|
136
|
+
std_dev_us: 3500.0,
|
|
135
137
|
},
|
|
136
138
|
error_breakdown,
|
|
137
139
|
avg_connection_latency_us: None,
|
|
@@ -212,13 +212,14 @@ class StrobEngine:
|
|
|
212
212
|
)
|
|
213
213
|
|
|
214
214
|
def _enrich_summary(self, summary: TestSummary) -> TestSummary:
|
|
215
|
-
|
|
215
|
+
enriched = summary.clone()
|
|
216
|
+
enriched.timestamp = datetime.now(UTC).isoformat()
|
|
216
217
|
if self.config is not None:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
f"strobengine.run(url='{summary.url}', workers={
|
|
218
|
+
enriched.workers = self.config.concurrency
|
|
219
|
+
enriched.raw_command = (
|
|
220
|
+
f"strobengine.run(url='{summary.url}', workers={enriched.workers})"
|
|
220
221
|
)
|
|
221
|
-
return
|
|
222
|
+
return enriched
|
|
222
223
|
|
|
223
224
|
def run(self) -> TestSummary:
|
|
224
225
|
opts = self._options
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import shutil
|
|
4
5
|
import sys
|
|
5
6
|
|
|
6
7
|
from strobengine._strobengine import TestSummary
|
|
@@ -46,11 +47,19 @@ def _format_bytes(n: int) -> str:
|
|
|
46
47
|
return f"{val:.1f} PB"
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def _render_histogram(
|
|
50
|
+
def _render_histogram(
|
|
51
|
+
histogram: dict[str, int], max_width: int | None = None
|
|
52
|
+
) -> list[str]:
|
|
50
53
|
"""Render ASCII bar chart from latency histogram buckets."""
|
|
51
54
|
if not histogram or not any(v > 0 for v in histogram.values()):
|
|
52
55
|
return []
|
|
53
56
|
|
|
57
|
+
# Dynamic width based on terminal size
|
|
58
|
+
if max_width is None:
|
|
59
|
+
terminal_width = shutil.get_terminal_size((80, 24)).columns
|
|
60
|
+
# Reserve space for label (12) + separator (2) + count (~8) + padding (2)
|
|
61
|
+
max_width = max(10, terminal_width - 24)
|
|
62
|
+
|
|
54
63
|
max_count = max(histogram.values())
|
|
55
64
|
if max_count == 0:
|
|
56
65
|
max_count = 1
|
|
@@ -70,14 +70,15 @@ class TestStrobEngineInit:
|
|
|
70
70
|
class TestStrobEngineRun:
|
|
71
71
|
@patch("strobengine.engine.run_load_test")
|
|
72
72
|
def test_run_returns_summary(self, mock_run):
|
|
73
|
-
|
|
74
|
-
mock_run.return_value = expected
|
|
73
|
+
mock_run.return_value = _make_summary()
|
|
75
74
|
|
|
76
75
|
engine = StrobEngine(url="http://example.com")
|
|
77
76
|
result = engine.run()
|
|
78
77
|
|
|
79
78
|
mock_run.assert_called_once_with(engine.config)
|
|
80
|
-
|
|
79
|
+
# Verify enrichment was applied (clone creates independent copy)
|
|
80
|
+
assert result.raw_command is not None
|
|
81
|
+
assert "http://example.com" in result.raw_command
|
|
81
82
|
|
|
82
83
|
@patch("strobengine.engine.run_load_test")
|
|
83
84
|
def test_run_with_custom_params(self, mock_run):
|
|
@@ -94,13 +95,14 @@ class TestStrobEngineRun:
|
|
|
94
95
|
class TestStrobEngineRunAsync:
|
|
95
96
|
@patch("strobengine.engine.run_load_test")
|
|
96
97
|
async def test_run_async_returns_summary(self, mock_run):
|
|
97
|
-
|
|
98
|
-
mock_run.return_value = expected
|
|
98
|
+
mock_run.return_value = _make_summary()
|
|
99
99
|
|
|
100
100
|
engine = StrobEngine(url="http://example.com")
|
|
101
101
|
result = await engine.run_async()
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
# Verify enrichment was applied (clone creates independent copy)
|
|
104
|
+
assert result.raw_command is not None
|
|
105
|
+
assert "http://example.com" in result.raw_command
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
class TestLoadTestFactory:
|
|
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
|
{strobengine-0.6.0 → strobengine-0.6.1}/src/strobengine/reporting/assets/report_template.html
RENAMED
|
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
|