diff --git a/README.md b/README.md index d371819..a1e2a47 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build](https://github.com/rmontanana/mdlp/actions/workflows/build.yml/badge.svg)](https://github.com/rmontanana/mdlp/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=rmontanana_mdlp&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=rmontanana_mdlp) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=rmontanana_mdlp&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=rmontanana_mdlp) -[![Coverage Badge](https://img.shields.io/badge/Coverage-96,1%25-green)](html/index.html) +[![Coverage Badge](https://img.shields.io/badge/Coverage-100,0%25-green)](html/index.html) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/rmontanana/mdlp) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14245443.svg)](https://doi.org/10.5281/zenodo.14245443) diff --git a/src/BinDisc.cpp b/src/BinDisc.cpp index 096fddf..06d61ab 100644 --- a/src/BinDisc.cpp +++ b/src/BinDisc.cpp @@ -49,7 +49,7 @@ namespace mdlp { // Note: y parameter is validated but not used in binning strategy fit(X); } - std::vector linspace(precision_t start, precision_t end, int num) + std::vector BinDisc::linspace(precision_t start, precision_t end, int num) { // Input validation if (num < 2) { @@ -77,7 +77,7 @@ namespace mdlp { { return std::max(lower, std::min(n, upper)); } - std::vector percentile(samples_t& data, const std::vector& percentiles) + std::vector BinDisc::percentile(samples_t& data, const std::vector& percentiles) { // Input validation if (data.empty()) { diff --git a/src/BinDisc.h b/src/BinDisc.h index 0a082be..abe239e 100644 --- a/src/BinDisc.h +++ b/src/BinDisc.h @@ -23,6 +23,9 @@ namespace mdlp { // y is included for compatibility with the Discretizer interface void fit(samples_t& X_, labels_t& y) override; void fit(samples_t& X); + protected: + std::vector linspace(precision_t start, precision_t end, int num); + std::vector percentile(samples_t& data, const std::vector& percentiles); private: void fit_uniform(const samples_t&); void fit_quantile(const samples_t&); diff --git a/src/CPPFImdlp.h b/src/CPPFImdlp.h index 7a8c0ab..4c48198 100644 --- a/src/CPPFImdlp.h +++ b/src/CPPFImdlp.h @@ -39,8 +39,8 @@ namespace mdlp { size_t getCandidate(size_t, size_t); size_t compute_max_num_cut_points() const; pair valueCutPoint(size_t, size_t, size_t); - private: - inline precision_t safe_X_access(size_t idx) const { + inline precision_t safe_X_access(size_t idx) const + { if (idx >= indices.size()) { throw std::out_of_range("Index out of bounds for indices array"); } @@ -50,7 +50,8 @@ namespace mdlp { } return X[real_idx]; } - inline label_t safe_y_access(size_t idx) const { + inline label_t safe_y_access(size_t idx) const + { if (idx >= indices.size()) { throw std::out_of_range("Index out of bounds for indices array"); } @@ -60,7 +61,8 @@ namespace mdlp { } return y[real_idx]; } - inline size_t safe_subtract(size_t a, size_t b) const { + inline size_t safe_subtract(size_t a, size_t b) const + { if (b > a) { throw std::underflow_error("Subtraction would cause underflow"); } diff --git a/tests/BinDisc_unittest.cpp b/tests/BinDisc_unittest.cpp index 0102a45..5d43c7b 100644 --- a/tests/BinDisc_unittest.cpp +++ b/tests/BinDisc_unittest.cpp @@ -11,6 +11,16 @@ #include #include "BinDisc.h" #include "Experiments.hpp" +#include + +#define EXPECT_THROW_WITH_MESSAGE(stmt, etype, whatstring) EXPECT_THROW( \ +try { \ +stmt; \ +} catch (const etype& ex) { \ +EXPECT_EQ(whatstring, std::string(ex.what())); \ +throw; \ +} \ +, etype) namespace mdlp { const float margin = 1e-4; @@ -400,4 +410,64 @@ namespace mdlp { } // std::cout << "* Number of experiments tested: " << num << std::endl; } + + TEST_F(TestBinDisc3U, FitDataSizeTooSmall) + { + // Test when data size is smaller than n_bins + samples_t X = { 1.0, 2.0 }; // Only 2 elements for 3 bins + EXPECT_THROW_WITH_MESSAGE(fit(X), std::invalid_argument, "Input data size must be at least equal to n_bins"); + } + + TEST_F(TestBinDisc3Q, FitDataSizeTooSmall) + { + // Test when data size is smaller than n_bins + samples_t X = { 1.0, 2.0 }; // Only 2 elements for 3 bins + EXPECT_THROW_WITH_MESSAGE(fit(X), std::invalid_argument, "Input data size must be at least equal to n_bins"); + } + + TEST_F(TestBinDisc3U, FitWithYEmptyX) + { + // Test fit(X, y) with empty X + samples_t X = {}; + labels_t y = { 1, 2, 3 }; + EXPECT_THROW_WITH_MESSAGE(fit(X, y), std::invalid_argument, "X cannot be empty"); + } + + TEST_F(TestBinDisc3U, LinspaceInvalidNumPoints) + { + // Test linspace with num < 2 + EXPECT_THROW_WITH_MESSAGE(linspace(0.0f, 1.0f, 1), std::invalid_argument, "Number of points must be at least 2 for linspace"); + } + + TEST_F(TestBinDisc3U, LinspaceNaNValues) + { + // Test linspace with NaN values + float nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THROW_WITH_MESSAGE(linspace(nan_val, 1.0f, 3), std::invalid_argument, "Start and end values cannot be NaN"); + EXPECT_THROW_WITH_MESSAGE(linspace(0.0f, nan_val, 3), std::invalid_argument, "Start and end values cannot be NaN"); + } + + TEST_F(TestBinDisc3U, LinspaceInfiniteValues) + { + // Test linspace with infinite values + float inf_val = std::numeric_limits::infinity(); + EXPECT_THROW_WITH_MESSAGE(linspace(inf_val, 1.0f, 3), std::invalid_argument, "Start and end values cannot be infinite"); + EXPECT_THROW_WITH_MESSAGE(linspace(0.0f, inf_val, 3), std::invalid_argument, "Start and end values cannot be infinite"); + } + + TEST_F(TestBinDisc3U, PercentileEmptyData) + { + // Test percentile with empty data + samples_t empty_data = {}; + std::vector percentiles = { 25.0f, 50.0f, 75.0f }; + EXPECT_THROW_WITH_MESSAGE(percentile(empty_data, percentiles), std::invalid_argument, "Data cannot be empty for percentile calculation"); + } + + TEST_F(TestBinDisc3U, PercentileEmptyPercentiles) + { + // Test percentile with empty percentiles + samples_t data = { 1.0f, 2.0f, 3.0f }; + std::vector empty_percentiles = {}; + EXPECT_THROW_WITH_MESSAGE(percentile(data, empty_percentiles), std::invalid_argument, "Percentiles cannot be empty"); + } } diff --git a/tests/FImdlp_unittest.cpp b/tests/FImdlp_unittest.cpp index 6712d64..b364712 100644 --- a/tests/FImdlp_unittest.cpp +++ b/tests/FImdlp_unittest.cpp @@ -373,4 +373,55 @@ namespace mdlp { EXPECT_EQ(computed_ft[i], expected[i]); } } + TEST_F(TestFImdlp, SafeXAccessIndexOutOfBounds) + { + // Test safe_X_access with index out of bounds for indices array + X = { 1.0f, 2.0f, 3.0f }; + y = { 1, 2, 3 }; + indices = { 0, 1 }; // shorter than expected + + // This should trigger the first exception in safe_X_access (idx >= indices.size()) + EXPECT_THROW_WITH_MESSAGE(safe_X_access(2), std::out_of_range, "Index out of bounds for indices array"); + } + + TEST_F(TestFImdlp, SafeXAccessXOutOfBounds) + { + // Test safe_X_access with real_idx out of bounds for X array + X = { 1.0f, 2.0f }; // shorter array + y = { 1, 2, 3 }; + indices = { 0, 1, 5 }; // indices[2] = 5 is out of bounds for X + + // This should trigger the second exception in safe_X_access (real_idx >= X.size()) + EXPECT_THROW_WITH_MESSAGE(safe_X_access(2), std::out_of_range, "Index out of bounds for X array"); + } + + TEST_F(TestFImdlp, SafeYAccessIndexOutOfBounds) + { + // Test safe_y_access with index out of bounds for indices array + X = { 1.0f, 2.0f, 3.0f }; + y = { 1, 2, 3 }; + indices = { 0, 1 }; // shorter than expected + + // This should trigger the first exception in safe_y_access (idx >= indices.size()) + EXPECT_THROW_WITH_MESSAGE(safe_y_access(2), std::out_of_range, "Index out of bounds for indices array"); + } + + TEST_F(TestFImdlp, SafeYAccessYOutOfBounds) + { + // Test safe_y_access with real_idx out of bounds for y array + X = { 1.0f, 2.0f, 3.0f }; + y = { 1, 2 }; // shorter array + indices = { 0, 1, 5 }; // indices[2] = 5 is out of bounds for y + + // This should trigger the second exception in safe_y_access (real_idx >= y.size()) + EXPECT_THROW_WITH_MESSAGE(safe_y_access(2), std::out_of_range, "Index out of bounds for y array"); + } + + TEST_F(TestFImdlp, SafeSubtractUnderflow) + { + // Test safe_subtract with underflow condition (b > a) + EXPECT_THROW_WITH_MESSAGE(safe_subtract(3, 5), std::underflow_error, "Subtraction would cause underflow"); + } + + }