blob: d19a3569e8a884d596c5cbe3336744631f2f761b [file] [log] [blame]
/*
* 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.ignite.internal.testframework;
import static java.lang.Thread.sleep;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.function.Function.identity;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.ignite.internal.lang.IgniteInternalException;
import org.apache.ignite.internal.lang.IgniteStringFormatter;
import org.apache.ignite.internal.lang.RunnableX;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
import org.apache.ignite.internal.thread.IgniteThreadFactory;
import org.apache.ignite.internal.thread.NamedThreadFactory;
import org.apache.ignite.internal.thread.ThreadOperation;
import org.apache.ignite.internal.util.ExceptionUtils;
import org.apache.ignite.lang.IgniteException;
import org.hamcrest.CustomMatcher;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.function.Executable;
/**
* Utility class for tests.
*/
public final class IgniteTestUtils {
private static final IgniteLogger LOG = Loggers.forClass(IgniteTestUtils.class);
private static final int TIMEOUT_SEC = 30;
/**
* Set object field value via reflection.
*
* @param obj Object to set field value to.
* @param fieldName Field name to set value for.
* @param val New field value.
* @throws IgniteInternalException In case of error.
*/
public static void setFieldValue(Object obj, String fieldName, @Nullable Object val) throws IgniteInternalException {
assert obj != null;
assert fieldName != null;
try {
Class<?> cls = obj instanceof Class ? (Class<?>) obj : obj.getClass();
Field field = cls.getDeclaredField(fieldName);
boolean isFinal = (field.getModifiers() & Modifier.FINAL) != 0;
boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
/*
* http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5.3
* If a final field is initialized to a compile-time constant in the field declaration,
* changes to the final field may not be observed.
*/
if (isFinal && isStatic) {
throw new IgniteInternalException("Modification of static final field through reflection.");
}
if (!field.canAccess(obj)) {
field.setAccessible(true);
}
field.set(obj, val);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IgniteInternalException("Failed to set object field [obj=" + obj + ", field=" + fieldName + ']', e);
}
}
/**
* Set object field value via reflection.
*
* @param obj Object to set field value to.
* @param cls Class to get field from.
* @param fieldName Field name to set value for.
* @param val New field value.
* @throws IgniteInternalException In case of error.
*/
public static void setFieldValue(Object obj, Class<?> cls, String fieldName, Object val) throws IgniteInternalException {
assert fieldName != null;
try {
Field field = cls.getDeclaredField(fieldName);
if (!field.canAccess(obj)) {
field.setAccessible(true);
}
boolean isFinal = (field.getModifiers() & Modifier.FINAL) != 0;
boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
/*
* http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5.3
* If a final field is initialized to a compile-time constant in the field declaration,
* changes to the final field may not be observed.
*/
if (isFinal && isStatic) {
throw new IgniteInternalException("Modification of static final field through reflection.");
}
field.set(obj, val);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IgniteInternalException("Failed to set object field [obj=" + obj + ", field=" + fieldName + ']', e);
}
}
/**
* Finds a field in the given {@code target} object of the {@code declaredClass} type.
*
* @param target target object from which to get field ({@code null} for static methods)
* @param declaredClass class on which the field is declared
* @param fieldName name of the field
* @return field
*/
public static Field getField(@Nullable Object target, Class<?> declaredClass, String fieldName) {
Field field;
try {
field = declaredClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
throw new IgniteInternalException("Did not find a field", e);
}
if (!field.canAccess(target)) {
field.setAccessible(true);
}
return field;
}
/**
* Returns field value.
*
* @param target target object from which to get field value ({@code null} for static methods)
* @param declaredClass class on which the field is declared
* @param fieldName name of the field
* @return field value
*/
public static <T> T getFieldValue(@Nullable Object target, Class<?> declaredClass, String fieldName) {
try {
return (T) getField(target, declaredClass, fieldName).get(target);
} catch (IllegalAccessException e) {
throw new IgniteInternalException("Cannot get field value", e);
}
}
/**
* Get object field value via reflection.
*
* @param obj Object or class to get field value from.
* @param fieldNames Field names to get value for: obj->field1->field2->...->fieldN.
* @param <T> Expected field class.
* @return Field value.
* @throws IgniteInternalException In case of error.
*/
public static <T> T getFieldValue(@Nullable Object obj, String... fieldNames) {
assert obj != null;
assert fieldNames != null;
assert fieldNames.length >= 1;
for (String fieldName : fieldNames) {
Class<?> cls = obj instanceof Class ? (Class<?>) obj : obj.getClass();
try {
obj = getFieldValue(obj, cls, fieldName);
} catch (IgniteInternalException e) {
// Resolve inner class, if not an inner field.
Class<?> innerCls = getInnerClass(cls, fieldName);
if (innerCls == null) {
throw new IgniteInternalException("Failed to get object field [obj=" + obj
+ ", fieldNames=" + Arrays.toString(fieldNames) + ']', e);
}
obj = innerCls;
}
}
return (T) obj;
}
/**
* Get inner class by its name from the enclosing class.
*
* @param parentCls Parent class to resolve inner class for.
* @param innerClsName Name of the inner class.
* @return Inner class.
*/
@Nullable public static <T> Class<T> getInnerClass(Class<?> parentCls, String innerClsName) {
for (Class<?> cls : parentCls.getDeclaredClasses()) {
if (innerClsName.equals(cls.getSimpleName())) {
return (Class<T>) cls;
}
}
return null;
}
/**
* Checks whether runnable throws exception, which is itself of a specified class.
*
* @param cls Expected exception class.
* @param run Runnable to check.
* @param errorMessageFragment Fragment of the error text in the expected exception, {@code null} if not to be checked.
* @return Thrown throwable.
*/
public static Throwable assertThrows(
Class<? extends Throwable> cls,
Executable run,
@Nullable String errorMessageFragment
) {
Throwable throwable = Assertions.assertThrows(cls, run);
if (errorMessageFragment != null) {
assertThat(throwable.getMessage(), containsString(errorMessageFragment));
}
return throwable;
}
/**
* Checks whether runnable throws the correct {@link IgniteException}, which is itself of a specified class.
*
* @param expectedClass Expected exception class.
* @param expectedErrorCode Expected error code of the {@link IgniteException}.
* @param run Runnable to check.
* @param errorMessageFragment Fragment of the error text in the expected exception, {@code null} if not to be checked.
* @return Thrown throwable.
*/
public static Throwable assertThrowsWithCode(
Class<? extends IgniteException> expectedClass,
int expectedErrorCode,
Executable run,
@Nullable String errorMessageFragment
) {
try {
run.execute();
} catch (Throwable throwable) {
try {
assertInstanceOf(expectedClass, throwable);
} catch (AssertionError err) {
// An AssertionError from assertInstanceOf has nothing but a class name of the original exception.
AssertionError assertionError = new AssertionError(err);
assertionError.addSuppressed(throwable);
throw assertionError;
}
IgniteException igniteException = (IgniteException) throwable;
assertEquals(expectedErrorCode, igniteException.code(), "Invalid error code: " + igniteException.codeAsString());
if (errorMessageFragment != null) {
assertThat(throwable.getMessage(), containsString(errorMessageFragment));
}
return throwable;
}
throw new AssertionError("Exception has not been thrown.");
}
/**
* Checks whether runnable throws exception, which is itself of a specified class, or has a cause of the specified class.
*
* @param run Runnable to check.
* @param cls Expected exception class.
* @return Thrown throwable.
*/
public static Throwable assertThrowsWithCause(
RunnableX run,
Class<? extends Throwable> cls
) {
return assertThrowsWithCause(run, cls, null);
}
/**
* Checks whether runnable throws exception, which is itself of a specified class, or has a cause of the specified class.
*
* @param run Runnable to check.
* @param cls Expected exception class.
* @param msg Message text that should be in cause (if {@code null}, message won't be checked).
* @return Thrown throwable.
*/
public static Throwable assertThrowsWithCause(
RunnableX run,
Class<? extends Throwable> cls,
@Nullable String msg
) {
try {
run.run();
} catch (Throwable e) {
if (!hasCause(e, cls, msg)) {
fail("Exception is neither of a specified class, nor has a cause of the specified class: " + cls, e);
}
return e;
}
throw new AssertionError("Exception has not been thrown.");
}
/**
* Checks if passed in {@code 'Throwable'} has given class in {@code 'cause'} hierarchy
* <b>including</b> that throwable itself.
*
* <p>Note that this method follows includes {@link Throwable#getSuppressed()} into check.
*
* @param t Throwable to check.
* @param cls Cause classes to check.
* @param messageFragment Fragment that must be a substring of a cause message (if {@code null}, message won't be checked).
* @return {@code True} if one of the causing exception is an instance of passed in classes, {@code false} otherwise.
*/
public static boolean hasCause(
Throwable t,
Class<?> cls,
@Nullable String messageFragment
) {
for (Throwable th = t; th != null; th = th.getCause()) {
if (cls.isAssignableFrom(th.getClass())) {
if (messageFragment == null) {
return true;
}
if (th.getMessage() != null && th.getMessage().contains(messageFragment)) {
return true;
}
}
for (Throwable n : th.getSuppressed()) {
if (hasCause(n, cls, messageFragment)) {
return true;
}
}
if (th.getCause() == th) {
break;
}
}
return false;
}
/**
* Checks if passed in {@code 'Throwable'} has given class in {@code 'cause'} hierarchy
* <b>including</b> that throwable itself.
*
* <p>Note that this method follows includes {@link Throwable#getSuppressed()} into check.
*
* @param t Throwable to check.
* @param cls Cause classes to check.
* @return reference to the cause error if found, otherwise returns {@code null}.
*/
public static <T extends Throwable> @Nullable T cause(Throwable t, Class<T> cls) {
return cause(t, cls, null);
}
/**
* Checks if passed in {@code 'Throwable'} has given class in {@code 'cause'} hierarchy
* <b>including</b> that throwable itself.
*
* <p>Note that this method follows includes {@link Throwable#getSuppressed()} into check.
*
* @param t Throwable to check.
* @param cls Cause classes to check.
* @param msg Message text that should be in cause (if {@code null}, message won't be checked).
* @return reference to the cause error if found, otherwise returns {@code null}.
*/
public static <T extends Throwable> @Nullable T cause(
Throwable t,
Class<T> cls,
@Nullable String msg
) {
for (Throwable th = t; th != null; th = th.getCause()) {
if (cls.isAssignableFrom(th.getClass())) {
if (msg != null) {
if (th.getMessage() != null && th.getMessage().contains(msg)) {
return (T) th;
} else {
continue;
}
}
return (T) th;
}
if (th.getCause() == th) {
break;
}
}
return null;
}
/**
* Runs runnable task asyncronously.
*
* @param task Runnable.
* @return Future with task result.
*/
public static CompletableFuture<Void> runAsync(RunnableX task) {
return runAsync(task, "async-runnable-runner");
}
/**
* Runs runnable task asyncronously.
*
* @param task Runnable.
* @return Future with task result.
*/
public static CompletableFuture<Void> runAsync(RunnableX task, String threadName) {
return runAsync(() -> {
try {
task.run();
} catch (Throwable e) {
sneakyThrow(e);
}
return null;
}, threadName);
}
/**
* Runs callable task asyncronously.
*
* @param task Callable.
* @return Future with task result.
*/
public static <T> CompletableFuture<T> runAsync(Callable<T> task) {
return runAsync(task, "async-callable-runner");
}
/**
* Runs callable task asyncronously.
*
* @param task Callable.
* @param threadName Thread name.
* @return Future with task result.
*/
public static <T> CompletableFuture<T> runAsync(Callable<T> task, String threadName) {
ThreadFactory thrFactory = IgniteThreadFactory.withPrefix(threadName, LOG, ThreadOperation.values());
CompletableFuture<T> fut = new CompletableFuture<T>();
thrFactory.newThread(() -> {
try {
// Execute task.
T res = task.call();
fut.complete(res);
} catch (Throwable e) {
fut.completeExceptionally(e);
}
}).start();
return fut;
}
/**
* Executes an asynchronous operation in a thread that is allowed to execute any thread operation.
*
* @param operation Operation to execute.
* @return Whatever the operation returns.
*/
public static <T> CompletableFuture<T> bypassingThreadAssertionsAsync(Supplier<CompletableFuture<T>> operation) {
return runAsync(operation::get).thenCompose(identity());
}
/**
* Executes a synchronous operation in a thread that is allowed to execute any thread operation.
*
* @param operation Operation to execute.
* @return Whatever the operation returns.
*/
public static <T> T bypassingThreadAssertions(Supplier<T> operation) {
return await(runAsync(operation::get));
}
/**
* Executes a synchronous operation in a thread that is allowed to execute any thread operation.
*
* @param operation Operation to execute.
*/
public static void bypassingThreadAssertions(Runnable operation) {
await(runAsync(operation::run));
}
/**
* Runs callable tasks each in separate threads.
*
* @param calls Callable tasks.
* @param threadFactory Thread factory.
* @return Execution time in milliseconds.
* @throws Exception If failed.
*/
public static long runMultiThreaded(Iterable<Callable<?>> calls, ThreadFactory threadFactory) throws Exception {
Collection<Thread> threads = new ArrayList<>();
Collection<CompletableFuture<?>> futures = new ArrayList<>();
for (Callable<?> task : calls) {
CompletableFuture<?> fut = new CompletableFuture<>();
futures.add(fut);
threads.add(threadFactory.newThread(() -> {
try {
// Execute task.
task.call();
fut.complete(null);
} catch (Throwable e) {
fut.completeExceptionally(e);
}
}));
}
long time = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
// Wait threads finish their job.
try {
for (Thread t : threads) {
t.join();
}
} catch (InterruptedException e) {
for (Thread t : threads) {
t.interrupt();
}
throw e;
}
time = System.currentTimeMillis() - time;
for (CompletableFuture<?> fut : futures) {
fut.join();
}
return time;
}
/**
* Runs callable tasks in specified number of threads.
*
* @param call Callable.
* @param threadNum Number of threads.
* @param threadName Thread names.
* @return Execution time in milliseconds.
* @throws Exception If failed.
*/
public static long runMultiThreaded(Callable<?> call, int threadNum, String threadName) throws Exception {
List<Callable<?>> calls = Collections.nCopies(threadNum, call);
NamedThreadFactory threadFactory = new NamedThreadFactory(threadName, LOG);
return runMultiThreaded(calls, threadFactory);
}
/**
* Runs runnable object in specified number of threads.
*
* @param run Target runnable.
* @param threadNum Number of threads.
* @param threadName Thread name.
* @return Future for the run. Future returns execution time in milliseconds.
*/
public static CompletableFuture<Long> runMultiThreadedAsync(Runnable run, int threadNum, String threadName) {
return runMultiThreadedAsync(() -> {
run.run();
return null;
}, threadNum, threadName);
}
/**
* Runs callable object in specified number of threads.
*
* @param call Callable.
* @param threadNum Number of threads.
* @param threadName Thread names.
* @return Future for the run. Future returns execution time in milliseconds.
*/
public static CompletableFuture<Long> runMultiThreadedAsync(Callable<?> call, int threadNum, String threadName) {
List<Callable<?>> calls = Collections.<Callable<?>>nCopies(threadNum, call);
NamedThreadFactory threadFactory = new NamedThreadFactory(threadName, LOG);
return runAsync(() -> runMultiThreaded(calls, threadFactory));
}
/**
* Waits for the condition.
*
* @param cond Condition.
* @param timeoutMillis Timeout in milliseconds.
* @return {@code True} if the condition was satisfied within the timeout.
* @throws InterruptedException If waiting was interrupted.
*/
public static boolean waitForCondition(BooleanSupplier cond, long timeoutMillis) throws InterruptedException {
return waitForCondition(cond, 10, timeoutMillis);
}
/**
* Waits for the condition.
*
* @param cond Condition.
* @param sleepMillis Sleep im milliseconds.
* @param timeoutMillis Timeout in milliseconds.
* @return {@code True} if the condition was satisfied within the timeout.
* @throws InterruptedException If waiting was interrupted.
*/
@SuppressWarnings("BusyWait")
public static boolean waitForCondition(BooleanSupplier cond, long sleepMillis, long timeoutMillis) throws InterruptedException {
long stop = System.currentTimeMillis() + timeoutMillis;
while (System.currentTimeMillis() < stop) {
if (cond.getAsBoolean()) {
return true;
}
sleep(sleepMillis);
}
return false;
}
/**
* Returns random BitSet.
*
* @param rnd Random generator.
* @param bits Amount of bits in bitset.
* @return Random BitSet.
*/
public static BitSet randomBitSet(Random rnd, int bits) {
BitSet set = new BitSet();
for (int i = 0; i < bits; i++) {
if (rnd.nextBoolean()) {
set.set(i);
}
}
return set;
}
/**
* Returns random byte array.
*
* @param rnd Random generator.
* @param len Byte array length.
* @return Random byte array.
*/
public static byte[] randomBytes(Random rnd, int len) {
byte[] data = new byte[len];
rnd.nextBytes(data);
return data;
}
/**
* Returns random string.
*
* @param rnd Random generator.
* @param len String length.
* @return Random string.
*/
public static String randomString(Random rnd, int len) {
StringBuilder sb = new StringBuilder();
while (sb.length() < len) {
char pt = (char) rnd.nextInt(Character.MAX_VALUE + 1);
if (Character.isDefined(pt)
&& Character.getType(pt) != Character.PRIVATE_USE
&& !Character.isSurrogate(pt)
) {
sb.append(pt);
}
}
return sb.toString();
}
/**
* Creates a unique Ignite node name for the given test.
*
* @param testInfo Test info.
* @param idx Node index.
*
* @return Node name.
*/
public static String testNodeName(TestInfo testInfo, int idx) {
String testMethodName = testInfo.getTestMethod().map(Method::getName).orElse("null");
String testClassName = testInfo.getTestClass().map(Class::getSimpleName).orElse("null");
return IgniteStringFormatter.format("{}_{}_{}",
shortTestMethodName(testClassName),
shortTestMethodName(testMethodName),
idx);
}
/**
* Returns a short version of the method name, such as "testShortMethodName" to "tsmn".
*
* @param methodName Method name for example "testShortMethodName".
*/
public static String shortTestMethodName(String methodName) {
assert !methodName.isBlank() : methodName;
StringBuilder sb = new StringBuilder();
char[] chars = methodName.trim().toCharArray();
sb.append(chars[0]);
for (int i = 1; i < chars.length; i++) {
char c = chars[i];
if (Character.isUpperCase(c)) {
sb.append(c);
}
}
return sb.toString().toLowerCase();
}
/**
* Throw an exception as if it were unchecked.
*
* <p>This method erases type of the exception in the thrown clause, so checked exception could be thrown without need to wrap it with
* unchecked one or adding a similar throws clause to the upstream methods.
*/
public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}
/**
* Awaits completion of the given stage and returns its result.
*
* @param stage The stage.
* @param timeout Maximum time to wait.
* @param unit Time unit of the timeout argument.
* @param <T> Type of the result returned by the stage.
* @return A result of the stage.
*/
@SuppressWarnings("UnusedReturnValue")
public static <T> T await(CompletionStage<T> stage, long timeout, TimeUnit unit) {
try {
return stage.toCompletableFuture().get(timeout, unit);
} catch (Throwable e) {
// In tests, we generally interested in original exception,
// rather than ExecutionException generated by the future's
// contract. But topmost frame keeps important information
// about failed operation in clients code. So, let's keep
// this information in a kinda unusual yet sufficient for
// further debug way
Throwable original = ExceptionUtils.unwrapCause(e);
if (original != e) {
original.addSuppressed(new RuntimeException("This is a trimmed root"));
}
sneakyThrow(original);
}
throw new AssertionError("Should not get here");
}
/**
* Awaits completion of the given stage and returns its result.
*
* @param stage The stage.
* @param <T> Type of the result returned by the stage.
* @return A result of the stage.
*/
@SuppressWarnings("UnusedReturnValue")
public static <T> T await(CompletionStage<T> stage) {
return await(stage, TIMEOUT_SEC, TimeUnit.SECONDS);
}
/**
* {@link #runRace(long, RunnableX...)} with default timeout of 10 seconds.
*/
public static void runRace(RunnableX... actions) {
runRace(TimeUnit.SECONDS.toMillis(10), actions);
}
/**
* Runs all actions, each in a separate thread, having a {@link CyclicBarrier} before calling {@link RunnableX#run()}.
* Waits for threads completion or fails with the assertion if timeout exceeded.
*
* @throws AssertionError In case of timeout or if any of the runnables thrown an exception.
*/
public static void runRace(long timeoutMillis, RunnableX... actions) {
int length = actions.length;
if (length == 0) {
return; // Nothing to run.
}
CyclicBarrier barrier = new CyclicBarrier(length);
Set<Throwable> throwables = ConcurrentHashMap.newKeySet();
Thread[] threads = IntStream.range(0, length).mapToObj(i -> new Thread(() -> {
try {
barrier.await();
actions[i].run();
} catch (Throwable e) {
throwables.add(e);
}
})).toArray(Thread[]::new);
Stream.of(threads).forEach(Thread::start);
long endTs = System.currentTimeMillis() + timeoutMillis;
try {
for (Thread thread : threads) {
thread.join(Math.max(1, endTs - System.currentTimeMillis()));
if (thread.isAlive()) {
throw new InterruptedException(); // Interrupt all actions and fail the race.
}
}
} catch (InterruptedException e) {
for (Thread thread : threads) {
thread.interrupt();
}
fail("Race operations took too long.");
}
if (!throwables.isEmpty()) {
AssertionError assertionError = new AssertionError("One or several threads have failed.");
for (Throwable throwable : throwables) {
assertionError.addSuppressed(throwable);
}
throw assertionError;
}
}
/**
* Returns a file system path for a resource name.
*
* @param cls A class.
* @param resourceName A resource name.
* @return A file system path matching the path component of the resource URL.
*/
public static String getResourcePath(Class<?> cls, String resourceName) {
return getPath(cls.getClassLoader().getResource(resourceName)).toString();
}
/**
* Converts a URL to a file system path.
*
* <p>This method is needed to get a proper file system name on the Windows platform. For portable code
* it should be used instead of URL::getPath().
*
* <p>For example, given a URL <i>file:///C:/dir/file.ext</i>, this method returns a Windows-specific
* path: <i>C:\dir\file.ext</i>.
*
* <p>While the URL::getPath() method returns a path that will not work for file system API on Windows:
* <i>/C:/dir/file.ext</i>.
*
* <p>There is no such problem on UNIX-like systems where given an URL <i>file:///dir/file.ext</i>,
* both methods return the same result: <i>/dir/file.ext</i>
*
* @param url A resource URL.
* @return A file system path matching the path component of the URL.
*/
public static Path getPath(URL url) {
try {
return Path.of(url.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e); // Shouldn't happen if the URL is obtained from the class loader.
}
}
/**
* Adds escape characters before backslashes in a path (on Windows), e.g. for the HOCON config parser.
*
* <p>For example, given a path argument <i>C:\dir\file.ext</i>, this method returns <i>C:\\dir\\file.ext</i>.
*
* @param path A path string.
* @return A path string with escaped backslashes.
*/
public static String escapeWindowsPath(String path) {
return path.replace("\\", "\\\\");
}
/**
* Generate file with dummy content with provided size.
*
* @param file File path.
* @param fileSize File size in bytes.
* @throws IOException if an I/O error is thrown.
*/
public static void fillDummyFile(Path file, long fileSize) throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(file, WRITE, CREATE)) {
channel.position(fileSize - 4);
ByteBuffer buf = ByteBuffer.allocate(4).putInt(2);
buf.rewind();
channel.write(buf);
}
}
/**
* Predicate matcher.
*
* @param <DataT> Data type.
*/
public static class PredicateMatcher<DataT> extends CustomMatcher<DataT> {
/** Predicate. */
private final Predicate<DataT> predicate;
/**
* Constructor.
*
* @param pred Predicate.
* @param msg Error description.
*/
public PredicateMatcher(Predicate<DataT> pred, String msg) {
super(msg);
predicate = pred;
}
/** {@inheritDoc} */
@Override
public boolean matches(Object o) {
return predicate.test((DataT) o);
}
}
}