Skip to content

Commit 3c5bb91

Browse files
committed
Move formula validation tests into a separate file
Signed-off-by: Simon Völcker <simon.voelcker@frequenz.com>
1 parent e000b05 commit 3c5bb91

2 files changed

Lines changed: 185 additions & 175 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# License: MIT
2+
# Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the Formula implementation."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
from frequenz.quantities import Quantity
10+
11+
from frequenz.sdk.timeseries.formulas._exceptions import FormulaSyntaxError
12+
from frequenz.sdk.timeseries.formulas._parser import parse
13+
14+
15+
@pytest.mark.parametrize(
16+
("formula_str", "parsed_formula_str"),
17+
[
18+
("#1", "[f](#1)"),
19+
("-(1+#1)", "[f](0.0 - (1.0 + #1))"),
20+
("1*(2+3)", "[f](1.0 * (2.0 + 3.0))"),
21+
],
22+
)
23+
async def test_parser_validation(
24+
formula_str: str,
25+
parsed_formula_str: str,
26+
) -> None:
27+
"""Test formula parser validation."""
28+
try:
29+
formula = parse(
30+
name="f",
31+
formula=formula_str,
32+
create_method=Quantity,
33+
telemetry_fetcher=Mock(),
34+
)
35+
assert str(formula) == parsed_formula_str
36+
except FormulaSyntaxError:
37+
assert False, "Parser should not raise an error for this formula"
38+
39+
40+
@pytest.mark.parametrize(
41+
("formula_str", "expected_error_line"),
42+
[
43+
(
44+
"1++",
45+
" ^ Expected expression",
46+
),
47+
(
48+
"1**",
49+
" ^ Expected expression",
50+
),
51+
(
52+
"--1",
53+
" ^ Expected expression",
54+
),
55+
(
56+
"(",
57+
" ^ Expected expression",
58+
),
59+
(
60+
"(1",
61+
"^ Unmatched parenthesis",
62+
),
63+
(
64+
"max",
65+
" ^ Expected '(' after function name",
66+
),
67+
(
68+
"max()",
69+
" ^ Expected argument",
70+
),
71+
(
72+
"max(1(",
73+
" ^ Expected ',' or ')'",
74+
),
75+
(
76+
"max(1",
77+
" ^ Unmatched parenthesis",
78+
),
79+
(
80+
"foo",
81+
"^^^ Unknown function name",
82+
),
83+
(
84+
"foo(1)",
85+
"^^^ Unknown function name",
86+
),
87+
(
88+
"max(1,,2)",
89+
" ^ Expected argument",
90+
),
91+
(
92+
"1 2",
93+
" ^ Unexpected token",
94+
),
95+
(
96+
"1, 2",
97+
" ^ Unexpected token",
98+
),
99+
(
100+
"max(1, 2,)",
101+
" ^ Expected argument",
102+
),
103+
(
104+
"max(1, 2))",
105+
" ^ Unexpected token",
106+
),
107+
(
108+
"max(1, 2),",
109+
" ^ Unexpected token",
110+
),
111+
],
112+
)
113+
async def test_parser_validation_errors(
114+
formula_str: str, expected_error_line: str
115+
) -> None:
116+
"""Test formula parser validation."""
117+
with pytest.raises(FormulaSyntaxError) as error:
118+
_ = parse(
119+
name="f",
120+
formula=formula_str,
121+
create_method=Quantity,
122+
telemetry_fetcher=Mock(),
123+
)
124+
125+
assert str(error.value) == (
126+
"Formula syntax error:\n"
127+
f" Formula: {formula_str}\n"
128+
f" {expected_error_line}"
129+
)
130+
131+
132+
@pytest.mark.parametrize(
133+
("formula_str", "expected_error"),
134+
[
135+
# Long formula with error near start -> Ellipsize end
136+
(
137+
"max(coalesce(#1001, %1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#1007, #1008, 0))", # noqa: E501
138+
"Formula syntax error:\n"
139+
" Formula: max(coalesce(#1001, %1002, 0), coalesce(#1003, #1004, 0), coalesc ...\n"
140+
" ^ Unexpected character",
141+
),
142+
# Long formula with error near the end -> Ellipsize start
143+
(
144+
"max(coalesce(#1001, #1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#10.07, #1008, 0))", # noqa: E501
145+
"Formula syntax error:\n"
146+
" Formula: ... 0), coalesce(#1005, #1006, 0), coalesce(#10.07, #1008, 0))\n"
147+
" ^ Unexpected character",
148+
),
149+
# Very long formula with error in the middle -> Ellipsize both sides
150+
(
151+
"max(coalesce(#1001, #1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#1007, #1008, 0)) :) " # noqa: E501
152+
"min(coalesce(#2001, #2002, 0), coalesce(#2003, #2004, 0), coalesce(#2005, #2006, 0), coalesce(#2007, #2008, 0))", # noqa: E501
153+
"Formula syntax error:\n"
154+
" Formula: ... 005, #1006, 0), coalesce(#1007, #1008, 0)) :) min(coalesce(#2 ...\n"
155+
" ^ Unexpected character",
156+
),
157+
],
158+
)
159+
async def test_parser_validation_errors_in_long_formulas(
160+
formula_str: str, expected_error: str
161+
) -> None:
162+
"""Test formula parser validation for long formulas."""
163+
with pytest.raises(FormulaSyntaxError) as error:
164+
_ = parse(
165+
name="f",
166+
formula=formula_str,
167+
create_method=Quantity,
168+
telemetry_fetcher=Mock(),
169+
)
170+
171+
assert str(error.value) == expected_error
172+
assert all(len(line) <= 80 for line in str(error.value).splitlines())
173+
174+
175+
async def test_empty_formula() -> None:
176+
"""Test formula parser validation."""
177+
with pytest.raises(FormulaSyntaxError) as error:
178+
_ = parse(
179+
name="f",
180+
formula="",
181+
create_method=Quantity,
182+
telemetry_fetcher=Mock(),
183+
)
184+
185+
assert str(error.value) == "Empty formula"

