This is an automated email from the ASF dual-hosted git repository. edimitrova pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit db9f7a67ec4b03413c10034956e2cf18739ca4b1 Author: Ekaterina Dimitrova <ekaterina.dimitr...@datastax.com> AuthorDate: Tue Dec 14 23:00:56 2021 -0500 Add new custom types and unit tests for configuration patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe, David Capwell, Michael Semb Wever and Benjamin Lerer for CASSANDRA-15234 --- CHANGES.txt | 1 + NEWS.txt | 6 + .../org/apache/cassandra/config/DataRateSpec.java | 378 ++++++++++++++++++++ .../apache/cassandra/config/DataStorageSpec.java | 397 +++++++++++++++++++++ .../org/apache/cassandra/config/DurationSpec.java | 342 ++++++++++++++++++ .../apache/cassandra/config/DataRateSpecTest.java | 136 +++++++ .../cassandra/config/DataStorageSpecTest.java | 141 ++++++++ .../apache/cassandra/config/DurationSpecTest.java | 160 +++++++++ 8 files changed, 1561 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4304aa7..66aae18 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1 + * Standardize storage configuration parameters' names. Support unit suffixes. (CASSANDRA-15234) * Remove support for Windows (CASSANDRA-16956) * Runtime-configurable YAML option to prohibit USE statements (CASSANDRA-17318) * When streaming sees a ClosedChannelException this triggers the disk failure policy (CASSANDRA-17116) diff --git a/NEWS.txt b/NEWS.txt index ff166df..966a729 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -81,6 +81,12 @@ New features Upgrading --------- + - There is a new cassandra.yaml version 2. Units suffixes should be provided for all rates(B/s|MiB/s|KiB/s|MiB/s), + memory (KiB|MiB|GiB|B) and duration(d|h|s|ms|us|µs|ns|m) + parameters. (CASSANDRA-15234) + Backward compatibility with the old cassandra.yaml file will be in place until at least the next major version. + - Many cassandra.yaml parameters' names have been changed. Full list can be found on ...... (ADD LINK LATER WHEN PAGE + IS CREATED) (CASSANDRA-15234) - Before you upgrade, if you are using `cassandra.auth_bcrypt_gensalt_log2_rounds` property, confirm it is set to value lower than 31 otherwise Cassandra will fail to start. See CASSANDRA-9384 for further details. You also need to regenerate passwords for users for who the password diff --git a/src/java/org/apache/cassandra/config/DataRateSpec.java b/src/java/org/apache/cassandra/config/DataRateSpec.java new file mode 100644 index 0000000..3512513 --- /dev/null +++ b/src/java/org/apache/cassandra/config/DataRateSpec.java @@ -0,0 +1,378 @@ +/* + * 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.Arrays; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.google.common.primitives.Ints; + +import org.apache.cassandra.exceptions.ConfigurationException; + +/** + * Represents a data rate type used for cassandra configuration. It supports the opportunity for the users to be able to + * add units to the confiuration parameter value. (CASSANDRA-15234) + */ +public final class DataRateSpec +{ + /** + * The Regexp used to parse the rate provided as String in cassandra.yaml. + */ + private static final Pattern BIT_RATE_UNITS_PATTERN = Pattern.compile("^(\\d+)(MiB/s|KiB/s|B/s)$"); + + private final double quantity; + + private final DataRateUnit unit; + + public DataRateSpec(String value) + { + //parse the string field value + Matcher matcher = BIT_RATE_UNITS_PATTERN.matcher(value); + + if (!matcher.find()) + throw new ConfigurationException("Invalid bit rate: " + value + " Accepted units: MiB/s, KiB/s, B/s where " + + "case matters and " + "only non-negative values are valid"); + + quantity = Long.parseLong(matcher.group(1)); + unit = DataRateUnit.fromSymbol(matcher.group(2)); + } + + DataRateSpec(double quantity, DataRateUnit unit) + { + if (quantity < 0) + throw new ConfigurationException("Invalid bit rare: value must be non-negative"); + + if (quantity > Long.MAX_VALUE) + throw new NumberFormatException("Invalid bit rate: value must be between 0 and Long.MAX_VALUE = 9223372036854775807"); + + this.quantity = quantity; + this.unit = unit; + } + + /** + * Creates a {@code DataRateSpec} of the specified amount of bits per second. + * + * @param bytesPerSecond the amount of bytes per second + * @return a {@code DataRateSpec} + */ + public static DataRateSpec inBytesPerSecond(long bytesPerSecond) + { + return new DataRateSpec(bytesPerSecond, DataRateUnit.BYTES_PER_SECOND); + } + + /** + * Creates a {@code DataRateSpec} of the specified amount of kibibytes per second. + * + * @param kibibytesPerSecond the amount of kibibytes per second + * @return a {@code DataRateSpec} + */ + public static DataRateSpec inKibibytesPerSecond(long kibibytesPerSecond) + { + return new DataRateSpec(kibibytesPerSecond, DataRateUnit.KIBIBYTES_PER_SECOND); + } + + /** + * Creates a {@code DataRateSpec} of the specified amount of mebibytes per second. + * + * @param mebibytesPerSecond the amount of mebibytes per second + * @return a {@code DataRateSpec} + */ + public static DataRateSpec inMebibytesPerSecond(long mebibytesPerSecond) + { + return new DataRateSpec(mebibytesPerSecond, DataRateUnit.MEBIBYTES_PER_SECOND); + } + + /** + * Creates a {@code DataRateSpec} of the specified amount of mebibytes per second. + * + * @param megabitsPerSecond the amount of megabits per second + * @return a {@code DataRateSpec} + */ + public static DataRateSpec megabitsPerSecondInMebibytesPerSecond(long megabitsPerSecond) + { + final double MEBIBYTES_PER_MEGABIT = 0.119209289550781; + double mebibytesPerSecond = (double)megabitsPerSecond * MEBIBYTES_PER_MEGABIT; + + return new DataRateSpec(mebibytesPerSecond, DataRateUnit.MEBIBYTES_PER_SECOND); + } + + /** + * @return the data rate unit assigned. + */ + public DataRateUnit getUnit() + { + return unit; + } + + /** + * @return the data rate in bytes per second + */ + public double toBytesPerSecond() + { + return unit.toBytesPerSecond(quantity); + } + + /** + * Returns the data rate in bytes per second as an {@code int} + * + * @return the data rate in bytes per second or {@code Integer.MAX_VALUE} if the rate is too large. + */ + public int toBytesPerSecondAsInt() + { + return Ints.saturatedCast(Math.round(toBytesPerSecond())); + } + + /** + * @return the data rate in kibibytes per second + */ + public double toKibibytesPerSecond() + { + return unit.toKibibytesPerSecond(quantity); + } + + /** + * Returns the data rate in kibibytes per second as an {@code int} + * + * @return the data rate in kibibytes per second or {@code Integer.MAX_VALUE} if the number of kibibytes is too large. + */ + public int toKibibytesPerSecondAsInt() + { + return Ints.saturatedCast(Math.round(toKibibytesPerSecond())); + } + + /** + * @return the data rate in mebibytes per second + */ + public double toMebibytesPerSecond() + { + return unit.toMebibytesPerSecond(quantity); + } + + /** + * Returns the data rate in mebibytes per second as an {@code int} + * + * @return the data rate in mebibytes per second or {@code Integer.MAX_VALUE} if the number of mebibytes is too large. + */ + public int toMebibytesPerSecondAsInt() + { + return Ints.saturatedCast(Math.round(toMebibytesPerSecond())); + } + + /** + * This method is required in order to support backward compatibility with the old unit used for a few Data Rate + * parameters before CASSANDRA-15234 + * + * @return the data rate in megabits per second. + */ + public double toMegabitsPerSecond() + { + return unit.toMegabitsPerSecond(quantity); + } + + /** + * Returns the data rate in megabits per second as an {@code int}. This method is required in order to support + * backward compatibility with the old unit used for a few Data Rate parameters before CASSANDRA-15234 + * + * @return the data rate in mebibytes per second or {@code Integer.MAX_VALUE} if the number of mebibytes is too large. + */ + public int toMegabitsPerSecondAsInt() + { + return Ints.saturatedCast(Math.round(toMegabitsPerSecond())); + } + + @Override + public int hashCode() + { + return Objects.hash(unit.toKibibytesPerSecond(quantity)); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + + if (!(obj instanceof DataRateSpec)) + return false; + + DataRateSpec other = (DataRateSpec) obj; + if (unit == other.unit) + return quantity == other.quantity; + + // Due to overflows we can only guarantee that the 2 data rates are equal if we get the same results + // doing the conversion in both directions. + return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity; + } + + @Override + public String toString() + { + return Math.round(quantity) + unit.symbol; + } + + public enum DataRateUnit + { + BYTES_PER_SECOND("B/s") + { + public double toBytesPerSecond(double d) + { + return d; + } + + public double toKibibytesPerSecond(double d) + { + return d / 1024.0; + } + + public double toMebibytesPerSecond(double d) + { + return d / (1024.0 * 1024.0); + } + + public double toMegabitsPerSecond(double d) { return (d / 125000.0); } + + public double convert(double source, DataRateUnit sourceUnit) + { + return sourceUnit.toBytesPerSecond(source); + } + }, + KIBIBYTES_PER_SECOND("KiB/s") + { + public double toBytesPerSecond(double d) + { + return x(d, 1024.0, (MAX / 1024.0)); + } + + public double toKibibytesPerSecond(double d) + { + return d; + } + + public double toMebibytesPerSecond(double d) + { + return d / 1024.0; + } + + public double toMegabitsPerSecond(double d) + { + return d / 122.0; + } + + public double convert(double source, DataRateUnit sourceUnit) + { + return sourceUnit.toKibibytesPerSecond(source); + } + }, + MEBIBYTES_PER_SECOND("MiB/s") + { + public double toBytesPerSecond(double d) + { + return x(d, (1024.0 * 1024.0), (MAX / (1024.0 * 1024.0))); + } + + public double toKibibytesPerSecond(double d) + { + return x(d, 1024.0, (MAX / 1024.0)); + } + + public double toMebibytesPerSecond(double d) + { + return d; + } + + public double toMegabitsPerSecond(double d) + { + if (d > MAX / (MEGABITS_PER_MEBIBYTE)) + return MAX; + return Math.round(d * MEGABITS_PER_MEBIBYTE); + } + + public double convert(double source, DataRateUnit sourceUnit) + { + return sourceUnit.toMebibytesPerSecond(source); + } + }; + + static final double MAX = Long.MAX_VALUE; + static final double MEGABITS_PER_MEBIBYTE = 8.388608; + + /** + * Scale d by m, checking for overflow. This has a short name to make above code more readable. + */ + static double x(double d, double m, double over) + { + assert (over > 0.0) && (over < (MAX - 1)) && (over == (MAX / m)); + + if (d > over) + return MAX; + return d * m; + } + + /** + * @param symbol the unit symbol + * @return the rate unit corresponding to the given symbol + */ + public static DataRateUnit fromSymbol(String symbol) + { + for (DataRateUnit value : values()) + { + if (value.symbol.equalsIgnoreCase(symbol)) + return value; + } + throw new ConfigurationException(String.format("Unsupported data rate unit: %s. Supported units are: %s", + symbol, Arrays.stream(values()) + .map(u -> u.symbol) + .collect(Collectors.joining(", ")))); + } + + /** + * The unit symbol + */ + private final String symbol; + + DataRateUnit(String symbol) + { + this.symbol = symbol; + } + + public double toBytesPerSecond(double d) + { + throw new AbstractMethodError(); + } + + public double toKibibytesPerSecond(double d) + { + throw new AbstractMethodError(); + } + + public double toMebibytesPerSecond(double d) + { + throw new AbstractMethodError(); + } + + public double toMegabitsPerSecond(double d) { throw new AbstractMethodError(); } + + public double convert(double source, DataRateUnit sourceUnit) + { + throw new AbstractMethodError(); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/config/DataStorageSpec.java b/src/java/org/apache/cassandra/config/DataStorageSpec.java new file mode 100644 index 0000000..72ed2ba --- /dev/null +++ b/src/java/org/apache/cassandra/config/DataStorageSpec.java @@ -0,0 +1,397 @@ +/* + * 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.Arrays; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.google.common.primitives.Ints; + +import org.apache.cassandra.exceptions.ConfigurationException; + +/** + * Represents an amount of data storage. Wrapper class for Cassandra configuration parameters, providing to the + * users the opportunity to be able to provide config with a unit of their choice in cassandra.yaml as per the available + * options. (CASSANDRA-15234) + */ +public final class DataStorageSpec +{ + /** + * The Regexp used to parse the storage provided as String. + */ + private static final Pattern STORAGE_UNITS_PATTERN = Pattern.compile("^(\\d+)(GiB|MiB|KiB|B)$"); + + private final long quantity; + + private final DataStorageUnit unit; + + public DataStorageSpec(String value) + { + if (value == null || value.equals("null")) + { + quantity = 0; + unit = DataStorageUnit.MEBIBYTES; // the unit doesn't really matter as 0 is 0 in all units + return; + } + + //parse the string field value + Matcher matcher = STORAGE_UNITS_PATTERN.matcher(value); + + if (!matcher.find()) + { + throw new ConfigurationException("Invalid data storage: " + value + " Accepted units: B, KiB, MiB, GiB" + + " where case matters and only non-negative values are accepted"); + } + + quantity = Long.parseLong(matcher.group(1)); + unit = DataStorageUnit.fromSymbol(matcher.group(2)); + } + + DataStorageSpec(long quantity, DataStorageUnit unit) + { + if (quantity < 0) + throw new ConfigurationException("Invalid data storage: value must be positive, but was " + quantity); + + this.quantity = quantity; + this.unit = unit; + } + + /** + * Creates a {@code DataStorageSpec} of the specified amount of bytes. + * + * @param bytes the amount of bytes + * @return a {@code DataStorageSpec} + */ + public static DataStorageSpec inBytes(long bytes) + { + return new DataStorageSpec(bytes, DataStorageUnit.BYTES); + } + + /** + * Creates a {@code DataStorageSpec} of the specified amount of kibibytes. + * + * @param kibibytes the amount of kibibytes + * @return a {@code DataStorageSpec} + */ + public static DataStorageSpec inKibibytes(long kibibytes) + { + return new DataStorageSpec(kibibytes, DataStorageUnit.KIBIBYTES); + } + + /** + * Creates a {@code DataStorageSpec} of the specified amount of mebibytes. + * + * @param mebibytes the amount of mebibytes + * @return a {@code DataStorageSpec} + */ + public static DataStorageSpec inMebibytes(long mebibytes) + { + return new DataStorageSpec(mebibytes, DataStorageUnit.MEBIBYTES); + } + + /** + * @return the data storage unit. + */ + public DataStorageUnit getUnit() + { + return unit; + } + + /** + * @return the amount of data storage in bytes + */ + public long toBytes() + { + return unit.toBytes(quantity); + } + + /** + * Returns the amount of data storage in bytes as an {@code int} + * + * @return the amount of data storage in bytes or {@code Integer.MAX_VALUE} if the number of bytes is too large. + */ + public int toBytesAsInt() + { + return Ints.saturatedCast(toBytes()); + } + + /** + * @return the amount of data storage in kibibytes + */ + public long toKibibytes() + { + return unit.toKibibytes(quantity); + } + + /** + * Returns the amount of data storage in kibibytes as an {@code int} + * + * @return the amount of data storage in kibibytes or {@code Integer.MAX_VALUE} if the number of kibibytes is too large. + */ + public int toKibibytesAsInt() + { + return Ints.saturatedCast(toKibibytes()); + } + + /** + * @return the amount of data storage in mebibytes + */ + public long toMebibytes() + { + return unit.toMebibytes(quantity); + } + + /** + * Returns the amount of data storage in mebibytes as an {@code int} + * + * @return the amount of data storage in mebibytes or {@code Integer.MAX_VALUE} if the number of mebibytes is too large. + */ + public int toMebibytesAsInt() + { + return Ints.saturatedCast(toMebibytes()); + } + + /** + * @return the amount of data storage in gibibytes + */ + public long toGibibytes() + { + return unit.toGibibytes(quantity); + } + + /** + * Returns the amount of data storage in gibibytes as an {@code int} + * + * @return the amount of data storage in gibibytes or {@code Integer.MAX_VALUE} if the number of gibibytes is too large. + */ + public int toGibibytesAsInt() + { + return Ints.saturatedCast(toGibibytes()); + } + + @Override + public int hashCode() + { + return Objects.hash(unit.toKibibytes(quantity)); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + + if (!(obj instanceof DataStorageSpec)) + return false; + + DataStorageSpec other = (DataStorageSpec) obj; + if (unit == other.unit) + return quantity == other.quantity; + + // Due to overflows we can only guarantee that the 2 storages are equal if we get the same results + // doing the convertion in both directions. + return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity; + } + + @Override + public String toString() + { + return quantity + unit.symbol; + } + + public enum DataStorageUnit + { + BYTES("B") + { + public long toBytes(long d) + { + return d; + } + + public long toKibibytes(long d) + { + return (d / 1024L); + } + + public long toMebibytes(long d) + { + return (d / (1024L * 1024)); + } + + public long toGibibytes(long d) + { + return (d / (1024L * 1024 * 1024)); + } + + public long convert(long source, DataStorageUnit sourceUnit) + { + return sourceUnit.toBytes(source); + } + }, + KIBIBYTES("KiB") + { + public long toBytes(long d) + { + return x(d, 1024L, (MAX / 1024L)); + } + + public long toKibibytes(long d) + { + return d; + } + + public long toMebibytes(long d) + { + return (d / 1024L); + } + + public long toGibibytes(long d) + { + return (d / (1024L * 1024)); + } + + public long convert(long source, DataStorageUnit sourceUnit) + { + return sourceUnit.toKibibytes(source); + } + }, + MEBIBYTES("MiB") + { + public long toBytes(long d) + { + return x(d, (1024L * 1024), MAX / (1024L * 1024)); + } + + public long toKibibytes(long d) + { + return x(d, 1024L, (MAX / 1024L)); + } + + public long toMebibytes(long d) + { + return d; + } + + public long toGibibytes(long d) + { + return (d / 1024L); + } + + public long convert(long source, DataStorageUnit sourceUnit) + { + return sourceUnit.toMebibytes(source); + } + }, + GIBIBYTES("GiB") + { + public long toBytes(long d) + { + return x(d, (1024L * 1024 * 1024), MAX / (1024L * 1024 * 1024)); + } + + public long toKibibytes(long d) + { + return x(d, (1024L * 1024), MAX / (1024L * 1024)); + } + + public long toMebibytes(long d) + { + return x(d, 1024L, (MAX / 1024L)); + } + + public long toGibibytes(long d) + { + return d; + } + + public long convert(long source, DataStorageUnit sourceUnit) + { + return sourceUnit.toGibibytes(source); + } + }; + + /** + * Scale d by m, checking for overflow. This has a short name to make above code more readable. + */ + static long x(long d, long m, long over) + { + assert (over > 0) && (over < (MAX-1L)) && (over == (MAX / m)); + + if (d > over) + return Long.MAX_VALUE; + return Math.multiplyExact(d, m); + } + + /** + * @param symbol the unit symbol + * @return the memory unit corresponding to the given symbol + */ + public static DataStorageUnit fromSymbol(String symbol) + { + for (DataStorageUnit value : values()) + { + if (value.symbol.equalsIgnoreCase(symbol)) + return value; + } + throw new ConfigurationException(String.format("Unsupported data storage unit: %s. Supported units are: %s", + symbol, Arrays.stream(values()) + .map(u -> u.symbol) + .collect(Collectors.joining(", ")))); + } + + static final long MAX = Long.MAX_VALUE; + + /** + * The unit symbol + */ + private final String symbol; + + DataStorageUnit(String symbol) + { + this.symbol = symbol; + } + + public long toBytes(long d) + { + throw new AbstractMethodError(); + } + + public long toKibibytes(long d) + { + throw new AbstractMethodError(); + } + + public long toMebibytes(long d) + { + throw new AbstractMethodError(); + } + + public long toGibibytes(long d) + { + throw new AbstractMethodError(); + } + + public long convert(long source, DataStorageUnit sourceUnit) + { + throw new AbstractMethodError(); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/config/DurationSpec.java b/src/java/org/apache/cassandra/config/DurationSpec.java new file mode 100644 index 0000000..796fde6 --- /dev/null +++ b/src/java/org/apache/cassandra/config/DurationSpec.java @@ -0,0 +1,342 @@ +/* + * 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.Arrays; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.google.common.primitives.Ints; + +import org.apache.cassandra.exceptions.ConfigurationException; + +/** + * Represents a positive time duration. Wrapper class for Cassandra duration configuration parameters, providing to the + * users the opportunity to be able to provide config with a unit of their choice in cassandra.yaml as per the available + * options. (CASSANDRA-15234) + */ +public final class DurationSpec +{ + /** + * The Regexp used to parse the duration provided as String. + */ + private static final Pattern TIME_UNITS_PATTERN = Pattern.compile(("^(\\d+)(d|h|s|ms|us|µs|ns|m)")); + private static final Pattern VALUES_PATTERN = Pattern.compile(("\\d+")); + + public final long quantity; + + private final TimeUnit unit; + + public DurationSpec(String value) + { + if (value == null || value.equals("null") || value.toLowerCase(Locale.ROOT).equals("nan")) + { + quantity = 0; + unit = TimeUnit.MILLISECONDS; + return; + } + + //parse the string field value + Matcher matcher = TIME_UNITS_PATTERN.matcher(value); + + if(matcher.find()) + { + quantity = Long.parseLong(matcher.group(1)); + unit = fromSymbol(matcher.group(2)); + } + else + { + throw new ConfigurationException("Invalid duration: " + value + " Accepted units: d, h, m, s, ms, us, µs," + + " ns where case matters and " + "only non-negative values"); + } + } + + DurationSpec(long quantity, TimeUnit unit) + { + if (quantity < 0) + throw new ConfigurationException("Invalid duration: value must be positive"); + + this.quantity = quantity; + this.unit = unit; + } + + private DurationSpec(double quantity, TimeUnit unit) + { + this(Math.round(quantity), unit); + } + + /** + * Creates a {@code DurationSpec} of the specified amount of milliseconds. + * + * @param milliseconds the amount of milliseconds + * @return a duration + */ + public static DurationSpec inMilliseconds(long milliseconds) + { + return new DurationSpec(milliseconds, TimeUnit.MILLISECONDS); + } + + public static DurationSpec inDoubleMilliseconds(double milliseconds) + { + return new DurationSpec(milliseconds, TimeUnit.MILLISECONDS); + } + + /** + * Creates a {@code DurationSpec} of the specified amount of seconds. + * + * @param seconds the amount of seconds + * @return a duration + */ + public static DurationSpec inSeconds(long seconds) + { + return new DurationSpec(seconds, TimeUnit.SECONDS); + } + + /** + * Creates a {@code DurationSpec} of the specified amount of minutes. + * + * @param minutes the amount of minutes + * @return a duration + */ + public static DurationSpec inMinutes(long minutes) + { + return new DurationSpec(minutes, TimeUnit.MINUTES); + } + + /** + * Creates a {@code DurationSpec} of the specified amount of hours. + * + * @param hours the amount of hours + * @return a duration + */ + public static DurationSpec inHours(long hours) + { + return new DurationSpec(hours, TimeUnit.HOURS); + } + + /** + * Creates a {@code DurationSpec} of the specified amount of seconds. Custom method for special cases. + * + * @param value which can be in the old form only presenting the quantity or the post CASSANDRA-15234 form - a + * value consisting of quantity and unit. This method is necessary for three parameters which didn't change their + * names but only their value format. (key_cache_save_period, row_cache_save_period, counter_cache_save_period) + * @return a duration + */ + public static DurationSpec inSecondsString(String value) + { + //parse the string field value + Matcher matcher = VALUES_PATTERN.matcher(value); + + long seconds; + //if the provided string value is just a number, then we create a Duration Spec value in seconds + if (matcher.matches()) + { + seconds = Long.parseLong(value); + return new DurationSpec(seconds, TimeUnit.SECONDS); + } + + //otherwise we just use the standard constructors + return new DurationSpec(value); + } + + /** + * @param symbol the time unit symbol + * @return the time unit associated to the specified symbol + */ + static TimeUnit fromSymbol(String symbol) + { + switch (symbol.toLowerCase()) + { + case "d": return TimeUnit.DAYS; + case "h": return TimeUnit.HOURS; + case "m": return TimeUnit.MINUTES; + case "s": return TimeUnit.SECONDS; + case "ms": return TimeUnit.MILLISECONDS; + case "us": + case "µs": return TimeUnit.MICROSECONDS; + case "ns": return TimeUnit.NANOSECONDS; + } + throw new ConfigurationException(String.format("Unsupported time unit: %s. Supported units are: %s", + symbol, Arrays.stream(TimeUnit.values()) + .map(DurationSpec::getSymbol) + .collect(Collectors.joining(", ")))); + } + + /** + * @param targetUnit the time unit + * @return this duration in the specified time unit + */ + public long to(TimeUnit targetUnit) + { + return targetUnit.convert(quantity, unit); + } + + /** + * @return this duration in number of hours + */ + public long toHours() + { + return unit.toHours(quantity); + } + + /** + * Returns this duration in number of minutes as an {@code int} + * + * @return this duration in number of minutes or {@code Integer.MAX_VALUE} if the number of minutes is too large. + */ + public int toHoursAsInt() + { + return Ints.saturatedCast(toHours()); + } + + /** + * @return this duration in number of minutes + */ + public long toMinutes() + { + return unit.toMinutes(quantity); + } + + /** + * Returns this duration in number of minutes as an {@code int} + * + * @return this duration in number of minutes or {@code Integer.MAX_VALUE} if the number of minutes is too large. + */ + public int toMinutesAsInt() + { + return Ints.saturatedCast(toMinutes()); + } + + /** + * @return this duration in number of seconds + */ + public long toSeconds() + { + return unit.toSeconds(quantity); + } + + /** + * Returns this duration in number of seconds as an {@code int} + * + * @return this duration in number of seconds or {@code Integer.MAX_VALUE} if the number of seconds is too large. + */ + public int toSecondsAsInt() + { + return Ints.saturatedCast(toSeconds()); + } + + /** + * @return this duration in number of nanoseconds + */ + public long toNanoseconds() + { + return unit.toNanos(quantity); + } + + /** + * Returns this duration in number of nanoseconds as an {@code int} + * + * @return this duration in number of nanoseconds or {@code Integer.MAX_VALUE} if the number of nanoseconds is too large. + */ + public int toNanosecondsAsInt() + { + return Ints.saturatedCast(toNanoseconds()); + } + + /** + * @return this duration in number of milliseconds + */ + public long toMilliseconds() + { + return unit.toMillis(quantity); + } + + /** + * @return the duration value in milliseconds + */ + public static long toMilliseconds(DurationSpec quantity) + { + return quantity.toMilliseconds(); + } + + /** + * Returns this duration in number of milliseconds as an {@code int} + * + * @return this duration in number of milliseconds or {@code Integer.MAX_VALUE} if the number of milliseconds is too large. + */ + public int toMillisecondsAsInt() + { + return Ints.saturatedCast(toMilliseconds()); + } + + @Override + public int hashCode() + { + // Milliseconds seems to be a reasonable tradeoff + return Objects.hash(unit.toMillis(quantity)); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + + if (!(obj instanceof DurationSpec)) + return false; + + DurationSpec other = (DurationSpec) obj; + if (unit == other.unit) + return quantity == other.quantity; + + // Due to overflows we can only guarantee that the 2 durations are equal if we get the same results + // doing the conversion in both directions. + return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity; + } + + @Override + public String toString() + { + return quantity + getSymbol(unit); + } + + /** + * Returns the symbol associated to the specified unit + * + * @param unit the time unit + * @return the time unit symbol + */ + static String getSymbol(TimeUnit unit) + { + switch (unit) + { + case DAYS: return "d"; + case HOURS: return "h"; + case MINUTES: return "m"; + case SECONDS: return "s"; + case MILLISECONDS: return "ms"; + case MICROSECONDS: return "us"; + case NANOSECONDS: return "ns"; + } + throw new AssertionError(); + } +} diff --git a/test/unit/org/apache/cassandra/config/DataRateSpecTest.java b/test/unit/org/apache/cassandra/config/DataRateSpecTest.java new file mode 100644 index 0000000..73625fb --- /dev/null +++ b/test/unit/org/apache/cassandra/config/DataRateSpecTest.java @@ -0,0 +1,136 @@ +/* + * 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 org.junit.Test; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.quicktheories.core.Gen; +import org.quicktheories.generators.SourceDSL; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.*; +import static org.quicktheories.QuickTheory.qt; + +public class DataRateSpecTest +{ + @Test + public void testConversions() + { + assertEquals(10, new DataRateSpec("10B/s").toBytesPerSecond(), 0); + assertEquals(10240, new DataRateSpec("10KiB/s").toBytesPerSecond(), 0); + assertEquals(0, new DataRateSpec("10KiB/s").toMebibytesPerSecond(), 0.1); + assertEquals(10240, new DataRateSpec("10MiB/s").toKibibytesPerSecond(), 0); + assertEquals(10485760, new DataRateSpec("10MiB/s").toBytesPerSecond(), 0); + assertEquals(10485760, new DataRateSpec("10MiB/s").toBytesPerSecond(), 0); + assertEquals(new DataRateSpec("24MiB/s").toString(), DataRateSpec.megabitsPerSecondInMebibytesPerSecond(200L).toString()); + } + + @Test + public void testOverflowingDuringConversion() + { + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807B/s").toBytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807B/s").toBytesPerSecondAsInt(), 0); + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toBytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toBytesPerSecondAsInt(), 0); + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecondAsInt(), 0); + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecondAsInt(), 0); + + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMegabitsPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMegabitsPerSecondAsInt()); + + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toKibibytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toKibibytesPerSecondAsInt()); + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toKibibytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toKibibytesPerSecondAsInt()); + + assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMebibytesPerSecond(), 0); + assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMebibytesPerSecondAsInt()); + } + + @Test + public void testFromSymbol() + { + assertEquals(DataRateSpec.DataRateUnit.fromSymbol("B/s"), DataRateSpec.DataRateUnit.BYTES_PER_SECOND); + assertEquals(DataRateSpec.DataRateUnit.fromSymbol("KiB/s"), DataRateSpec.DataRateUnit.KIBIBYTES_PER_SECOND); + assertEquals(DataRateSpec.DataRateUnit.fromSymbol("MiB/s"), DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND); + assertThatThrownBy(() -> DataRateSpec.DataRateUnit.fromSymbol("n")) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported data rate unit: n"); + } + + @Test + public void testInvalidInputs() + { + assertThatThrownBy(() -> new DataRateSpec("10")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid bit rate: 10"); + assertThatThrownBy(() -> new DataRateSpec("-10b/s")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid bit rate: -10b/s"); + assertThatThrownBy(() -> new DataRateSpec("10xb/s")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid bit rate: 10xb/s"); + assertThatThrownBy(() -> new DataRateSpec("9223372036854775809B/s") + .toBytesPerSecond()).isInstanceOf(NumberFormatException.class) + .hasMessageContaining("For input string: \"9223372036854775809\""); + } + + @Test + public void testEquals() + { + assertEquals(new DataRateSpec("10B/s"), new DataRateSpec("10B/s")); + assertEquals(new DataRateSpec("10KiB/s"), new DataRateSpec("10240B/s")); + assertEquals(new DataRateSpec("10240B/s"), new DataRateSpec("10KiB/s")); + assertEquals(DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE), DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE)); + assertNotEquals(DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE), DataRateSpec.inBytesPerSecond(Long.MAX_VALUE)); + assertNotEquals(new DataRateSpec("0KiB/s"), new DataRateSpec("10MiB/s")); + } + + @Test + public void thereAndBack() + { + Gen<DataRateSpec.DataRateUnit> unitGen = SourceDSL.arbitrary().enumValues(DataRateSpec.DataRateUnit.class); + Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE); + qt().forAll(valueGen, unitGen).check((value, unit) -> { + DataRateSpec there = new DataRateSpec(value, unit); + DataRateSpec back = new DataRateSpec(there.toString()); + DataRateSpec BACK = new DataRateSpec(there.toString()); + return there.equals(back) && there.equals(BACK); + }); + } + + @Test + public void eq() + { + qt().forAll(gen(), gen()).check((a, b) -> a.equals(b) == b.equals(a)); + } + + @Test + public void eqAndHash() + { + qt().forAll(gen(), gen()).check((a, b) -> !a.equals(b) || a.hashCode() == b.hashCode()); + } + + private static Gen<DataRateSpec> gen() + { + Gen<DataRateSpec.DataRateUnit> unitGen = SourceDSL.arbitrary().enumValues(DataRateSpec.DataRateUnit.class); + Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE); + Gen<DataRateSpec> gen = rs -> new DataRateSpec(valueGen.generate(rs), unitGen.generate(rs)); + return gen.describedAs(DataRateSpec::toString); + } +} \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java b/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java new file mode 100644 index 0000000..66c6444 --- /dev/null +++ b/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java @@ -0,0 +1,141 @@ +/* + * 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.Locale; + +import org.junit.Test; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.quicktheories.core.Gen; +import org.quicktheories.generators.SourceDSL; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.*; +import static org.quicktheories.QuickTheory.qt; + +public class DataStorageSpecTest +{ + @Test + public void testConversions() + { + assertEquals(10, new DataStorageSpec("10B").toBytes()); + assertEquals(10240, new DataStorageSpec("10KiB").toBytes()); + assertEquals(0, new DataStorageSpec("10KiB").toMebibytes()); + assertEquals(10240, new DataStorageSpec("10MiB").toKibibytes()); + assertEquals(10485760, new DataStorageSpec("10MiB").toBytes()); + } + + @Test + public void testOverflowingDuringConversion() + { + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807B").toBytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807B").toBytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toBytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toBytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toBytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toBytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toBytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toBytesAsInt()); + + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toKibibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toKibibytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toKibibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toKibibytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toKibibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toKibibytesAsInt()); + + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toMebibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toMebibytesAsInt()); + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toMebibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toMebibytesAsInt()); + + assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toGibibytes()); + assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toGibibytesAsInt()); + } + + @Test + public void testFromSymbol() + { + assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("B"), DataStorageSpec.DataStorageUnit.BYTES); + assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("KiB"), DataStorageSpec.DataStorageUnit.KIBIBYTES); + assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("MiB"), DataStorageSpec.DataStorageUnit.MEBIBYTES); + assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("GiB"), DataStorageSpec.DataStorageUnit.GIBIBYTES); + assertThatThrownBy(() -> DataStorageSpec.DataStorageUnit.fromSymbol("n")) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported data storage unit: n"); + } + + @Test + public void testInvalidInputs() + { + assertThatThrownBy(() -> new DataStorageSpec("10")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid data storage: 10"); + assertThatThrownBy(() -> new DataStorageSpec("-10bps")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid data storage: -10bps"); + assertThatThrownBy(() -> new DataStorageSpec("-10b")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid data storage: -10b"); + assertThatThrownBy(() -> new DataStorageSpec("10HG")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid data storage: 10HG"); + assertThatThrownBy(() -> new DataStorageSpec("9223372036854775809B") + .toBytes()).isInstanceOf(NumberFormatException.class) + .hasMessageContaining("For input string: \"9223372036854775809\""); + } + + @Test + public void testEquals() + { + assertEquals(new DataStorageSpec("10B"), new DataStorageSpec("10B")); + assertEquals(new DataStorageSpec("10KiB"), new DataStorageSpec("10240B")); + assertEquals(new DataStorageSpec("10240B"), new DataStorageSpec("10KiB")); + assertEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inMebibytes(Long.MAX_VALUE)); + assertNotEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inKibibytes(Long.MAX_VALUE)); + assertNotEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inBytes(Long.MAX_VALUE)); + assertNotEquals(new DataStorageSpec("0MiB"), new DataStorageSpec("10KiB")); + } + + @Test + public void thereAndBack() + { + qt().forAll(gen()).check(there -> { + DataStorageSpec back = new DataStorageSpec(there.toString()); + DataStorageSpec BACK = new DataStorageSpec(there.toString().toUpperCase(Locale.ROOT).replace("I", "i")); + return there.equals(back) && there.equals(BACK); + }); + } + + @Test + public void eq() + { + qt().forAll(gen(), gen()).check((a, b) -> a.equals(b) == b.equals(a)); + } + + @Test + public void eqAndHash() + { + qt().forAll(gen(), gen()).check((a, b) -> !a.equals(b) || a.hashCode() == b.hashCode()); + } + + private static Gen<DataStorageSpec> gen() + { + Gen<DataStorageSpec.DataStorageUnit> unitGen = SourceDSL.arbitrary().enumValues(DataStorageSpec.DataStorageUnit.class); + Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE); + Gen<DataStorageSpec> gen = rs -> new DataStorageSpec(valueGen.generate(rs), unitGen.generate(rs)); + return gen.describedAs(DataStorageSpec::toString); + } +} \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/config/DurationSpecTest.java b/test/unit/org/apache/cassandra/config/DurationSpecTest.java new file mode 100644 index 0000000..84733d8 --- /dev/null +++ b/test/unit/org/apache/cassandra/config/DurationSpecTest.java @@ -0,0 +1,160 @@ +/* + * 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.apache.cassandra.exceptions.ConfigurationException; +import org.quicktheories.core.Gen; +import org.quicktheories.generators.SourceDSL; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.*; +import static org.quicktheories.QuickTheory.qt; + +public class DurationSpecTest +{ + @Test + public void testConversions() + { + assertEquals(10L, new DurationSpec("10s").toSeconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt()); + assertEquals(10000, new DurationSpec("10s").toMilliseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMillisecondsAsInt()); + assertEquals(0, new DurationSpec("10s").toMinutes()); + assertEquals(10, new DurationSpec("10m").toMinutes()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMinutesAsInt()); + assertEquals(600000, new DurationSpec("10m").toMilliseconds()); + assertEquals(600, new DurationSpec("10m").toSeconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt()); + assertEquals(DurationSpec.inDoubleMilliseconds(0.7), new DurationSpec("1ms")); + assertEquals(DurationSpec.inDoubleMilliseconds(0.33), new DurationSpec("0ms")); + assertEquals(DurationSpec.inDoubleMilliseconds(0.333), new DurationSpec("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"), TimeUnit.NANOSECONDS); + assertThatThrownBy(() -> DurationSpec.fromSymbol("n")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported time unit: n"); + } + + @Test + public void testGetSymbol() + { + assertEquals(DurationSpec.getSymbol(TimeUnit.MILLISECONDS), "ms"); + assertEquals(DurationSpec.getSymbol(TimeUnit.DAYS), "d"); + assertEquals(DurationSpec.getSymbol(TimeUnit.HOURS), "h"); + assertEquals(DurationSpec.getSymbol(TimeUnit.MINUTES), "m"); + assertEquals(DurationSpec.getSymbol(TimeUnit.SECONDS), "s"); + assertEquals(DurationSpec.getSymbol(TimeUnit.MICROSECONDS), "us"); + assertEquals(DurationSpec.getSymbol(TimeUnit.NANOSECONDS), "ns"); + } + + @Test + public void testInvalidInputs() + { + assertThatThrownBy(() -> new DurationSpec("10")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid duration: 10"); + assertThatThrownBy(() -> new DurationSpec("-10s")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid duration: -10s"); + assertThatThrownBy(() -> new DurationSpec("10xd")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid duration: 10xd"); + assertThatThrownBy(() -> new DurationSpec("0.333555555ms")).isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Invalid duration: 0.333555555ms"); + } + + @Test + public void testEquals() + { + assertEquals(new DurationSpec("10s"), new DurationSpec("10s")); + assertEquals(new DurationSpec("10s"), new DurationSpec("10000ms")); + assertEquals(new DurationSpec("10000ms"), new DurationSpec("10s")); + assertEquals(DurationSpec.inMinutes(Long.MAX_VALUE), DurationSpec.inMinutes(Long.MAX_VALUE)); + assertEquals(new DurationSpec("4h"), new DurationSpec("14400s")); + assertEquals(DurationSpec.inSecondsString("14400"), new DurationSpec("14400s")); + assertEquals(DurationSpec.inSecondsString("4h"), new DurationSpec("14400s")); + assertEquals(DurationSpec.inSecondsString("14400s"), new DurationSpec("14400s")); + assertEquals(DurationSpec.inHours(Long.MAX_VALUE),DurationSpec.inHours(Long.MAX_VALUE)); + assertNotEquals(DurationSpec.inMinutes(Long.MAX_VALUE), DurationSpec.inMilliseconds(Long.MAX_VALUE)); + assertNotEquals(new DurationSpec("0m"), new DurationSpec("10ms")); + } + + @Test + public void thereAndBack() + { + Gen<TimeUnit> unitGen = SourceDSL.arbitrary().enumValues(TimeUnit.class); + Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE); + qt().forAll(valueGen, unitGen).check((value, unit) -> { + DurationSpec there = new DurationSpec(value, unit); + DurationSpec back = new DurationSpec(there.toString()); + return there.equals(back); + }); + } + + @Test + public void testOverflowingDuringConversion() + { + // we are heavily dependent on the Java TimeUnit for our configuration of type duration. We want to be sure + // that any regression in handlining overflow will be caught quickly on our end + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ns").toNanoseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ns").toNanosecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ms").toNanoseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ms").toNanosecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toNanoseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toNanosecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toNanoseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toNanosecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toNanoseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toNanosecondsAsInt()); + + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ms").toMilliseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ms").toMillisecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toMilliseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMillisecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toMilliseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toMillisecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toMilliseconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toMillisecondsAsInt()); + + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toSeconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toSeconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toSecondsAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toSeconds()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toSecondsAsInt()); + + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toMinutes()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toMinutesAsInt()); + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toMinutes()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toMinutesAsInt()); + + assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toHours()); + assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toHoursAsInt()); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org