|
106 | 106 | import java.util.List; |
107 | 107 | import java.util.Locale; |
108 | 108 |
|
| 109 | +import java.util.Optional; |
109 | 110 | import org.testng.annotations.BeforeMethod; |
110 | 111 | import org.testng.annotations.DataProvider; |
111 | 112 | import org.testng.annotations.Test; |
@@ -1225,6 +1226,83 @@ public void plusNanos_long_overflowTooSmall() { |
1225 | 1226 | t.plusNanos(-1); |
1226 | 1227 | } |
1227 | 1228 |
|
| 1229 | + @DataProvider(name = "PlusSaturating") |
| 1230 | + Object[][] provider_plusSaturating() { |
| 1231 | + return new Object[][]{ |
| 1232 | + // 1. {edge or constant instants} x {edge or constant durations} |
| 1233 | + {Instant.MIN, Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1234 | + {Instant.MIN, Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1235 | + {Instant.MIN, Duration.ZERO, Optional.empty()}, |
| 1236 | + {Instant.MIN, Duration.ofSeconds(Long.MAX_VALUE, 999_999_999), Optional.of(Instant.MAX)}, |
| 1237 | + {Instant.EPOCH, Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1238 | + {Instant.EPOCH, Duration.ZERO, Optional.empty()}, |
| 1239 | + {Instant.EPOCH, Duration.ofSeconds(Long.MAX_VALUE, 999_999_999), Optional.of(Instant.MAX)}, |
| 1240 | + {Instant.MAX, Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1241 | + {Instant.MAX, Duration.ZERO, Optional.empty()}, |
| 1242 | + {Instant.MAX, Duration.ofSeconds(Long.MAX_VALUE, 999_999_999), Optional.of(Instant.MAX)}, |
| 1243 | + // 2. {edge or constant instants} x {normal durations} |
| 1244 | + {Instant.MIN, Duration.ofDays(-32), Optional.of(Instant.MIN)}, |
| 1245 | + {Instant.MIN, Duration.ofDays(32), Optional.empty()}, |
| 1246 | + {Instant.EPOCH, Duration.ofDays(-32), Optional.empty()}, |
| 1247 | + {Instant.EPOCH, Duration.ofDays(32), Optional.empty()}, |
| 1248 | + {Instant.MAX, Duration.ofDays(-32), Optional.empty()}, |
| 1249 | + {Instant.MAX, Duration.ofDays(32), Optional.of(Instant.MAX)}, |
| 1250 | + // 3. {normal instants with both positive and negative epoch seconds} x {edge or constant durations} |
| 1251 | + {Instant.parse("1950-01-01T00:00:00Z"), Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1252 | + {Instant.parse("1950-01-01T00:00:00Z"), Duration.ZERO, Optional.empty()}, |
| 1253 | + {Instant.parse("1950-01-01T00:00:00Z"), Duration.ofSeconds(Long.MAX_VALUE, 999_999_999), Optional.of(Instant.MAX)}, |
| 1254 | + {Instant.parse("1990-01-01T00:00:00Z"), Duration.ofSeconds(Long.MIN_VALUE, 0), Optional.of(Instant.MIN)}, |
| 1255 | + {Instant.parse("1990-01-01T00:00:00Z"), Duration.ZERO, Optional.empty()}, |
| 1256 | + {Instant.parse("1990-01-01T00:00:00Z"), Duration.ofSeconds(Long.MAX_VALUE, 999_999_999), Optional.of(Instant.MAX)}, |
| 1257 | + // 4. {normal instants with both positive and negative epoch seconds} x {normal durations} |
| 1258 | + {Instant.parse("1950-01-01T00:00:00Z"), Duration.ofDays(-32), Optional.empty()}, |
| 1259 | + {Instant.parse("1950-01-01T00:00:00Z"), Duration.ofDays(32), Optional.empty()}, |
| 1260 | + {Instant.parse("1990-01-01T00:00:00Z"), Duration.ofDays(-32), Optional.empty()}, |
| 1261 | + {Instant.parse("1990-01-01T00:00:00Z"), Duration.ofDays(32), Optional.empty()}, |
| 1262 | + // 5. instant boundary |
| 1263 | + {Instant.MIN, Duration.between(Instant.MIN, Instant.MAX), Optional.of(Instant.MAX)}, |
| 1264 | + {Instant.EPOCH, Duration.between(Instant.EPOCH, Instant.MAX), Optional.of(Instant.MAX)}, |
| 1265 | + {Instant.EPOCH, Duration.between(Instant.EPOCH, Instant.MIN), Optional.of(Instant.MIN)}, |
| 1266 | + {Instant.MAX, Duration.between(Instant.MAX, Instant.MIN), Optional.of(Instant.MIN)} |
| 1267 | + }; |
| 1268 | + } |
| 1269 | + |
| 1270 | + @Test(dataProvider = "PlusSaturating") |
| 1271 | + public void plusSaturating(Instant i, Duration d, Optional<Instant> value) { |
| 1272 | + var actual = i.plusSaturating(d); |
| 1273 | + try { |
| 1274 | + assertEquals(actual, i.plus(d)); |
| 1275 | + // If `value` is present, perform an additional check. It may be |
| 1276 | + // important to ensure that not only does the result of `plusSaturating` |
| 1277 | + // match that of `plus`, but that it also matches our expectation. |
| 1278 | + // Because if it doesn’t, then the test isn’t testing what we think |
| 1279 | + // it is, and needs to be fixed. |
| 1280 | + value.ifPresent(instant -> assertEquals(actual, instant)); |
| 1281 | + } catch (DateTimeException /* instant overflow */ |
| 1282 | + | ArithmeticException /* long overflow */ e) { |
| 1283 | + if (value.isEmpty()) { |
| 1284 | + throw new AssertionError(); |
| 1285 | + } |
| 1286 | + assertEquals(actual, value.get()); |
| 1287 | + } |
| 1288 | + } |
| 1289 | + |
| 1290 | + @DataProvider(name = "PlusSaturating_null") |
| 1291 | + Object[][] provider_plusSaturating_null() { |
| 1292 | + return new Object[][]{ |
| 1293 | + {Instant.MIN}, |
| 1294 | + {Instant.EPOCH}, |
| 1295 | + {Instant.MAX}, |
| 1296 | + // any non-random but also non-special instant |
| 1297 | + {Instant.parse("2025-10-13T20:47:50.369955Z")}, |
| 1298 | + }; |
| 1299 | + } |
| 1300 | + |
| 1301 | + @Test(expectedExceptions = NullPointerException.class, dataProvider = "PlusSaturating_null") |
| 1302 | + public void test_plusSaturating_null(Instant i) { |
| 1303 | + i.plusSaturating(null); |
| 1304 | + } |
| 1305 | + |
1228 | 1306 | //----------------------------------------------------------------------- |
1229 | 1307 | @DataProvider(name="Minus") |
1230 | 1308 | Object[][] provider_minus() { |
|
0 commit comments