@@ -34,6 +34,8 @@ struct perf_entry {
3434 double minimum = -1.0 ;
3535 double maximum = -1.0 ;
3636 std::string unit;
37+ std::string warning_range;
38+ std::string critical_range;
3739 bool has_warning = false ;
3840 bool has_critical = false ;
3941 bool has_minimum = false ;
@@ -72,6 +74,8 @@ struct test_builder final : parsers::perfdata::builder {
7274 current_.has_maximum = true ;
7375 }
7476 void set_unit (const std::string& value) override { current_.unit = value; }
77+ void set_warning_range (const std::string& range) override { current_.warning_range = range; }
78+ void set_critical_range (const std::string& range) override { current_.critical_range = range; }
7579 void next () override {
7680 entries.push_back (current_);
7781 current_ = perf_entry ();
@@ -377,3 +381,73 @@ TEST_F(PerfDataParserTest, ValueIsOnlyUnit) {
377381 ASSERT_EQ (1 , b->entries .size ());
378382 EXPECT_DOUBLE_EQ (0.0 , b->entries [0 ].value );
379383}
384+
385+ // ==============================================================
386+ // is_threshold_range
387+ // ==============================================================
388+
389+ TEST (IsThresholdRange, PlainNumberIsNotARange) { EXPECT_FALSE (parsers::perfdata::is_threshold_range (" 10" )); }
390+ TEST (IsThresholdRange, NumberWithUnitIsNotARange) { EXPECT_FALSE (parsers::perfdata::is_threshold_range (" 10s" )); }
391+ TEST (IsThresholdRange, EmptyIsNotARange) { EXPECT_FALSE (parsers::perfdata::is_threshold_range (" " )); }
392+ TEST (IsThresholdRange, ColonInsideIsRange) { EXPECT_TRUE (parsers::perfdata::is_threshold_range (" 4:5" )); }
393+ TEST (IsThresholdRange, ColonPrefixIsRange) { EXPECT_TRUE (parsers::perfdata::is_threshold_range (" :10" )); }
394+ TEST (IsThresholdRange, ColonSuffixIsRange) { EXPECT_TRUE (parsers::perfdata::is_threshold_range (" 10:" )); }
395+ TEST (IsThresholdRange, InvertedPrefixIsRange) { EXPECT_TRUE (parsers::perfdata::is_threshold_range (" @10:20" )); }
396+ TEST (IsThresholdRange, NegativeInfinityPrefixIsRange) { EXPECT_TRUE (parsers::perfdata::is_threshold_range (" ~:10" )); }
397+
398+ // ==============================================================
399+ // parse — threshold ranges (issue #748)
400+ // ==============================================================
401+
402+ // The parser must preserve the original range string AND set the numeric
403+ // lower bound so consumers that read only the float don't regress.
404+ TEST_F (PerfDataParserTest, RangeWarningPreservesColon) {
405+ parsers::perfdata::parse (b, " 'FOO'=10;4:5;6:9" );
406+ ASSERT_EQ (1 , b->entries .size ());
407+ EXPECT_EQ (" FOO" , b->entries [0 ].alias );
408+ EXPECT_DOUBLE_EQ (10.0 , b->entries [0 ].value );
409+ // Numeric back-compat: lower bound of the range.
410+ EXPECT_TRUE (b->entries [0 ].has_warning );
411+ EXPECT_DOUBLE_EQ (4.0 , b->entries [0 ].warning );
412+ EXPECT_TRUE (b->entries [0 ].has_critical );
413+ EXPECT_DOUBLE_EQ (6.0 , b->entries [0 ].critical );
414+ // Full range syntax preserved.
415+ EXPECT_EQ (" 4:5" , b->entries [0 ].warning_range );
416+ EXPECT_EQ (" 6:9" , b->entries [0 ].critical_range );
417+ }
418+
419+ TEST_F (PerfDataParserTest, RangeWarningOpenBounds) {
420+ // 10: = "alert if value < 10", :20 = "alert if value > 20"
421+ parsers::perfdata::parse (b, " 'x'=1;10:;:20" );
422+ ASSERT_EQ (1 , b->entries .size ());
423+ EXPECT_EQ (" 10:" , b->entries [0 ].warning_range );
424+ EXPECT_EQ (" :20" , b->entries [0 ].critical_range );
425+ }
426+
427+ TEST_F (PerfDataParserTest, RangeInvertedAndInfinity) {
428+ parsers::perfdata::parse (b, " 'x'=1;@10:20;~:30" );
429+ ASSERT_EQ (1 , b->entries .size ());
430+ EXPECT_EQ (" @10:20" , b->entries [0 ].warning_range );
431+ EXPECT_EQ (" ~:30" , b->entries [0 ].critical_range );
432+ }
433+
434+ TEST_F (PerfDataParserTest, RangeWithUnitSurvives) {
435+ parsers::perfdata::parse (b, " 'x'=5s;4:6;7:8" );
436+ ASSERT_EQ (1 , b->entries .size ());
437+ EXPECT_DOUBLE_EQ (5.0 , b->entries [0 ].value );
438+ EXPECT_EQ (" s" , b->entries [0 ].unit );
439+ EXPECT_EQ (" 4:6" , b->entries [0 ].warning_range );
440+ EXPECT_EQ (" 7:8" , b->entries [0 ].critical_range );
441+ }
442+
443+ // Important regression guard: plain-numeric thresholds must NOT set the
444+ // range fields. Otherwise we'd pollute the wire with empty-looking ranges
445+ // for every existing producer.
446+ TEST_F (PerfDataParserTest, PlainNumericThresholdsDoNotSetRange) {
447+ parsers::perfdata::parse (b, " 'x'=1;5;10" );
448+ ASSERT_EQ (1 , b->entries .size ());
449+ EXPECT_DOUBLE_EQ (5.0 , b->entries [0 ].warning );
450+ EXPECT_DOUBLE_EQ (10.0 , b->entries [0 ].critical );
451+ EXPECT_TRUE (b->entries [0 ].warning_range .empty ());
452+ EXPECT_TRUE (b->entries [0 ].critical_range .empty ());
453+ }
0 commit comments