@@ -175,6 +175,130 @@ void testIncludeRunInPerBuildMetricsBothLimits() {
175175 }
176176 }
177177
178+ @ Test
179+ void testIncludeRunInPerBuildMetricsMaxBuildsEdgeCases () {
180+ // Test edge cases for max builds limit
181+ try (MockedStatic <PrometheusConfiguration > prometheusConfigurationStatic = mockStatic (PrometheusConfiguration .class )) {
182+ PrometheusConfiguration configuration = mock (PrometheusConfiguration .class );
183+ when (configuration .getPerBuildMetricsMaxAgeInHours ()).thenReturn (0L );
184+ when (configuration .getPerBuildMetricsMaxBuilds ()).thenReturn (1 );
185+ prometheusConfigurationStatic .when (PrometheusConfiguration ::get ).thenReturn (configuration );
186+
187+ // Only first build (index 0) should be included when maxBuilds = 1
188+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ));
189+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , 1 ));
190+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , 2 ));
191+ }
192+ }
193+
194+ @ Test
195+ void testIncludeRunInPerBuildMetricsMaxAgeExactBoundary () {
196+ // Test exact boundary for max age (exactly 24 hours ago)
197+ try (MockedStatic <PrometheusConfiguration > prometheusConfigurationStatic = mockStatic (PrometheusConfiguration .class )) {
198+ PrometheusConfiguration configuration = mock (PrometheusConfiguration .class );
199+ when (configuration .getPerBuildMetricsMaxAgeInHours ()).thenReturn (24L );
200+ when (configuration .getPerBuildMetricsMaxBuilds ()).thenReturn (0 );
201+ prometheusConfigurationStatic .when (PrometheusConfiguration ::get ).thenReturn (configuration );
202+
203+ long now = System .currentTimeMillis ();
204+ long buildDuration = 0L ; // instant build
205+ when (mockedRun .getDuration ()).thenReturn (buildDuration );
206+
207+ // Build that ended just under 24 hours ago should be included
208+ // Using 23 hours 59 minutes to ensure it's within the limit
209+ long justUnder24HoursAgo = now - (23L * 60L * 60L * 1000L ) - (59L * 60L * 1000L );
210+ when (mockedRun .getTimeInMillis ()).thenReturn (justUnder24HoursAgo );
211+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ));
212+
213+ // Build that ended 25 hours ago should be excluded
214+ long over24HoursAgo = now - (25L * 60L * 60L * 1000L );
215+ when (mockedRun .getTimeInMillis ()).thenReturn (over24HoursAgo );
216+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ));
217+ }
218+ }
219+
220+ @ Test
221+ void testIncludeRunInPerBuildMetricsZeroDuration () {
222+ // Test with zero duration builds
223+ try (MockedStatic <PrometheusConfiguration > prometheusConfigurationStatic = mockStatic (PrometheusConfiguration .class )) {
224+ PrometheusConfiguration configuration = mock (PrometheusConfiguration .class );
225+ when (configuration .getPerBuildMetricsMaxAgeInHours ()).thenReturn (1L ); // 1 hour
226+ when (configuration .getPerBuildMetricsMaxBuilds ()).thenReturn (0 );
227+ prometheusConfigurationStatic .when (PrometheusConfiguration ::get ).thenReturn (configuration );
228+
229+ long now = System .currentTimeMillis ();
230+ when (mockedRun .getDuration ()).thenReturn (0L ); // instant build
231+
232+ // Build that started 30 minutes ago with 0 duration (ended 30 minutes ago)
233+ when (mockedRun .getTimeInMillis ()).thenReturn (now - 1800000L );
234+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ));
235+
236+ // Build that started 2 hours ago with 0 duration (ended 2 hours ago)
237+ when (mockedRun .getTimeInMillis ()).thenReturn (now - 7200000L );
238+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ));
239+ }
240+ }
241+
242+ @ Test
243+ void testIncludeRunInPerBuildMetricsSequentialBuildIndices () {
244+ // Simulates JobCollector iterating through builds with incrementing indices
245+ // This covers the buildIndex++ logic in JobCollector lines 262-277
246+ try (MockedStatic <PrometheusConfiguration > prometheusConfigurationStatic = mockStatic (PrometheusConfiguration .class )) {
247+ PrometheusConfiguration configuration = mock (PrometheusConfiguration .class );
248+ when (configuration .getPerBuildMetricsMaxAgeInHours ()).thenReturn (0L );
249+ when (configuration .getPerBuildMetricsMaxBuilds ()).thenReturn (3 );
250+ prometheusConfigurationStatic .when (PrometheusConfiguration ::get ).thenReturn (configuration );
251+
252+ // Simulate iterating through builds like JobCollector does
253+ int buildIndex = 0 ;
254+
255+ // First iteration - index 0
256+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , buildIndex ));
257+ buildIndex ++;
258+
259+ // Second iteration - index 1
260+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , buildIndex ));
261+ buildIndex ++;
262+
263+ // Third iteration - index 2 (last included)
264+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , buildIndex ));
265+ buildIndex ++;
266+
267+ // Fourth iteration - index 3 (should be excluded)
268+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , buildIndex ));
269+ buildIndex ++;
270+
271+ // Fifth iteration - index 4 (should be excluded)
272+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , buildIndex ));
273+ }
274+ }
275+
276+ @ Test
277+ void testIncludeRunInPerBuildMetricsLongRunningBuild () {
278+ // Test with a long-running build where start time is old but end time is recent
279+ try (MockedStatic <PrometheusConfiguration > prometheusConfigurationStatic = mockStatic (PrometheusConfiguration .class )) {
280+ PrometheusConfiguration configuration = mock (PrometheusConfiguration .class );
281+ when (configuration .getPerBuildMetricsMaxAgeInHours ()).thenReturn (24L );
282+ when (configuration .getPerBuildMetricsMaxBuilds ()).thenReturn (0 );
283+ prometheusConfigurationStatic .when (PrometheusConfiguration ::get ).thenReturn (configuration );
284+
285+ long now = System .currentTimeMillis ();
286+ long buildDuration = 48L * 60L * 60L * 1000L ; // 48 hours build duration
287+ when (mockedRun .getDuration ()).thenReturn (buildDuration );
288+
289+ // Build started 50 hours ago but ran for 48 hours, so ended 2 hours ago
290+ // End time = start time + duration = (now - 50h) + 48h = now - 2h
291+ when (mockedRun .getTimeInMillis ()).thenReturn (now - (50L * 60L * 60L * 1000L ));
292+ assertTrue (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ),
293+ "Long-running build that ended recently should be included" );
294+
295+ // Build started 100 hours ago but ran for 48 hours, so ended 52 hours ago
296+ when (mockedRun .getTimeInMillis ()).thenReturn (now - (100L * 60L * 60L * 1000L ));
297+ assertFalse (Runs .includeRunInPerBuildMetrics (mockedRun , 0 ),
298+ "Long-running build that ended long ago should be excluded" );
299+ }
300+ }
301+
178302
179303 private static Stream <Arguments > provideBuildResults () {
180304 return Stream .of (
0 commit comments