cassandra-trunk/test/unit/org/apache/cassandra/config/DurationSpecTest.java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.cassandra.config;

import java.util.concurrent.TimeUnit;

import org.junit.Test;

import org.quicktheories.core.Gen;
import org.quicktheories.generators.SourceDSL;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.*;
import static org.quicktheories.QuickTheory.qt;

public class DurationSpecTest
{
    private static final long MAX_INT_CONFIG_VALUE = Integer.MAX_VALUE - 1;

    @SuppressWarnings("AssertBetweenInconvertibleTypes")
    @Test
    public void testConversions()
    {
        assertEquals(10000000000L, new DurationSpec.LongNanosecondsBound ("10s").toNanoseconds());
        assertEquals(MAX_INT_CONFIG_VALUE, new DurationSpec.IntSecondsBound(MAX_INT_CONFIG_VALUE + "s").toSeconds());
        assertEquals(MAX_INT_CONFIG_VALUE, new DurationSpec.LongMillisecondsBound(MAX_INT_CONFIG_VALUE + "ms").toMilliseconds());
        assertEquals(600000000000L, new DurationSpec.LongNanosecondsBound ("10m").toNanoseconds());
        assertEquals(MAX_INT_CONFIG_VALUE, new DurationSpec.IntMinutesBound(MAX_INT_CONFIG_VALUE + "m").toMinutes());
        assertEquals(MAX_INT_CONFIG_VALUE, new DurationSpec.IntSecondsBound(MAX_INT_CONFIG_VALUE + "s").toSeconds());
        assertEquals(new DurationSpec.IntMillisecondsBound(0.7, TimeUnit.MILLISECONDS), new DurationSpec.LongNanosecondsBound("1ms"));
        assertEquals(new DurationSpec.IntMillisecondsBound(0.33, TimeUnit.MILLISECONDS), new DurationSpec.LongNanosecondsBound("0ms"));
        assertEquals(new DurationSpec.IntMillisecondsBound(0.333, TimeUnit.MILLISECONDS), new DurationSpec.LongNanosecondsBound("0ms"));
    }

    @Test
    public void testFromSymbol()
    {
        assertEquals(DurationSpec.fromSymbol("ms"), TimeUnit.MILLISECONDS);
        assertEquals(DurationSpec.fromSymbol("d"), TimeUnit.DAYS);
        assertEquals(DurationSpec.fromSymbol("h"), TimeUnit.HOURS);
        assertEquals(DurationSpec.fromSymbol("m"), TimeUnit.MINUTES);
        assertEquals(DurationSpec.fromSymbol("s"), TimeUnit.SECONDS);
        assertEquals(DurationSpec.fromSymbol("us"), TimeUnit.MICROSECONDS);
        assertEquals(DurationSpec.fromSymbol("µs"), TimeUnit.MICROSECONDS);
        assertEquals(DurationSpec.fromSymbol("ns"), NANOSECONDS);
        assertThatThrownBy(() -> DurationSpec.fromSymbol("n")).isInstanceOf(IllegalArgumentException.class);
    }

    @Test
    public void testGetSymbol()
    {
        assertEquals(DurationSpec.symbol(TimeUnit.MILLISECONDS), "ms");
        assertEquals(DurationSpec.symbol(TimeUnit.DAYS), "d");
        assertEquals(DurationSpec.symbol(TimeUnit.HOURS), "h");
        assertEquals(DurationSpec.symbol(TimeUnit.MINUTES), "m");
        assertEquals(DurationSpec.symbol(TimeUnit.SECONDS), "s");
        assertEquals(DurationSpec.symbol(TimeUnit.MICROSECONDS), "us");
        assertEquals(DurationSpec.symbol(NANOSECONDS), "ns");
    }

