Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion fdb-test-utils/fdb-test-utils.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,44 @@
* limitations under the License.
*/

plugins {
alias(libs.plugins.errorprone)
}

apply from: rootProject.file('gradle/publishing.gradle')

dependencies {
api(libs.bundles.test.impl)
api(libs.slf4j.api)
compileOnly(libs.bundles.test.compileOnly)
compileOnly(libs.jsr305)
compileOnly(libs.jspecify)
annotationProcessor(libs.autoService)
implementation(libs.fdbJava)
implementation(libs.snakeyaml)

errorprone(libs.errorprone.core)
errorprone(libs.nullaway)

testImplementation(libs.bundles.test.impl)
testRuntimeOnly(libs.bundles.test.runtime)
testCompileOnly(libs.bundles.test.compileOnly)
testAnnotationProcessor(libs.autoService)
}

// jspecify + NullAway null-checking, scoped to this module only. See @NullMarked
// package-info.java files in com.apple.foundationdb.test / com.apple.test. Unlike the other
// modules that have adopted this, fdb-test-utils has two unrelated top-level packages, so both
// are listed in AnnotatedPackages.
tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableAllChecks = true
error("NullAway")
option("NullAway:AnnotatedPackages", "com.apple.foundationdb.test,com.apple.test")
option("NullAway:JSpecifyMode", "true")
option("NullAway:AcknowledgeRestrictiveAnnotations", "true")
}
}