tests/timeseries/_formulas/test_formulas.py

Lines changed: 0 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from frequenz.quantities import Quantity
1919

2020
from frequenz.sdk.timeseries import Sample
21-
from frequenz.sdk.timeseries.formulas._exceptions import FormulaSyntaxError
2221
from frequenz.sdk.timeseries.formulas._formula import Formula, FormulaBuilder
2322
from frequenz.sdk.timeseries.formulas._parser import parse
2423
from frequenz.sdk.timeseries.formulas._resampled_stream_fetcher import (
@@ -345,180 +344,6 @@ async def test_max_min_coalesce(self) -> None:
345344
)
346345

347346

348-
class TestFormulaValidation:
349-
"""Tests for Formula validation."""
350-
351-
@pytest.mark.parametrize(
352-
("formula_str", "parsed_formula_str"),
353-
[
354-
("#1", "[f](#1)"),
355-
("-(1+#1)", "[f](0.0 - (1.0 + #1))"),
356-
("1*(2+3)", "[f](1.0 * (2.0 + 3.0))"),
357-
],
358-
)
359-
async def test_parser_validation(
360-
self,
361-
formula_str: str,
362-
parsed_formula_str: str,
363-
) -> None:
364-
"""Test formula parser validation."""
365-
try:
366-
formula = parse(
367-
name="f",
368-
formula=formula_str,
369-
create_method=Quantity,
370-
telemetry_fetcher=MagicMock(spec=ResampledStreamFetcher),
371-
)
372-
assert str(formula) == parsed_formula_str
373-
except FormulaSyntaxError:
374-
assert False, "Parser should not raise an error for this formula"
375-
376-
@pytest.mark.parametrize(
377-
("formula_str", "expected_error_line"),
378-
[
379-
(
380-
"1++",
381-
" ^ Expected expression",
382-
),
383-
(
384-
"1**",
385-
" ^ Expected expression",
386-
),
387-
(
388-
"--1",
389-
" ^ Expected expression",
390-
),
391-
(
392-
"(",
393-
" ^ Expected expression",
394-
),
395-
(
396-
"(1",
397-
"^ Unmatched parenthesis",
398-
),
399-
(
400-
"max",
401-
" ^ Expected '(' after function name",
402-
),
403-
(
404-
"max()",
405-
" ^ Expected argument",
406-
),
407-
(
408-
"max(1(",
409-
" ^ Expected ',' or ')'",
410-
),
411-
(
412-
"max(1",
413-
" ^ Unmatched parenthesis",
414-
),
415-
(
416-
"foo",
417-
"^^^ Unknown function name",
418-
),
419-
(
420-
"foo(1)",
421-
"^^^ Unknown function name",
422-
),
423-
(
424-
"max(1,,2)",
425-
" ^ Expected argument",
426-
),
427-
(
428-
"1 2",
429-
" ^ Unexpected token",
430-
),
431-
(
432-
"1, 2",
433-
" ^ Unexpected token",
434-
),
435-
(
436-
"max(1, 2,)",
437-
" ^ Expected argument",
438-
),
439-
(
440-
"max(1, 2))",
441-
" ^ Unexpected token",
442-
),
443-
(
444-
"max(1, 2),",
445-
" ^ Unexpected token",
446-
),
447-
],
448-
)
449-
async def test_parser_validation_errors(
450-
self, formula_str: str, expected_error_line: str
451-
) -> None:
452-
"""Test formula parser validation."""
453-
with pytest.raises(FormulaSyntaxError) as error:
454-
_ = parse(
455-
name="f",
456-
formula=formula_str,
457-
create_method=Quantity,
458-
telemetry_fetcher=MagicMock(spec=ResampledStreamFetcher),
459-
)
460-
461-
assert str(error.value) == (
462-
"Formula syntax error:\n"
463-
f" Formula: {formula_str}\n"
464-
f" {expected_error_line}"
465-
)
466-
467-
@pytest.mark.parametrize(
468-
("formula_str", "expected_error"),
469-
[
470-
# Long formula with error near start -> Ellipsize end
471-
(
472-
"max(coalesce(#1001, %1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#1007, #1008, 0))", # noqa: E501
473-
"Formula syntax error:\n"
474-
" Formula: max(coalesce(#1001, %1002, 0), coalesce(#1003, #1004, 0), coalesc ...\n"
475-
" ^ Unexpected character",
476-
),
477-
# Long formula with error near the end -> Ellipsize start
478-
(
479-
"max(coalesce(#1001, #1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#10.07, #1008, 0))", # noqa: E501
480-
"Formula syntax error:\n"
481-
" Formula: ... 0), coalesce(#1005, #1006, 0), coalesce(#10.07, #1008, 0))\n"
482-
" ^ Unexpected character",
483-
),
484-
# Very long formula with error in the middle -> Ellipsize both sides
485-
(
486-
"max(coalesce(#1001, #1002, 0), coalesce(#1003, #1004, 0), coalesce(#1005, #1006, 0), coalesce(#1007, #1008, 0)) :) " # noqa: E501
487-
"min(coalesce(#2001, #2002, 0), coalesce(#2003, #2004, 0), coalesce(#2005, #2006, 0), coalesce(#2007, #2008, 0))", # noqa: E501
488-
"Formula syntax error:\n"
489-
" Formula: ... 005, #1006, 0), coalesce(#1007, #1008, 0)) :) min(coalesce(#2 ...\n"
490-
" ^ Unexpected character",
491-
),
492-
],
493-
)
494-
async def test_parser_validation_errors_in_long_formulas(
495-
self, formula_str: str, expected_error: str
496-
) -> None:
497-
"""Test formula parser validation for long formulas."""
498-
with pytest.raises(FormulaSyntaxError) as error:
499-
_ = parse(
500-
name="f",
501-
formula=formula_str,
502-
create_method=Quantity,
503-
telemetry_fetcher=MagicMock(spec=ResampledStreamFetcher),
504-
)
505-
506-
assert str(error.value) == expected_error
507-
assert all(len(line) <= 80 for line in str(error.value).splitlines())
508-
509-
async def test_empty_formula(self) -> None:
510-
"""Test formula parser validation."""
511-
with pytest.raises(FormulaSyntaxError) as error:
512-
_ = parse(
513-
name="f",
514-
formula="",
515-
create_method=Quantity,
516-
telemetry_fetcher=MagicMock(spec=ResampledStreamFetcher),
517-
)
518-
519-
assert str(error.value) == "Empty formula"
520-
521-
522347
class TestFormulaComposition:
523348
"""Tests for formula channels."""
524349

0 commit comments

Comments
 (0)