    @Test
    public void testInvalidInputs()
    {
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound("10")).isInstanceOf(IllegalArgumentException.class)
                                                                             .hasMessageContaining("Invalid duration: 10");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: -10s");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(-10, SECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: value must be non-negativ");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound("10xd")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10xd");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound("0.333555555ms")).isInstanceOf(IllegalArgumentException.class)
                                                                                        .hasMessageContaining("Invalid duration: 0.333555555ms");
    }

    @Test
    public void testInvalidForConversion()
    {
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE + "ns")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 9223372036854775807ns. " +
                                                                                                 "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE + "ms")).isInstanceOf(IllegalArgumentException.class)
                                                                             .hasMessageContaining("Invalid duration: 9223372036854775807ms. " +
                                                                                                   "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE-5 + "µs")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 9223372036854775802µs. " +
                                                                                                     "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE-5 + "us")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 9223372036854775802us. " +
                                                                                                     "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE-5 + "s")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 9223372036854775802s. " +
                                                                                                 "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE-5 + "h")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 9223372036854775802h. " +
                                                                                                "It shouldn't be more than 9223372036854775806 in nanoseconds");
        assertThatThrownBy(() -> new DurationSpec.LongNanosecondsBound(Long.MAX_VALUE-5 + "d")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 9223372036854775802d. " +
                                                                                                "It shouldn't be more than 9223372036854775806 in nanoseconds");
    }

    @Test
    public void testOverflowingConversion()
    {
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("2147483648ms")).isInstanceOf(IllegalArgumentException.class)
                                                                                       .hasMessageContaining("Invalid duration: 2147483648ms." +
                                                                                                             " It shouldn't be more than 2147483646 in milliseconds");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound(2147483648L)).isInstanceOf(IllegalArgumentException.class)
                                                                                    .hasMessageContaining("Invalid duration: 2147483648 milliseconds. " +
                                                                                                          "It shouldn't be more than 2147483646 in milliseconds");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("2147483648s")).isInstanceOf(IllegalArgumentException.class)
                                                                                      .hasMessageContaining("Invalid duration: 2147483648s. " +
                                                                                                            "It shouldn't be more than 2147483646 in milliseconds");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("35791395m")).isInstanceOf(IllegalArgumentException.class)
                                                                                    .hasMessageContaining("Invalid duration: 35791395m. " +
                                                                                                          "It shouldn't be more than 2147483646 in milliseconds");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("597h")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 597h. " +
                                                                                                     "It shouldn't be more than 2147483646 in milliseconds");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("24856d")).isInstanceOf(IllegalArgumentException.class)
                                                                                 .hasMessageContaining("Invalid duration: 24856d. " +
                                                                                                       "It shouldn't be more than 2147483646 in milliseconds");

        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("2147483648s")).isInstanceOf(IllegalArgumentException.class)
                                                                                 .hasMessageContaining("Invalid duration: 2147483648s. " +
                                                                                                       "It shouldn't be more than 2147483646 in seconds");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound(2147483648L)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 2147483648 seconds. " +
                                                                                                     "It shouldn't be more than 2147483646 in seconds");
        assertThatThrownBy(() -> DurationSpec.IntSecondsBound.inSecondsString("2147483648")).isInstanceOf(NumberFormatException.class)
                                                                                            .hasMessageContaining("For input string: \"2147483648\"");
        assertThatThrownBy(() -> DurationSpec.IntSecondsBound.inSecondsString("2147483648s")).isInstanceOf(IllegalArgumentException.class)
                                                                                             .hasMessageContaining("Invalid duration: 2147483648s. " +
                                                                                                                   "It shouldn't be more than 2147483646 in seconds");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("35791395m")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 35791395m. " +
                                                                                                     "It shouldn't be more than 2147483646 in seconds");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("596524h")).isInstanceOf(IllegalArgumentException.class)
                                                                             .hasMessageContaining("Invalid duration: 596524h. " +
                                                                                                   "It shouldn't be more than 2147483646 in seconds");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("24856d")).isInstanceOf(IllegalArgumentException.class)
                                                                            .hasMessageContaining("Invalid duration: 24856d. " +
                                                                                                  "It shouldn't be more than 2147483646 in seconds");

        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("2147483648s")).isInstanceOf(IllegalArgumentException.class)
                                                                                 .hasMessageContaining("Invalid duration: 2147483648s " +
                                                                                                       "Accepted units:[MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("2147483648m")).isInstanceOf(IllegalArgumentException.class)
                                                                                 .hasMessageContaining("Invalid duration: 2147483648m. " +
                                                                                                       "It shouldn't be more than 2147483646 in minutes");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("35791395h")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 35791395h. " +
                                                                                                     "It shouldn't be more than 2147483646 in minutes");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("1491309d")).isInstanceOf(IllegalArgumentException.class)
                                                                              .hasMessageContaining("Invalid duration: 1491309d. " +
                                                                                                    "It shouldn't be more than 2147483646 in minutes");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(2147483648L)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 2147483648 minutes. " +
                                                                                                     "It shouldn't be more than 2147483646 in minutes");
    }

    @SuppressWarnings("AssertBetweenInconvertibleTypes")
    @Test
    public void testEquals()
    {
        assertEquals(new DurationSpec.LongNanosecondsBound ("10s"), new DurationSpec.LongNanosecondsBound ("10s"));
        assertEquals(new DurationSpec.LongNanosecondsBound ("10s"), new DurationSpec.LongNanosecondsBound ("10000ms"));
        assertEquals(new DurationSpec.LongNanosecondsBound ("10000ms"), new DurationSpec.LongNanosecondsBound ("10s"));
        assertEquals(new DurationSpec.LongNanosecondsBound ("4h"), new DurationSpec.LongNanosecondsBound ("14400s"));
        assertEquals(DurationSpec.LongNanosecondsBound .IntSecondsBound.inSecondsString("14400"), new DurationSpec.LongNanosecondsBound ("14400s"));
        assertEquals(DurationSpec.LongNanosecondsBound .IntSecondsBound.inSecondsString("4h"), new DurationSpec.LongNanosecondsBound ("14400s"));
        assertEquals(DurationSpec.LongNanosecondsBound .IntSecondsBound.inSecondsString("14400s"), new DurationSpec.LongNanosecondsBound ("14400s"));
        assertNotEquals(new DurationSpec.LongNanosecondsBound ("0m"), new DurationSpec.LongNanosecondsBound ("10ms"));
        assertEquals(Long.MAX_VALUE-1, new DurationSpec.LongNanosecondsBound ("9223372036854775806ns").toNanoseconds());
    }

    @Test
    public void thereAndBack()
    {
        Gen<TimeUnit> unitGen = SourceDSL.arbitrary().enumValues(TimeUnit.class);
        Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE/24/60/60/1000L/1000L/1000L);
        qt().forAll(valueGen, unitGen).check((value, unit) -> {
            DurationSpec.LongNanosecondsBound  there = new DurationSpec.LongNanosecondsBound (value, unit);
            DurationSpec.LongNanosecondsBound  back = new DurationSpec.LongNanosecondsBound (there.toString());
            return there.equals(back);
        });
    }

    @Test
    public void testValidUnits()
    {
        assertEquals(10L, new DurationSpec.IntMillisecondsBound("10ms").toMilliseconds());
        assertEquals(10L, new DurationSpec.IntSecondsBound("10s").toSeconds());
        assertEquals(new DurationSpec.IntSecondsBound("10s"), DurationSpec.IntSecondsBound.inSecondsString("10"));
        assertEquals(new DurationSpec.IntSecondsBound("10s"), DurationSpec.IntSecondsBound.inSecondsString("10s"));

        assertEquals(10L, new DurationSpec.LongMicrosecondsBound("10us").toMicroseconds());
        assertEquals(10L, new DurationSpec.LongMillisecondsBound("10ms").toMilliseconds());
        assertEquals(10L, new DurationSpec.LongSecondsBound("10s").toSeconds());
    }

    @Test
    public void testInvalidUnits()
    {
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10ns " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10 NANOSECONDS " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("10us")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10us " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10 MICROSECONDS " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("10µs")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10µs " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: 10 MICROSECONDS " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");
        assertThatThrownBy(() -> new DurationSpec.IntMillisecondsBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                               .hasMessageContaining("Invalid duration: -10s " +
                                                                                                     "Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]");

        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("10ms")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10ms Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound(10, MILLISECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MILLISECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10ns Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("10us")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10us Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("10µs")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10µs Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntSecondsBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: -10s");

        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("10s")).isInstanceOf(IllegalArgumentException.class)
                                                                         .hasMessageContaining("Invalid duration: 10s Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(10, SECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                         .hasMessageContaining("Invalid duration: 10 SECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("10ms")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10ms Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(10, MILLISECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MILLISECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10ns Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("10us")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10us Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("10µs")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10µs Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                          .hasMessageContaining("Invalid duration: -10s");

        assertThatThrownBy(() -> new DurationSpec.LongMicrosecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10ns Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMicrosecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                                         .hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");

        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10ns Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound("10us")).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10us Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound("10µs")).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10µs Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                                .hasMessageContaining("Invalid duration: -10s");

        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound("10ms")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10ms Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound(10, MILLISECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10 MILLISECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10ns Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound("10µs")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10µs Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound(10, MICROSECONDS)).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: 10 MICROSECONDS Accepted units");
        assertThatThrownBy(() -> new DurationSpec.LongSecondsBound("-10s")).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessageContaining("Invalid duration: -10s");
    }
}
