View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.time;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotEquals;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.IOException;
27  import java.time.Duration;
28  import java.time.Instant;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.concurrent.TimeUnit;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  import org.apache.commons.lang3.AbstractLangTest;
35  import org.apache.commons.lang3.ThreadUtils;
36  import org.apache.commons.lang3.reflect.FieldUtils;
37  import org.junit.jupiter.api.RepeatedTest;
38  import org.junit.jupiter.api.Test;
39  
40  /**
41   * Tests {@link StopWatch}.
42   */
43  class StopWatchTest extends AbstractLangTest {
44  
45      private static final int SPLIT_CLOCK_STR_LEN = 12;
46      private static final Duration MIN_DURATION = Duration.ofMillis(20);
47      private static final String MESSAGE = "Baking cookies";
48      private static final String ZERO_HOURS_PREFIX = "00:";
49      private static final String ZERO_TIME_ELAPSED = "00:00:00.000";
50  
51      /**
52       * <p>
53       * Creates a suspended StopWatch object which appears to have elapsed for the requested amount of time in nanoseconds.
54       * <p>
55       *
56       * <pre>
57       * // Create a mock StopWatch with a time of 2:59:01.999
58       * final long nanos = TimeUnit.HOURS.toNanos(2) + TimeUnit.MINUTES.toNanos(59) + TimeUnit.SECONDS.toNanos(1) + TimeUnit.MILLISECONDS.toNanos(999);
59       * final StopWatch watch = createMockStopWatch(nanos);
60       * </pre>
61       *
62       * @param nanos Time in nanoseconds to have elapsed on the stop watch
63       * @return StopWatch in a suspended state with the elapsed time
64       */
65      private StopWatch createMockStopWatch(final long nanos) {
66          final StopWatch watch = StopWatch.createStarted();
67          watch.suspend();
68          return set(watch, nanos);
69      }
70  
71      private StopWatch set(final StopWatch watch, final long nanos) {
72          try {
73              final long currentNanos = System.nanoTime();
74              final List<StopWatch.Split> splits = new ArrayList<>();
75              splits.add(new StopWatch.Split(String.valueOf(0), Duration.ofNanos(nanos)));
76              FieldUtils.writeField(watch, "startTimeNanos", currentNanos - nanos, true);
77              FieldUtils.writeField(watch, "stopTimeNanos", currentNanos, true);
78              FieldUtils.writeField(watch, "splits", splits, true);
79          } catch (final IllegalAccessException e) {
80              return null;
81          }
82          return watch;
83      }
84  
85      /**
86       * Sleeps the requested duration plus one millisecond. On Java 8, sleeping for 2 or 20 millis can sleep for a tiny bit less.
87       *
88       * @param duration How long to sleep.
89       * @throws InterruptedException if any thread has interrupted the current thread.
90       */
91      private void sleepPlus1(final Duration duration) throws InterruptedException {
92          ThreadUtils.sleep(duration.plusMillis(1));
93      }
94  
95      /**
96       * Tests bad states.
97       */
98      @Test
99      void testBadStates() {
100         final StopWatch watch = new StopWatch();
101         assertThrows(IllegalStateException.class, watch::stop, "Calling stop on an unstarted StopWatch should throw an exception.");
102         assertThrows(IllegalStateException.class, watch::suspend, "Calling suspend on an unstarted StopWatch should throw an exception.");
103         assertThrows(IllegalStateException.class, watch::split, "Calling split on a non-running StopWatch should throw an exception.");
104         assertThrows(IllegalStateException.class, watch::unsplit, "Calling unsplit on an unsplit StopWatch should throw an exception.");
105         assertThrows(IllegalStateException.class, watch::resume, "Calling resume on an unsuspended StopWatch should throw an exception.");
106         watch.start();
107         assertThrows(IllegalStateException.class, watch::start, "Calling start on a started StopWatch should throw an exception.");
108         assertThrows(IllegalStateException.class, watch::unsplit, "Calling unsplit on an unsplit StopWatch should throw an exception.");
109         assertThrows(IllegalStateException.class, watch::getSplitTime, "Calling getSplitTime on an unsplit StopWatch should throw an exception.");
110         assertThrows(IllegalStateException.class, watch::getSplitDuration, "Calling getSplitTime on an unsplit StopWatch should throw an exception.");
111         assertThrows(IllegalStateException.class, watch::resume, "Calling resume on an unsuspended StopWatch should throw an exception.");
112         watch.stop();
113         assertThrows(IllegalStateException.class, watch::start, "Calling start on a stopped StopWatch should throw an exception as it needs to be reset.");
114     }
115 
116     @Test
117     void testBooleanStates() {
118         final StopWatch watch = new StopWatch();
119         assertFalse(watch.isStarted());
120         assertFalse(watch.isSuspended());
121         assertTrue(watch.isStopped());
122 
123         watch.start();
124         assertTrue(watch.isStarted());
125         assertFalse(watch.isSuspended());
126         assertFalse(watch.isStopped());
127 
128         watch.suspend();
129         assertTrue(watch.isStarted());
130         assertTrue(watch.isSuspended());
131         assertFalse(watch.isStopped());
132 
133         watch.stop();
134         assertFalse(watch.isStarted());
135         assertFalse(watch.isSuspended());
136         assertTrue(watch.isStopped());
137     }
138 
139     @Test
140     void testFormatSplitTime() {
141         final StopWatch watch = StopWatch.createStarted();
142         ThreadUtils.sleepQuietly(MIN_DURATION);
143         watch.split();
144         final String formatSplitTime = watch.formatSplitTime();
145         assertNotEquals(ZERO_TIME_ELAPSED, formatSplitTime);
146         assertTrue(formatSplitTime.startsWith(ZERO_HOURS_PREFIX), "formatSplitTime");
147     }
148 
149     @Test
150     void testFormatSplitTimeWithMessage() {
151         final StopWatch watch = new StopWatch(MESSAGE);
152         watch.start();
153         ThreadUtils.sleepQuietly(MIN_DURATION);
154         watch.split();
155         final String formatSplitTime = watch.formatSplitTime();
156         assertFalse(formatSplitTime.startsWith(MESSAGE), "formatSplitTime");
157         assertTrue(formatSplitTime.startsWith(ZERO_HOURS_PREFIX), "formatSplitTime");
158     }
159 
160     @Test
161     void testFormatTime() {
162         final StopWatch watch = StopWatch.create();
163         final String formatTime = watch.formatTime();
164         assertEquals(ZERO_TIME_ELAPSED, formatTime);
165         assertTrue(formatTime.startsWith(ZERO_HOURS_PREFIX), "formatTime");
166     }
167 
168     @Test
169     void testFormatTimeWithMessage() {
170         final StopWatch watch = new StopWatch(MESSAGE);
171         final String formatTime = watch.formatTime();
172         assertFalse(formatTime.startsWith(MESSAGE), "formatTime");
173     }
174 
175     @Test
176     void testGet() throws Throwable {
177         final StopWatch watch = new StopWatch();
178         final AtomicInteger i = new AtomicInteger();
179         assertEquals(1, watch.get(i::incrementAndGet));
180         assertEquals(2, watch.getT(i::incrementAndGet));
181         final IOException e = assertThrows(IOException.class, () -> watch.getT(this::throwIOException));
182         assertEquals("A", e.getMessage());
183         // test state
184         assertTrue(watch.isSuspended());
185         assertEquals(3, watch.get(() -> {
186             assertTrue(watch.isStarted());
187             return i.incrementAndGet();
188         }));
189         assertTrue(watch.isSuspended());
190         final long nanos1 = watch.getDuration().toNanos();
191         assertTrue(nanos1 >= 0);
192         // test state
193         assertTrue(watch.isSuspended());
194         assertEquals(4, watch.getT(() -> {
195             assertTrue(watch.isStarted());
196             return i.incrementAndGet();
197         }));
198         assertTrue(watch.isSuspended());
199         assertTrue(watch.getDuration().toNanos() >= nanos1);
200     }
201 
202     @Test
203     void testGetDuration() throws InterruptedException {
204         final StopWatch watch = new StopWatch();
205         assertEquals(Duration.ZERO, watch.getDuration());
206         assertEquals(ZERO_TIME_ELAPSED, watch.toString());
207         watch.start();
208         sleepPlus1(MIN_DURATION);
209         final long nanos = watch.getNanoTime();
210         assertTrue(nanos > 0, () -> "getNanoTime(): " + nanos);
211         assertTrue(DurationUtils.isPositive(watch.getDuration()));
212     }
213 
214     @Test
215     void testGetSplitDuration() {
216         // Create a mock StopWatch with a time of 2:59:01.999
217         final StopWatch watch = StopWatch.createStarted();
218         watch.split();
219         set(watch, 123456);
220         assertEquals(Duration.ofNanos(123456), watch.getSplitDuration());
221     }
222 
223     @Test
224     void testGetSplits() {
225         final StopWatch stopWatch = StopWatch.create();
226         assertTrue(stopWatch.getSplits().isEmpty());
227         stopWatch.start();
228         testGetSplits(stopWatch);
229         testGetSplits(StopWatch.createStarted());
230     }
231 
232     private void testGetSplits(final StopWatch watch) {
233         assertTrue(watch.getSplits().isEmpty());
234         watch.split();
235         assertEquals(1, watch.getSplits().size());
236         watch.unsplit();
237         assertTrue(watch.getSplits().isEmpty());
238     }
239 
240     @Test
241     void testGetStartInstant() {
242         final long beforeStopWatchMillis = System.currentTimeMillis();
243         final StopWatch watch = new StopWatch();
244         assertThrows(IllegalStateException.class, watch::getStartInstant, "Calling getStartInstant on an unstarted StopWatch should throw an exception");
245         watch.start();
246 
247         watch.getStartInstant();
248         assertTrue(watch.getStartInstant().compareTo(Instant.ofEpochMilli(beforeStopWatchMillis)) >= 0);
249 
250         watch.reset();
251         assertThrows(IllegalStateException.class, watch::getStartInstant,
252                 "Calling getStartInstant on a reset, but unstarted StopWatch should throw an exception");
253     }
254 
255     @Test
256     void testGetStartTime() {
257         final long beforeStopWatchMillis = System.currentTimeMillis();
258         final StopWatch watch = new StopWatch();
259         assertThrows(IllegalStateException.class, watch::getStartTime, "Calling getStartTime on an unstarted StopWatch should throw an exception");
260         watch.start();
261 
262         watch.getStartTime();
263         assertTrue(watch.getStartTime() >= beforeStopWatchMillis, "getStartTime");
264 
265         watch.reset();
266         assertThrows(IllegalStateException.class, watch::getStartTime, "Calling getStartTime on a reset, but unstarted StopWatch should throw an exception");
267     }
268 
269     @RepeatedTest(10)
270     void testGetTime() throws InterruptedException {
271         final StopWatch watch = new StopWatch();
272         assertEquals(0, watch.getTime());
273         assertEquals(ZERO_TIME_ELAPSED, watch.toString());
274         watch.start();
275         sleepPlus1(MIN_DURATION);
276         final long time = watch.getTime();
277         assertTrue(time > 0, () -> "getTime() millis: " + time);
278         assertTrue(time < 2000, () -> "getTime() millis: " + time);
279     }
280 
281     @Test
282     void testGetWithTimeUnit() {
283         // Create a mock StopWatch with a time of 2:59:01.999
284         // @formatter:off
285         final StopWatch watch = createMockStopWatch(
286             TimeUnit.HOURS.toNanos(2)
287                     + TimeUnit.MINUTES.toNanos(59)
288                     + TimeUnit.SECONDS.toNanos(1)
289                     + TimeUnit.MILLISECONDS.toNanos(999));
290         // @formatter:on
291         assertEquals(2L, watch.getTime(TimeUnit.HOURS));
292         assertEquals(179L, watch.getTime(TimeUnit.MINUTES));
293         assertEquals(10741L, watch.getTime(TimeUnit.SECONDS));
294         assertEquals(10741999L, watch.getTime(TimeUnit.MILLISECONDS));
295     }
296 
297     @Test
298     void testLang315() throws InterruptedException {
299         final StopWatch watch = StopWatch.createStarted();
300         sleepPlus1(MIN_DURATION);
301         watch.suspend();
302         final long suspendTime = watch.getTime();
303         final Duration suspendDuration = watch.getDuration();
304         sleepPlus1(MIN_DURATION);
305         watch.stop();
306         final long totalTime = watch.getTime();
307         final Duration totalDuration = watch.getDuration();
308         assertEquals(suspendTime, totalTime);
309         assertEquals(suspendDuration, totalDuration);
310     }
311 
312     @Test
313     void testMessage() {
314         assertNull(StopWatch.create().getMessage());
315         final StopWatch stopWatch = new StopWatch(MESSAGE);
316         assertEquals(MESSAGE, stopWatch.getMessage());
317         assertTrue(stopWatch.toString().startsWith(MESSAGE), "stopWatch.toString");
318         stopWatch.start();
319         stopWatch.split();
320         assertTrue(stopWatch.toSplitString().startsWith(MESSAGE), "stopWatch.toSplitString");
321     }
322 
323     @Test
324     void testRun() throws Throwable {
325         final StopWatch watch = new StopWatch();
326         final AtomicInteger i = new AtomicInteger();
327         watch.run(i::incrementAndGet);
328         assertEquals(1, i.get());
329         watch.runT(i::incrementAndGet);
330         assertEquals(2, i.get());
331         final IOException e = assertThrows(IOException.class, () -> watch.runT(this::throwIOException));
332         assertEquals("A", e.getMessage());
333         // test state
334         assertTrue(watch.isSuspended());
335         watch.run(() -> {
336             assertTrue(watch.isStarted());
337             i.incrementAndGet();
338         });
339         assertEquals(3, i.get());
340         assertTrue(watch.isSuspended());
341         final long nanos1 = watch.getDuration().toNanos();
342         assertTrue(nanos1 > 0);
343         // test state
344         assertTrue(watch.isSuspended());
345         watch.runT(() -> {
346             assertTrue(watch.isStarted());
347             i.incrementAndGet();
348         });
349         assertEquals(4, i.get());
350         assertTrue(watch.isSuspended());
351         assertTrue(watch.getDuration().toNanos() >= nanos1);
352     }
353 
354     @Test
355     void testSimple() throws InterruptedException {
356         final StopWatch watch = StopWatch.createStarted();
357         final Duration sleepDuration = MIN_DURATION;
358         sleepPlus1(sleepDuration);
359         watch.stop();
360         final long time = watch.getTime();
361         final Duration duration = watch.getDuration();
362         assertEquals(time, watch.getTime());
363         assertEquals(duration, watch.getDuration());
364         assertTrue(duration.compareTo(sleepDuration) >= 0, () -> "duration: " + duration);
365         watch.reset();
366         assertEquals(0, watch.getTime());
367         assertEquals(Duration.ZERO, watch.getDuration());
368     }
369 
370     @Test
371     void testSplit() throws InterruptedException {
372         final StopWatch watch = StopWatch.createStarted();
373         final Duration sleepDuration = MIN_DURATION;
374         final long sleepMillis = sleepDuration.toMillis();
375         assertTrue(sleepMillis > 0);
376         sleepPlus1(sleepDuration);
377         watch.split();
378         final long splitTime = watch.getSplitTime();
379         final Duration splitDuration = watch.getSplitDuration();
380         assertEquals(splitTime, watch.getSplitDuration().toMillis());
381         assertEquals(SPLIT_CLOCK_STR_LEN, watch.toSplitString().length(), "Formatted split string not the correct length");
382         sleepPlus1(sleepDuration);
383         watch.unsplit();
384         sleepPlus1(sleepDuration);
385         watch.stop();
386         final long totalTime = watch.getTime();
387         final Duration totalDuration = watch.getDuration();
388         assertTrue(splitTime >= sleepMillis, () -> "splitTime: " + splitTime);
389         assertTrue(splitDuration.toMillis() >= sleepMillis, () -> "splitDuration: " + splitDuration);
390         final long sleepMillisX3 = sleepMillis * 3;
391         assertTrue(totalTime >= sleepMillisX3 && splitTime < 21000);
392         assertTrue(totalDuration.toMillis() >= sleepMillisX3);
393     }
394 
395     @Test
396     void testStatic() {
397         final StopWatch watch = StopWatch.createStarted();
398         assertTrue(watch.isStarted());
399     }
400 
401     @Test
402     void testStopInstantSimple() throws InterruptedException {
403         final StopWatch watch = StopWatch.createStarted();
404         final long testStartMillis = System.currentTimeMillis();
405         sleepPlus1(MIN_DURATION);
406         watch.stop();
407         final long testEndMillis = System.currentTimeMillis();
408         final Instant stopTime = watch.getStopInstant();
409         assertEquals(stopTime, watch.getStopInstant());
410         // Only less than, not equal
411         assertTrue(testStartMillis < testEndMillis);
412         assertTrue(Instant.ofEpochMilli(testStartMillis).isBefore(Instant.ofEpochMilli(testEndMillis)));
413     }
414 
415     @Test
416     void testStopTimeSimple() throws InterruptedException {
417         final StopWatch watch = StopWatch.createStarted();
418         final long testStartMillis = System.currentTimeMillis();
419         sleepPlus1(MIN_DURATION);
420         watch.stop();
421         final long testEndMillis = System.currentTimeMillis();
422         final long stopTime = watch.getStopTime();
423         assertEquals(stopTime, watch.getStopTime());
424         // Only less than, not equal
425         assertTrue(testStartMillis < testEndMillis);
426     }
427 
428     @Test
429     void testSuspend() throws InterruptedException {
430         // Watch out comparing measurements from System.currentTimeMillis() vs. System.nanoTime()
431         final StopWatch watch = StopWatch.createStarted();
432         final long testStartMillis = System.currentTimeMillis();
433         final long testStartNanos = System.nanoTime();
434         final Instant testStartInstant = Instant.ofEpochMilli(testStartMillis);
435         final Duration sleepDuration = MIN_DURATION;
436         final long sleepMillis = sleepDuration.toMillis();
437         sleepPlus1(sleepDuration);
438         watch.suspend();
439         final long testSuspendMillis = System.currentTimeMillis();
440         final long testSuspendNanos = System.nanoTime();
441         final long testSuspendTimeNanos = testSuspendNanos - testStartNanos;
442         // See sleepPlus1
443         final Duration testSuspendDuration = Duration.ofNanos(testSuspendTimeNanos).plusMillis(1);
444         final long suspendTimeFromNanos = watch.getTime();
445         final Duration suspendDuration = watch.getDuration();
446         final long stopTimeMillis = watch.getStopTime();
447         final Instant stopInstant = watch.getStopInstant();
448 
449         assertTrue(testStartMillis <= stopTimeMillis, () -> String.format("testStartMillis %s <= stopTimeMillis %s", testStartMillis, stopTimeMillis));
450         assertTrue(testStartInstant.isBefore(stopInstant), () -> String.format("testStartInstant %s < stopInstant %s", testStartInstant, stopInstant));
451         assertTrue(testSuspendMillis <= stopTimeMillis, () -> String.format("testSuspendMillis %s <= stopTimeMillis %s", testSuspendMillis, stopTimeMillis));
452         assertTrue(testSuspendMillis <= stopInstant.toEpochMilli(),
453                 () -> String.format("testSuspendMillis %s <= stopInstant %s", testSuspendMillis, stopInstant));
454 
455         sleepPlus1(sleepDuration);
456         watch.resume();
457         sleepPlus1(sleepDuration);
458         watch.stop();
459         final long totalTimeFromNanos = watch.getTime();
460         final Duration totalDuration = watch.getDuration();
461 
462         assertTrue(suspendTimeFromNanos >= sleepMillis, () -> String.format("suspendTimeFromNanos %s >= sleepMillis %s", suspendTimeFromNanos, sleepMillis));
463         assertTrue(suspendDuration.compareTo(Duration.ofMillis(sleepMillis)) >= 0,
464                 () -> String.format("suspendDuration %s >= sleepMillis %s", suspendDuration, sleepMillis));
465         assertTrue(suspendTimeFromNanos <= testSuspendTimeNanos,
466                 () -> String.format("suspendTimeFromNanos %s <= testSuspendTimeNanos %s", suspendTimeFromNanos, testSuspendTimeNanos));
467         assertTrue(suspendDuration.compareTo(testSuspendDuration) <= 0,
468                 () -> String.format("suspendDuration %s <= testSuspendDuration %s", suspendDuration, testSuspendDuration));
469 
470         final long sleepMillisX2 = sleepMillis + sleepMillis;
471         assertTrue(totalTimeFromNanos >= sleepMillisX2, () -> String.format("totalTimeFromNanos %s >= sleepMillisX2 %s", totalTimeFromNanos, sleepMillisX2));
472         assertTrue(totalDuration.compareTo(Duration.ofMillis(sleepMillisX2)) >= 0,
473                 () -> String.format("totalDuration >= sleepMillisX2", totalDuration, sleepMillisX2));
474 
475         // Be lenient for slow running builds
476         final long testTooLongMillis = sleepMillis * 100;
477         assertTrue(totalTimeFromNanos < testTooLongMillis,
478                 () -> String.format("totalTimeFromNanos %s < testTooLongMillis %s", totalTimeFromNanos, testTooLongMillis));
479         assertTrue(totalDuration.compareTo(Duration.ofMillis(testTooLongMillis)) < 0,
480                 () -> String.format("totalDuration %s < testTooLongMillis %s", totalDuration, testTooLongMillis));
481 
482     }
483 
484     @Test
485     void testToSplitString() throws InterruptedException {
486         final StopWatch watch = StopWatch.createStarted();
487         sleepPlus1(MIN_DURATION);
488         watch.split();
489         final String splitStr = watch.toSplitString();
490         assertEquals(SPLIT_CLOCK_STR_LEN, splitStr.length(), "Formatted split string not the correct length");
491     }
492 
493     @Test
494     void testToSplitStringWithMessage() throws InterruptedException {
495         final StopWatch watch = new StopWatch(MESSAGE);
496         watch.start();
497         sleepPlus1(MIN_DURATION);
498         watch.split();
499         final String splitStr = watch.toSplitString();
500         assertEquals(SPLIT_CLOCK_STR_LEN + MESSAGE.length() + 1, splitStr.length(), "Formatted split string not the correct length");
501     }
502 
503     @Test
504     void testToString() throws InterruptedException {
505         //
506         final StopWatch watch = StopWatch.createStarted();
507         sleepPlus1(MIN_DURATION);
508         watch.split();
509         final String splitStr = watch.toString();
510         assertEquals(SPLIT_CLOCK_STR_LEN, splitStr.length(), "Formatted split string not the correct length");
511     }
512 
513     @Test
514     void testToStringWithMessage() throws InterruptedException {
515         assertTrue(new StopWatch(MESSAGE).toString().startsWith(MESSAGE), "message");
516         //
517         final StopWatch watch = new StopWatch(MESSAGE);
518         watch.start();
519         sleepPlus1(MIN_DURATION);
520         watch.split();
521         final String splitStr = watch.toString();
522         assertEquals(SPLIT_CLOCK_STR_LEN + MESSAGE.length() + 1, splitStr.length(), "Formatted split string not the correct length");
523     }
524 
525     @Test
526     void testSplitsWithStringLabels() {
527         final StopWatch watch = new StopWatch();
528         final String firstLabel = "one";
529         final String secondLabel = "two";
530         final String thirdLabel = "three";
531         watch.start();
532         // starting splits
533         watch.split(firstLabel);
534         watch.split(secondLabel);
535         watch.split(thirdLabel);
536         watch.stop();
537         // getting splits
538         final List<StopWatch.Split> splits = watch.getSplits();
539         // check size
540         assertEquals(3, splits.size());
541         // check labels
542         assertEquals(firstLabel, splits.get(0).getLabel());
543         assertEquals(secondLabel, splits.get(1).getLabel());
544         assertEquals(thirdLabel, splits.get(2).getLabel());
545         // check time in nanos
546         assertTrue(splits.get(0).getDuration().toNanos() > 0);
547         assertTrue(splits.get(1).getDuration().toNanos() > 0);
548         assertTrue(splits.get(2).getDuration().toNanos() > 0);
549         // We can only unsplit once
550         watch.unsplit();
551         assertEquals(2, watch.getSplits().size());
552         assertThrows(IllegalStateException.class, watch::unsplit);
553     }
554 
555     private int throwIOException() throws IOException {
556         throw new IOException("A");
557     }
558 }