gool 1.3.2__tar.gz → 1.3.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.
- {gool-1.3.2 → gool-1.3.3}/.vscode/launch.json +15 -0
- {gool-1.3.2 → gool-1.3.3}/PKG-INFO +1 -1
- {gool-1.3.2 → gool-1.3.3}/src/gool/_version.py +2 -2
- {gool-1.3.2 → gool-1.3.3}/src/gool/arguments_parser.py +10 -7
- {gool-1.3.2 → gool-1.3.3}/src/gool/log_clustering.py +2 -0
- {gool-1.3.2 → gool-1.3.3}/src/gool/logs_miner.py +9 -4
- gool-1.3.3/tests/data/log/Crafted_logs.log +5 -0
- gool-1.3.3/tests/data/results/Crafted_logs_time_120014.txt +5 -0
- {gool-1.3.2 → gool-1.3.3}/tests/test_gool.py +24 -2
- {gool-1.3.2 → gool-1.3.3}/tests/test_logs_miner.py +14 -14
- {gool-1.3.2 → gool-1.3.3}/.gitignore +0 -0
- {gool-1.3.2 → gool-1.3.3}/.gitlab-ci.yml +0 -0
- {gool-1.3.2 → gool-1.3.3}/.pre-commit-config.yaml +0 -0
- {gool-1.3.2 → gool-1.3.3}/.vscode/extensions.json +0 -0
- {gool-1.3.2 → gool-1.3.3}/.vscode/settings.json +0 -0
- {gool-1.3.2 → gool-1.3.3}/.vscode/tasks.json +0 -0
- {gool-1.3.2 → gool-1.3.3}/LICENSE +0 -0
- {gool-1.3.2 → gool-1.3.3}/Makefile +0 -0
- {gool-1.3.2 → gool-1.3.3}/README.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/docs/api/arguments_parser.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/docs/api/log_clustering.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/docs/api/logs_miner.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/docs/index.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/docs/license.md +0 -0
- {gool-1.3.2 → gool-1.3.3}/misc/Dockerfile +0 -0
- {gool-1.3.2 → gool-1.3.3}/mkdocs.yml +0 -0
- {gool-1.3.2 → gool-1.3.3}/pyproject.toml +0 -0
- {gool-1.3.2 → gool-1.3.3}/sonar-project.properties +0 -0
- {gool-1.3.2 → gool-1.3.3}/src/gool/__init__.py +0 -0
- {gool-1.3.2 → gool-1.3.3}/src/gool/drain3.ini +0 -0
- {gool-1.3.2 → gool-1.3.3}/src/gool/error_code.py +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/drain3.ini +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/drain3_compare_baseline.ini +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/Apache_2k.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/Apache_2k_MonDec05.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/Apache_2k_SunDec04.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/Zookeeper_2k.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/Zookeeper_unordered.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/log/empty.log +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/6times_Zookeeper_2k_raw_output.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Apache_2K_baseline_compare.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Apache_2K_baseline_compare_common.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Apache_2k_raw_output.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Apache_2k_time_filter.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_depth10.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_lex_order.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_raw_output.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_sim_thres_5.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_size_order.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_2k_warn.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/tests/data/results/Zookeeper_unordered.txt +0 -0
- {gool-1.3.2 → gool-1.3.3}/uv.lock +0 -0
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
"${workspaceFolder}/tests/data/log/Zookeeper_2k.log"
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
"name": "gool sample crafted logs",
|
|
19
|
+
"type": "debugpy",
|
|
20
|
+
"request": "launch",
|
|
21
|
+
"module": "gool.log_clustering",
|
|
22
|
+
"args": [
|
|
23
|
+
"--cfg-file",
|
|
24
|
+
"${workspaceFolder}/tests/data/drain3.ini",
|
|
25
|
+
"--similarity-threshold",
|
|
26
|
+
"0.65",
|
|
27
|
+
"-m",
|
|
28
|
+
"13:00:00",
|
|
29
|
+
"${workspaceFolder}/tests/data/log/Crafted_logs.log"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
17
32
|
{
|
|
18
33
|
"name": "gool sample apache date/time filtered",
|
|
19
34
|
"type": "debugpy",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 3,
|
|
21
|
+
__version__ = version = '1.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -204,10 +204,16 @@ def guess_time_regexp_and_format_code(time_str: str) -> tuple[str, str] | None:
|
|
|
204
204
|
Returns:
|
|
205
205
|
A tuple containing the guessed regex pattern and format code.
|
|
206
206
|
"""
|
|
207
|
-
time_regex = r"(\d{2}:\d{2}:\d{2}
|
|
208
|
-
time_format = "%H:%M:%S
|
|
207
|
+
time_regex = r"(\d{2}:\d{2}:\d{2})"
|
|
208
|
+
time_format = "%H:%M:%S"
|
|
209
209
|
regex = re.compile(time_regex)
|
|
210
210
|
match = regex.fullmatch(time_str)
|
|
211
|
+
|
|
212
|
+
if not match:
|
|
213
|
+
time_regex = r"(\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?)"
|
|
214
|
+
time_format = "%H:%M:%S.%f"
|
|
215
|
+
regex = re.compile(time_regex)
|
|
216
|
+
match = regex.fullmatch(time_str)
|
|
211
217
|
if not match:
|
|
212
218
|
time_regex = r"(\d{2}:\d{2}:\d{2}(?:,\d{1,9})?)"
|
|
213
219
|
time_format = "%H:%M:%S,%f"
|
|
@@ -218,9 +224,6 @@ def guess_time_regexp_and_format_code(time_str: str) -> tuple[str, str] | None:
|
|
|
218
224
|
matched_time = match.group(1)
|
|
219
225
|
with contextlib.suppress(ValueError):
|
|
220
226
|
datetime.strptime(matched_time, time_format)
|
|
221
|
-
return time_regex, time_format
|
|
222
|
-
|
|
223
|
-
time_format = "%H:%M:%S"
|
|
224
|
-
datetime.strptime(matched_time, time_format)
|
|
225
|
-
return time_regex, time_format
|
|
227
|
+
return r".*" + time_regex + r".*", time_format
|
|
228
|
+
|
|
226
229
|
return None
|
|
@@ -171,6 +171,8 @@ def display_diff_baseline(
|
|
|
171
171
|
|
|
172
172
|
def display_data_rich(list_data: list[str], header: str) -> None:
|
|
173
173
|
table = Table(title=header, highlight=True)
|
|
174
|
+
min_width_empty_tab = 100
|
|
175
|
+
table.min_width = min_width_empty_tab
|
|
174
176
|
table.add_column("Template", justify="left")
|
|
175
177
|
for item in list_data:
|
|
176
178
|
pattern = surrogate_non_printable(item)
|
|
@@ -81,6 +81,8 @@ class LogsMiner:
|
|
|
81
81
|
self.time_max = time_max
|
|
82
82
|
self.unordered_time = unordered_time
|
|
83
83
|
self.cluster_char_sizes: Counter = Counter()
|
|
84
|
+
# misc attr
|
|
85
|
+
self.time_warn_displayed = False
|
|
84
86
|
|
|
85
87
|
def get_cluster_data(self) -> list[ClusterResult]:
|
|
86
88
|
"""
|
|
@@ -134,13 +136,17 @@ class LogsMiner:
|
|
|
134
136
|
def _extract_time_from_line(self, line: str) -> datetime | None:
|
|
135
137
|
if not self.time_pattern_regexp or not self.time_format:
|
|
136
138
|
return None
|
|
137
|
-
match = self.time_pattern_regexp.
|
|
139
|
+
match = self.time_pattern_regexp.match(line)
|
|
138
140
|
if match:
|
|
139
141
|
time_str = match.group(1)
|
|
140
142
|
try:
|
|
141
143
|
return datetime.strptime(time_str, self.time_format)
|
|
142
144
|
except ValueError:
|
|
143
|
-
|
|
145
|
+
if not self.time_warn_displayed:
|
|
146
|
+
logging.warning(
|
|
147
|
+
"Failed to parse time '%s' in line: %s. This warning is only displayed once", time_str, line
|
|
148
|
+
)
|
|
149
|
+
self.time_warn_displayed = True
|
|
144
150
|
return None
|
|
145
151
|
else:
|
|
146
152
|
logging.warning("Time pattern not found in line: %s", line)
|
|
@@ -166,8 +172,7 @@ class LogsMiner:
|
|
|
166
172
|
return False
|
|
167
173
|
line_time = self._extract_time_from_line(line)
|
|
168
174
|
if line_time is None:
|
|
169
|
-
|
|
170
|
-
return False
|
|
175
|
+
return True
|
|
171
176
|
if self.time_min and line_time < self.time_min:
|
|
172
177
|
return True
|
|
173
178
|
if self.time_max and line_time > self.time_max:
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
+
from gool.arguments_parser import guess_time_regexp_and_format_code
|
|
9
10
|
from gool.log_clustering import (
|
|
10
11
|
Arguments,
|
|
11
12
|
ErrorCode,
|
|
@@ -33,6 +34,20 @@ class TestMisc:
|
|
|
33
34
|
def test_surrogate_non_printable(self) -> None:
|
|
34
35
|
assert surrogate_non_printable("foo") == "foo"
|
|
35
36
|
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
("time_str", "expected"),
|
|
39
|
+
[
|
|
40
|
+
("15:04:05", (r".*(\d{2}:\d{2}:\d{2}).*", "%H:%M:%S")),
|
|
41
|
+
("15:04:05.000", (r".*(\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?).*", "%H:%M:%S.%f")),
|
|
42
|
+
("15:04:05,000", (r".*(\d{2}:\d{2}:\d{2}(?:,\d{1,9})?).*", "%H:%M:%S,%f")),
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
def test_guess_time_regexp_and_format_code(self, time_str: str, expected: tuple[str, str]) -> None:
|
|
46
|
+
assert guess_time_regexp_and_format_code(time_str) == expected
|
|
47
|
+
|
|
48
|
+
def test_guess_time_regexp_and_format_code_invalid(self) -> None:
|
|
49
|
+
assert guess_time_regexp_and_format_code("not-a-time") is None
|
|
50
|
+
|
|
36
51
|
def test_create_config_file_if_needed(self, tmp_path: Path) -> None:
|
|
37
52
|
|
|
38
53
|
home_config = tmp_path / "drain3.ini"
|
|
@@ -183,7 +198,7 @@ class TestNormalOutput:
|
|
|
183
198
|
"--time-format",
|
|
184
199
|
"%a %b %d %H:%M:%S %Y",
|
|
185
200
|
"--time-pattern",
|
|
186
|
-
"(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4})",
|
|
201
|
+
"\\[(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4}).*",
|
|
187
202
|
"--time-min",
|
|
188
203
|
"Mon Dec 05 18:46:00 2005",
|
|
189
204
|
"--time-max",
|
|
@@ -225,7 +240,7 @@ class TestNormalOutput:
|
|
|
225
240
|
"--time-format",
|
|
226
241
|
"%a %b %d %H:%M:%S %Y",
|
|
227
242
|
"--time-pattern",
|
|
228
|
-
"(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4})",
|
|
243
|
+
"\\[(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4}).*",
|
|
229
244
|
"--time-min",
|
|
230
245
|
"Mon Dec 05 00:00:00 2005",
|
|
231
246
|
"--time-max",
|
|
@@ -279,6 +294,13 @@ class TestNormalOutput:
|
|
|
279
294
|
cmd = ["--unordered_time", "--time-max", "15:37:00,0"]
|
|
280
295
|
run_gool_on_file_from_cli_and_check(cmd, input_path, expected_path)
|
|
281
296
|
|
|
297
|
+
def test_time_filter_low_precision(self) -> None:
|
|
298
|
+
input_path = input_dir_path / "Crafted_logs.log"
|
|
299
|
+
print(input_path)
|
|
300
|
+
expected_path = output_dir_path / "Crafted_logs_time_120014.txt"
|
|
301
|
+
cmd = ["--time-min", "12:00:14"]
|
|
302
|
+
run_gool_on_file_from_cli_and_check(cmd, input_path, expected_path)
|
|
303
|
+
|
|
282
304
|
def test_version_cli_outputs_one_line(self) -> None:
|
|
283
305
|
original_argv = sys.argv.copy()
|
|
284
306
|
try:
|
|
@@ -11,7 +11,7 @@ from gool.logs_miner import ExceptionLineAfterMaxTime, LogsMiner
|
|
|
11
11
|
@pytest.fixture
|
|
12
12
|
def logs_miner_with_time_config() -> Any:
|
|
13
13
|
"""LogsMiner instance with time pattern and format configured."""
|
|
14
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
14
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
15
15
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
16
16
|
with patch("gool.logs_miner.TemplateMiner"), patch("gool.logs_miner.create_drain3_cfg", return_value=Mock()):
|
|
17
17
|
return LogsMiner(
|
|
@@ -40,7 +40,7 @@ class TestExtractTimeFromLine:
|
|
|
40
40
|
|
|
41
41
|
def test_extract_time_when_no_format(self) -> None:
|
|
42
42
|
"""Should return None when time_format is not set."""
|
|
43
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
43
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
44
44
|
with patch("gool.logs_miner.TemplateMiner"), patch("gool.logs_miner.create_drain3_cfg", return_value=Mock()):
|
|
45
45
|
logs_miner = LogsMiner(
|
|
46
46
|
drain3_cfg_file=Mock(),
|
|
@@ -90,7 +90,7 @@ class TestCheckLineOutTimeRange:
|
|
|
90
90
|
|
|
91
91
|
def test_returns_false_when_no_time_format(self) -> None:
|
|
92
92
|
"""Should return False when time_format is not set."""
|
|
93
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
93
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
94
94
|
with patch("gool.logs_miner.TemplateMiner"), patch("gool.logs_miner.create_drain3_cfg", return_value=Mock()):
|
|
95
95
|
logs_miner = LogsMiner(
|
|
96
96
|
drain3_cfg_file=Mock(),
|
|
@@ -102,16 +102,16 @@ class TestCheckLineOutTimeRange:
|
|
|
102
102
|
result = logs_miner._check_line_out_time_range("2024-01-15 10:30:45 Some log")
|
|
103
103
|
assert result is False
|
|
104
104
|
|
|
105
|
-
def
|
|
106
|
-
"""Should return
|
|
105
|
+
def test_returns_true_when_time_cannot_be_extracted(self, logs_miner_with_time_config: Any) -> None:
|
|
106
|
+
"""Should return True and log warning when time cannot be extracted."""
|
|
107
107
|
with patch("gool.logs_miner.logging") as mock_logging:
|
|
108
108
|
result = logs_miner_with_time_config._check_line_out_time_range("No time in this message")
|
|
109
|
-
assert result is
|
|
109
|
+
assert result is True
|
|
110
110
|
mock_logging.warning.assert_called()
|
|
111
111
|
|
|
112
112
|
def test_returns_true_when_time_before_min(self) -> None:
|
|
113
113
|
"""Should return True when line time is before time_min."""
|
|
114
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
114
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
115
115
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
116
116
|
time_min = datetime(2024, 1, 15, 12, 0, 0)
|
|
117
117
|
|
|
@@ -131,7 +131,7 @@ class TestCheckLineOutTimeRange:
|
|
|
131
131
|
|
|
132
132
|
def test_returns_false_when_time_after_min(self) -> None:
|
|
133
133
|
"""Should return False when line time is after time_min."""
|
|
134
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
134
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
135
135
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
136
136
|
time_min = datetime(2024, 1, 15, 10, 0, 0)
|
|
137
137
|
|
|
@@ -151,7 +151,7 @@ class TestCheckLineOutTimeRange:
|
|
|
151
151
|
|
|
152
152
|
def test_raises_exception_when_time_after_max_ordered(self) -> None:
|
|
153
153
|
"""Should raise ExceptionLineAfterMaxTime when time > time_max and unordered_time is False."""
|
|
154
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
154
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
155
155
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
156
156
|
time_max = datetime(2024, 1, 15, 10, 0, 0)
|
|
157
157
|
|
|
@@ -172,7 +172,7 @@ class TestCheckLineOutTimeRange:
|
|
|
172
172
|
|
|
173
173
|
def test_returns_true_when_time_after_max_unordered(self) -> None:
|
|
174
174
|
"""Should return True when time > time_max and unordered_time is True."""
|
|
175
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
175
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
176
176
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
177
177
|
time_max = datetime(2024, 1, 15, 10, 0, 0)
|
|
178
178
|
|
|
@@ -193,7 +193,7 @@ class TestCheckLineOutTimeRange:
|
|
|
193
193
|
|
|
194
194
|
def test_returns_false_when_time_before_max(self) -> None:
|
|
195
195
|
"""Should return False when line time is before time_max."""
|
|
196
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
196
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
197
197
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
198
198
|
time_max = datetime(2024, 1, 15, 12, 0, 0)
|
|
199
199
|
|
|
@@ -213,7 +213,7 @@ class TestCheckLineOutTimeRange:
|
|
|
213
213
|
|
|
214
214
|
def test_returns_false_when_time_within_range(self) -> None:
|
|
215
215
|
"""Should return False when line time is within min and max range."""
|
|
216
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
216
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
217
217
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
218
218
|
time_min = datetime(2024, 1, 15, 10, 0, 0)
|
|
219
219
|
time_max = datetime(2024, 1, 15, 12, 0, 0)
|
|
@@ -235,7 +235,7 @@ class TestCheckLineOutTimeRange:
|
|
|
235
235
|
|
|
236
236
|
def test_returns_false_when_time_equal_to_min(self) -> None:
|
|
237
237
|
"""Should return False when line time equals time_min."""
|
|
238
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
238
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
239
239
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
240
240
|
time_min = datetime(2024, 1, 15, 10, 30, 45)
|
|
241
241
|
|
|
@@ -255,7 +255,7 @@ class TestCheckLineOutTimeRange:
|
|
|
255
255
|
|
|
256
256
|
def test_returns_false_when_time_equal_to_max(self) -> None:
|
|
257
257
|
"""Should return False when line time equals time_max."""
|
|
258
|
-
time_pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
|
258
|
+
time_pattern = re.compile(r".*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*")
|
|
259
259
|
time_format = "%Y-%m-%d %H:%M:%S"
|
|
260
260
|
time_max = datetime(2024, 1, 15, 10, 30, 45)
|
|
261
261
|
|
|
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
|