publishing {
publications {
library(MavenPublication) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
import org.assertj.core.api.Assumptions;
import org.yaml.snakeyaml.Yaml;

import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -37,7 +38,9 @@
* that are available.
*/
public final class FDBTestEnvironment {
private static final List<String> clusterFiles;
// A cluster file entry may be null, meaning "use the default cluster file" (no fdb-environment.yaml
// configured, or an environment entry that intentionally leaves the cluster file unspecified).
private static final List<@Nullable String> clusterFiles;

static {
final String fdbEnvironment = System.getenv("FDB_ENVIRONMENT_YAML");
Expand All @@ -57,7 +60,11 @@ private static List<String> parseFDBEnvironmentYaml(final String fdbEnvironment)
Yaml yaml = new Yaml();
try (FileInputStream yamlInput = new FileInputStream(fdbEnvironment)) {
Object fdbConfig = yaml.load(yamlInput);
return (List<String>)((Map<?, ?>)fdbConfig).get("clusterFiles");
List<String> parsedClusterFiles = (List<String>)((Map<?, ?>)fdbConfig).get("clusterFiles");
if (parsedClusterFiles == null) {
throw new IllegalStateException("fdb-environment.yaml at " + fdbEnvironment + " does not define \"clusterFiles\"");
}
return parsedClusterFiles;
} catch (IOException e) {
throw new IllegalStateException("Could not read fdb-environment.yaml", e);
} catch (ClassCastException e) {
Expand All @@ -70,16 +77,17 @@ public static String getClusterFile(int i) {
return clusterFiles.get(i);
}

public static List<String> allClusterFiles() {
public static List<@Nullable String> allClusterFiles() {
return clusterFiles;
}

public static List<String> allClusterFilesInRandomOrder() {
final List<String> randomized = new ArrayList<>(clusterFiles);
public static List<@Nullable String> allClusterFilesInRandomOrder() {
final List<@Nullable String> randomized = new ArrayList<>(clusterFiles);
Collections.shuffle(randomized);
return randomized;
}

@Nullable
public static String randomClusterFile() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real finding: randomClusterFile() was previously declared to return plain String even though clusterFiles can contain a null entry (the "no fdb-environment.yaml configured" case uses Collections.singletonList(null)), and every other accessor here (getClusterFile(int), and now allClusterFiles()/allClusterFilesInRandomOrder()) already exposed that nullability. So callers of randomClusterFile() (e.g. TestDatabaseExtension.getDatabase()) had no static signal that a null cluster file could come back. Adding @Nullable here, plus widening the backing list to List<@Nullable String>, documents a contract that was previously only true by accident.

return clusterFiles.get(ThreadLocalRandom.current().nextInt(clusterFiles.size()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import org.jspecify.annotations.Nullable;

public class RootLogLevelExtension implements BeforeEachCallback, AfterEachCallback {
private final Level tempLevel;
@Nullable
private Level original;

public RootLogLevelExtension(Level tempLevel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,19 @@
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import javax.annotation.Nonnull;

/**
* Variant of {@link TestSubspaceExtension} that can be used to create a test subspace that is shared by
* all tests in a given class. This will ensure that the subspace is cleared out at the conclusion of
* all tests have completed, whereas the {@link TestSubspaceExtension} clears out the subspace at the end
* of every test.
*/
public class TestClassSubspaceExtension implements AfterAllCallback {
@Nonnull
private final TestSubspaceExtension subspaceExtension;

public TestClassSubspaceExtension(@Nonnull TestDatabaseExtension dbExtension) {
public TestClassSubspaceExtension(TestDatabaseExtension dbExtension) {
this.subspaceExtension = new TestSubspaceExtension(dbExtension);
}

@Nonnull
public Subspace getSubspace() {
return subspaceExtension.getSubspace();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;

import java.util.Objects;
import java.util.concurrent.Executor;

Expand Down Expand Up @@ -64,9 +64,9 @@ public class TestDatabaseExtension implements BeforeAllCallback, AfterAllCallbac
* timeouts. Using a dedicated cached pool here mirrors what {@code FDBDatabaseExtension} does for
* {@code FDBDatabaseFactory.setExecutor}.
*/
@Nonnull
private static final Executor threadPoolExecutor = TestExecutors.newThreadPool("fdb-extensions-test");

@Nullable
private Database db;

public TestDatabaseExtension() {
Expand All @@ -80,7 +80,6 @@ public static int getAPIVersion() {
return apiVersion;
}

@Nonnull
private static FDB getFDB() {
if (fdb == null) {
synchronized (TestDatabaseExtension.class) {
Expand All @@ -104,7 +103,6 @@ public void beforeAll(final ExtensionContext extensionContext) {
getFDB();
}

@Nonnull
public Database getDatabase() {
if (db == null) {
db = FDB.instance().open(FDBTestEnvironment.randomClusterFile(), threadPoolExecutor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

package com.apple.foundationdb.test;

import javax.annotation.Nonnull;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
Expand All @@ -30,7 +29,6 @@
* Executors to use during testing.
*/
public final class TestExecutors {
@Nonnull
private static final Executor DEFAULT_THREAD_POOL = newThreadPool("fdb-unit-test");

private TestExecutors() {
Expand All @@ -43,7 +41,7 @@ public static class TestThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger count;

public TestThreadFactory(@Nonnull String namePrefix) {
public TestThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
this.count = new AtomicInteger();
}
Expand All @@ -57,11 +55,10 @@ public Thread newThread(final Runnable r) {
}
}

public static Executor newThreadPool(@Nonnull String namePrefix) {
public static Executor newThreadPool(String namePrefix) {
return Executors.newCachedThreadPool(new TestThreadFactory(namePrefix));
}

@Nonnull
public static Executor defaultThreadPool() {
return DEFAULT_THREAD_POOL;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;

import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -63,7 +63,6 @@ public TestSubspaceExtension(TestDatabaseExtension dbExtension) {
this.dbExtension = dbExtension;
}

@Nonnull
public Subspace getSubspace() {
if (subspace == null) {
subspace = dbExtension.getDatabase().runAsync(tr ->
Expand All @@ -80,11 +79,12 @@ public Subspace getSubspace() {
@Override
public void afterEach(final ExtensionContext extensionContext) {
if (subspace != null) {
final Subspace subspaceToClear = subspace;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("clearing test subspace subspace=\"{}\"", subspace);
LOGGER.debug("clearing test subspace subspace=\"{}\"", subspaceToClear);
}
dbExtension.getDatabase().run(tx -> {
tx.clear(Range.startsWith(subspace.pack()));
tx.clear(Range.startsWith(subspaceToClear.pack()));
return null;
});
subspace = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* package-info.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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.
*/

/**
* JUnit extensions and utilities for interacting with FoundationDB in tests.
*/
@NullMarked
package com.apple.foundationdb.test;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* argument.
*/
class BooleanArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<BooleanSource> {
private String[] names;
private String[] names = new String[0];

@Override
public void accept(BooleanSource booleanSource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* {@link org.junit.jupiter.params.ParameterizedTest} test.
*/
public class RandomSeedProvider implements ArgumentsProvider, AnnotationConsumer<RandomSeedSource> {
private long[] fixedSeeds;
private long[] fixedSeeds = new long[0];

@Override
public void accept(final RandomSeedSource annotation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import org.junit.jupiter.params.provider.Arguments;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -86,7 +85,6 @@ public static Stream<Arguments> randomArguments(Function<Random, Arguments> rand
* @param staticSeeds a set of seeds to always include in the returned random seeds
* @return a stream of random {@code long}s to initialize {@link Random}s
*/
@Nonnull
public static Stream<Long> randomSeeds(long... staticSeeds) {
LongStream longStream = staticSeeds.length == 0 ? LongStream.of(FIXED_SEED) : LongStream.of(staticSeeds);
if (includeRandomTests()) {
Expand Down
28 changes: 28 additions & 0 deletions fdb-test-utils/src/main/java/com/apple/test/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* package-info.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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.
*/

/**
* General-purpose JUnit test utilities (parameterized test helpers, tags, annotations) that are
* not specific to FoundationDB.
*/
@NullMarked
package com.apple.test;

import org.jspecify.annotations.NullMarked;
Loading