This is an automated email from the ASF dual-hosted git repository.

chia7712 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/trunk by this push:
     new c12e01b9a16 MINOR: Remove jqwik dependency (#22604)
c12e01b9a16 is described below

commit c12e01b9a16a640e4a3c8c1cf85ba5983341cf00
Author: Mickael Maison <[email protected]>
AuthorDate: Tue Jun 23 14:39:00 2026 +0200

    MINOR: Remove jqwik dependency (#22604)
    
    Update test to use plain JUnit logic.
    
    The latest jqwik release introduced an anti AI clause
    (https://jqwik.net/docs/1.10.1/user-guide.html#anti-ai-usage-clause).
    
    Reviewers: Chia-Ping Tsai <[email protected]>
---
 NOTICE                                             |   4 -
 NOTICE-binary                                      |   4 -
 build.gradle                                       |   5 +-
 checkstyle/import-control-group-coordinator.xml    |   1 -
 checkstyle/import-control-metadata.xml             |   1 -
 checkstyle/import-control-server-common.xml        |   1 -
 checkstyle/import-control-server.xml               |   1 -
 checkstyle/import-control-storage.xml              |   1 -
 checkstyle/import-control.xml                      |   2 -
 gradle/dependencies.gradle                         |   2 -
 .../kafka/metadata/PartitionRegistrationTest.java  |  36 +-
 .../authorizer/StandardAuthorizerPropertyTest.java | 267 ++++-----
 .../kafka/raft/KafkaRaftClientFetchTest.java       |  15 +-
 .../java/org/apache/kafka/raft/MockLogTest.java    |  28 +-
 .../apache/kafka/raft/RaftEventSimulationTest.java | 642 ++++++++++-----------
 .../kafka/raft/internals/KafkaRaftLogTest.java     |  39 +-
 .../kafka/raft/internals/RecordsIteratorTest.java  | 117 ++--
 .../record/internal/ArbitraryMemoryRecords.java    |  31 +-
 18 files changed, 559 insertions(+), 638 deletions(-)

diff --git a/NOTICE b/NOTICE
index 301c99863fd..eaa0b8690d2 100644
--- a/NOTICE
+++ b/NOTICE
@@ -7,10 +7,6 @@ The Apache Software Foundation (https://www.apache.org/).
 This distribution has a binary dependency on jersey, which is available under 
the CDDL
 License. The source code of jersey can be found at 
https://github.com/jersey/jersey/.
 
-This distribution has a binary test dependency on jqwik, which is available 
under
-the Eclipse Public License 2.0. The source code can be found at
-https://github.com/jlink/jqwik.
-
 The streams-scala (streams/streams-scala) module was donated by Lightbend and 
the original code was copyrighted by them:
 Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
 Copyright (C) 2017-2018 Alexis Seigneurin.
diff --git a/NOTICE-binary b/NOTICE-binary
index 54a5ffe7856..3a8292e35f6 100644
--- a/NOTICE-binary
+++ b/NOTICE-binary
@@ -7,10 +7,6 @@ The Apache Software Foundation (https://www.apache.org/).
 This distribution has a binary dependency on jersey, which is available under 
the CDDL
 License. The source code of jersey can be found at 
https://github.com/jersey/jersey/.
 
-This distribution has a binary test dependency on jqwik, which is available 
under
-the Eclipse Public License 2.0. The source code can be found at
-https://github.com/jlink/jqwik.
-
 The streams-scala (streams/streams-scala) module was donated by Lightbend and 
the original code was copyrighted by them:
 Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
 Copyright (C) 2017-2018 Alexis Seigneurin.
diff --git a/build.gradle b/build.gradle
index 6b25da9cab5..5b8c8e8c874 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1441,7 +1441,6 @@ project(':metadata') {
     testFixturesImplementation libs.junitJupiter
 
     testImplementation libs.junitJupiter
-    testImplementation libs.jqwik
     testImplementation libs.mockitoCore
     testImplementation testFixtures(project(':clients'))
     testImplementation testFixtures(project(':raft'))
@@ -2215,7 +2214,6 @@ project(':raft') {
     testFixturesImplementation project(':clients')
     testFixturesImplementation testFixtures(project(':clients'))
     testFixturesImplementation project(':server-common')
-    testFixturesImplementation libs.jqwik
     testFixturesImplementation libs.junitJupiter
     testFixturesImplementation libs.mockitoCore
     testFixturesImplementation libs.slf4jApi
@@ -2227,7 +2225,6 @@ project(':raft') {
     testImplementation libs.jacksonDataformatYaml
     testImplementation libs.junitJupiter
     testImplementation libs.mockitoCore
-    testImplementation libs.jqwik
     testImplementation testLog4j2Libs
 
     testRuntimeOnly runtimeTestLibs
@@ -2293,7 +2290,7 @@ project(':raft') {
 
   test {
     useJUnitPlatform {
-      includeEngines 'jqwik', 'junit-jupiter'
+      includeEngines 'junit-jupiter'
     }
   }
 
diff --git a/checkstyle/import-control-group-coordinator.xml 
b/checkstyle/import-control-group-coordinator.xml
index 0e9aabad4a2..befe4b002b3 100644
--- a/checkstyle/import-control-group-coordinator.xml
+++ b/checkstyle/import-control-group-coordinator.xml
@@ -33,7 +33,6 @@
     <allow pkg="javax.net.ssl" />
     <allow pkg="javax.security" />
     <allow pkg="javax.crypto" />
-    <allow pkg="net.jqwik.api" />
 
     <!-- no one depends on the server -->
     <disallow pkg="kafka" />
diff --git a/checkstyle/import-control-metadata.xml 
b/checkstyle/import-control-metadata.xml
index 5c8e377f628..cdc73dcf65d 100644
--- a/checkstyle/import-control-metadata.xml
+++ b/checkstyle/import-control-metadata.xml
@@ -32,7 +32,6 @@
     <allow pkg="javax.net.ssl" />
     <allow pkg="javax.security" />
     <allow pkg="javax.crypto" />
-    <allow pkg="net.jqwik.api" />
 
     <!-- no one depends on the server -->
     <disallow pkg="kafka" />
diff --git a/checkstyle/import-control-server-common.xml 
b/checkstyle/import-control-server-common.xml
index 7d075dd71d7..05cf1395204 100644
--- a/checkstyle/import-control-server-common.xml
+++ b/checkstyle/import-control-server-common.xml
@@ -32,7 +32,6 @@
     <allow pkg="java.security" />
     <allow pkg="javax.net.ssl" />
     <allow pkg="javax.security" />
-    <allow pkg="net.jqwik.api" />
     <allow pkg="javax.crypto" />
 
     <!-- no one depends on the server -->
diff --git a/checkstyle/import-control-server.xml 
b/checkstyle/import-control-server.xml
index db528b8c717..30924f61324 100644
--- a/checkstyle/import-control-server.xml
+++ b/checkstyle/import-control-server.xml
@@ -31,7 +31,6 @@
   <allow pkg="java.security" />
   <allow pkg="javax.net.ssl" />
   <allow pkg="javax.security" />
-  <allow pkg="net.jqwik.api" />
   <allow pkg="org.apache.commons.validator.routines" />
 
   <!-- no one depends on the server -->
diff --git a/checkstyle/import-control-storage.xml 
b/checkstyle/import-control-storage.xml
index 7f6f56dd16a..9b3c66bc550 100644
--- a/checkstyle/import-control-storage.xml
+++ b/checkstyle/import-control-storage.xml
@@ -32,7 +32,6 @@
     <allow pkg="java.security" />
     <allow pkg="javax.net.ssl" />
     <allow pkg="javax.security" />
-    <allow pkg="net.jqwik.api" />
 
     <!-- no one depends on the server -->
     <disallow pkg="kafka" />
diff --git a/checkstyle/import-control.xml b/checkstyle/import-control.xml
index c76980b572e..9062dc0f199 100644
--- a/checkstyle/import-control.xml
+++ b/checkstyle/import-control.xml
@@ -34,7 +34,6 @@
   <allow pkg="javax.net.ssl" />
   <allow pkg="javax.security" />
   <allow pkg="org.ietf.jgss" />
-  <allow pkg="net.jqwik.api" />
 
   <!-- no one depends on the server -->
   <disallow pkg="kafka" />
@@ -475,7 +474,6 @@
     <allow pkg="org.apache.kafka.storage.log.metrics" />
     <allow pkg="org.apache.kafka.test"/>
     <allow pkg="com.fasterxml.jackson" />
-    <allow pkg="net.jqwik"/>
   </subpackage>
 
   <subpackage name="snapshot">
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 25cf854528f..4a4e8dca6df 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -83,7 +83,6 @@ versions += [
   jopt: "5.0.4",
   jose4j: "0.9.6",
   junit: "5.14.3",
-  jqwik: "1.9.2",
   kafka_0110: "0.11.0.3",
   kafka_10: "1.0.2",
   kafka_11: "1.1.1",
@@ -194,7 +193,6 @@ libs += [
   junitJupiterApi: "org.junit.jupiter:junit-jupiter-api:$versions.junit",
   junitPlatformSuiteEngine: 
"org.junit.platform:junit-platform-suite-engine:$versions.junitPlatform",
   junitPlatformLanucher: 
"org.junit.platform:junit-platform-launcher:$versions.junitPlatform",
-  jqwik: "net.jqwik:jqwik:$versions.jqwik",
   hamcrest: "org.hamcrest:hamcrest:$versions.hamcrest",
   kafkaStreams_0110: "org.apache.kafka:kafka-streams:$versions.kafka_0110",
   kafkaStreams_10: "org.apache.kafka:kafka-streams:$versions.kafka_10",
diff --git 
a/metadata/src/test/java/org/apache/kafka/metadata/PartitionRegistrationTest.java
 
b/metadata/src/test/java/org/apache/kafka/metadata/PartitionRegistrationTest.java
index 0fe7025c178..ad33b6b030d 100644
--- 
a/metadata/src/test/java/org/apache/kafka/metadata/PartitionRegistrationTest.java
+++ 
b/metadata/src/test/java/org/apache/kafka/metadata/PartitionRegistrationTest.java
@@ -26,12 +26,6 @@ import 
org.apache.kafka.image.writer.UnwritableMetadataException;
 import org.apache.kafka.server.common.ApiMessageAndVersion;
 import org.apache.kafka.server.common.MetadataVersion;
 
-import net.jqwik.api.Arbitraries;
-import net.jqwik.api.Arbitrary;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-import net.jqwik.api.Provide;
-
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -328,23 +322,9 @@ public class PartitionRegistrationTest {
         assertTrue(exceptions.isEmpty());
     }
 
-    @Property
-    public void testConsistentEqualsAndHashCode(
-        @ForAll("uniqueSamples") PartitionRegistration a,
-        @ForAll("uniqueSamples") PartitionRegistration b
-    ) {
-        if (a.equals(b)) {
-            assertEquals(a.hashCode(), b.hashCode(), "a=" + a + "\nb=" + b);
-        }
-
-        if (a.hashCode() != b.hashCode()) {
-            assertNotEquals(a, b, "a=" + a + "\nb=" + b);
-        }
-    }
-
-    @Provide
-    Arbitrary<PartitionRegistration> uniqueSamples() {
-        return Arbitraries.of(
+    @Test
+    public void testConsistentEqualsAndHashCode() {
+        List<PartitionRegistration> samples = List.of(
             new PartitionRegistration.Builder().setReplicas(new int[] {1, 2, 
3}).setIsr(new int[] {1, 2, 3}).
                 setDirectories(new 
Uuid[]{Uuid.fromString("HyTsxr8hT6Gq5heZMA2Bug"), 
Uuid.fromString("ePwTiSgFRvaKRBaUX3EcZQ"), 
Uuid.fromString("F3zwSDR1QWGKNNLMowVoYg")}).
                 
setLeader(1).setLeaderRecoveryState(LeaderRecoveryState.RECOVERED).setLeaderEpoch(100).setPartitionEpoch(200).setElr(new
 int[] {1, 2, 3}).build(),
@@ -374,6 +354,16 @@ public class PartitionRegistrationTest {
                 
setLeader(1).setLeaderRecoveryState(LeaderRecoveryState.RECOVERED).setLeaderEpoch(100).setPartitionEpoch(200).setElr(new
 int[] {2, 3}).setLastKnownElr(new int[] {1, 2}).build()
         );
 
+        for (PartitionRegistration a : samples) {
+            for (PartitionRegistration b : samples) {
+                if (a.equals(b)) {
+                    assertEquals(a.hashCode(), b.hashCode(), "a=" + a + "\nb=" 
+ b);
+                }
+                if (a.hashCode() != b.hashCode()) {
+                    assertNotEquals(a, b, "a=" + a + "\nb=" + b);
+                }
+            }
+        }
     }
 
     @Test
diff --git 
a/metadata/src/test/java/org/apache/kafka/metadata/authorizer/StandardAuthorizerPropertyTest.java
 
b/metadata/src/test/java/org/apache/kafka/metadata/authorizer/StandardAuthorizerPropertyTest.java
index 2e0eaa80ab6..eb032b69280 100644
--- 
a/metadata/src/test/java/org/apache/kafka/metadata/authorizer/StandardAuthorizerPropertyTest.java
+++ 
b/metadata/src/test/java/org/apache/kafka/metadata/authorizer/StandardAuthorizerPropertyTest.java
@@ -23,6 +23,8 @@ import org.apache.kafka.common.acl.AclBindingFilter;
 import org.apache.kafka.common.acl.AclOperation;
 import org.apache.kafka.common.acl.AclPermissionType;
 import org.apache.kafka.common.internals.Topic;
+import org.apache.kafka.common.metrics.Metrics;
+import org.apache.kafka.common.metrics.internals.PluginMetricsImpl;
 import org.apache.kafka.common.resource.PatternType;
 import org.apache.kafka.common.resource.ResourcePattern;
 import org.apache.kafka.common.resource.ResourcePatternFilter;
@@ -32,20 +34,11 @@ import org.apache.kafka.server.authorizer.Action;
 import org.apache.kafka.server.authorizer.AuthorizableRequestContext;
 import org.apache.kafka.server.authorizer.AuthorizationResult;
 
-import net.jqwik.api.Assume;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-import net.jqwik.api.constraints.AlphaChars;
-import net.jqwik.api.constraints.Chars;
-import net.jqwik.api.constraints.NumericChars;
-import net.jqwik.api.constraints.Size;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import org.junit.jupiter.api.Test;
+
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -53,120 +46,147 @@ import java.util.function.Predicate;
 import static org.apache.kafka.common.security.auth.KafkaPrincipal.USER_TYPE;
 import static 
org.apache.kafka.metadata.authorizer.StandardAuthorizerTest.PLAINTEXT;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 
 public class StandardAuthorizerPropertyTest {
 
-    @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, 
ElementType.TYPE_USE })
-    @Retention(RetentionPolicy.RUNTIME)
-    @AlphaChars @NumericChars @Chars({ '_', '-', '.' })
-    public @interface ValidTopicChars { }
-
-    @Property(tries = 5000)
-    public void matchingPrefixDenyOverridesAllAllowRules(
-        @ForAll Random random,
-        @ForAll @ValidTopicChars String topic,
-        @ForAll @Size(max = 10) Set<@ValidTopicChars String> randomSuffixes
-    ) throws Exception {
-        Assume.that(Topic.isValid(topic));
-        StandardAuthorizer authorizer = buildAuthorizer();
-
-        // Create one DENY rule which matches and zero or more ALLOW rules 
which may or
-        // may not match. Regardless of the ALLOW rules, the final result 
should be DENIED.
+    private static final String VALID_TOPIC_CHARS =
+        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
 
-        String topicPrefix = topic.substring(0, 
random.nextInt(topic.length()));
-        StandardAcl denyRule = buildTopicWriteAcl(topicPrefix, 
PatternType.PREFIXED, AclPermissionType.DENY);
-        authorizer.addAcl(Uuid.randomUuid(), denyRule);
-        addRandomPrefixAllowAcls(authorizer, topic, randomSuffixes);
-
-        assertAuthorizationResult(
-            authorizer,
-            AuthorizationResult.DENIED,
-            AclOperation.WRITE,
-            new ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL)
-        );
+    private interface TopicSuffixesRandom {
+        void accept(String topic, Set<String> suffixes, Random random) throws 
Exception;
     }
 
-    @Property(tries = 5000)
-    public void matchingLiteralDenyOverridesAllAllowRules(
-        @ForAll @ValidTopicChars String topic,
-        @ForAll @Size(max = 10) Set<@ValidTopicChars String> randomSuffixes
-    ) throws Exception {
-        Assume.that(Topic.isValid(topic));
-        StandardAuthorizer authorizer = buildAuthorizer();
-
-        // Create one DENY rule which matches and zero or more ALLOW rules 
which may or
-        // may not match. Regardless of the ALLOW rules, the final result 
should be DENIED.
-
-        StandardAcl denyRule = buildTopicWriteAcl(topic, PatternType.LITERAL, 
AclPermissionType.DENY);
-        authorizer.addAcl(Uuid.randomUuid(), denyRule);
-        addRandomPrefixAllowAcls(authorizer, topic, randomSuffixes);
-
-        assertAuthorizationResult(
-            authorizer,
-            AuthorizationResult.DENIED,
-            AclOperation.WRITE,
-            new ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL)
-        );
+    private interface TopicSuffixes {
+        void accept(String topic, Set<String> suffixes) throws Exception;
     }
 
-    @Property(tries = 5000)
-    public void matchingPrefixAllowWithNoMatchingDenyRules(
-        @ForAll Random random,
-        @ForAll @ValidTopicChars String topic,
-        @ForAll @Size(max = 10) Set<@ValidTopicChars String> randomSuffixes
-    ) throws Exception {
-        Assume.that(Topic.isValid(topic));
-        StandardAuthorizer authorizer = buildAuthorizer();
-
-        // Create one ALLOW rule which matches and zero or more DENY rules 
which do not
-        // match. The final result should be ALLOWED.
-
-        String topicPrefix = topic.substring(0, 
random.nextInt(topic.length()));
-        StandardAcl denyRule = buildTopicWriteAcl(topicPrefix, 
PatternType.PREFIXED, AclPermissionType.ALLOW);
-        authorizer.addAcl(Uuid.randomUuid(), denyRule);
-
-        addRandomNonMatchingPrefixDenyAcls(authorizer, topic, randomSuffixes);
-
-        assertAuthorizationResult(
-            authorizer,
-            AuthorizationResult.ALLOWED,
-            AclOperation.WRITE,
-            new ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL)
-        );
+    private static void forTopicSuffixesRandom(TopicSuffixesRandom 
topicSuffixesRandom) {
+        for (int run = 0; run < 5000; run++) {
+            long seed = System.nanoTime() + run;
+            Random random = new Random(seed);
+            String topic;
+            do {
+                topic = randomTopicString(random, 249);
+            } while (!Topic.isValid(topic));
+            Set<String> suffixes = randomSuffixes(random);
+            try {
+                topicSuffixesRandom.accept(topic, suffixes, random);
+            } catch (Throwable e) {
+                fail("Failed with seed=" + seed + ", topic=" + topic + ", 
suffixes=" + suffixes, e);
+            }
+        }
     }
 
-    @Property(tries = 5000)
-    public void matchingLiteralAllowWithNoMatchingDenyRules(
-        @ForAll @ValidTopicChars String topic,
-        @ForAll @Size(max = 10) Set<@ValidTopicChars String> randomSuffixes
-    ) throws Exception {
-        Assume.that(Topic.isValid(topic));
-        StandardAuthorizer authorizer = buildAuthorizer();
+    private static void forTopicSuffixes(TopicSuffixes topicSuffixes) {
+        forTopicSuffixesRandom((topic, suffixes, random) -> 
topicSuffixes.accept(topic, suffixes));
+    }
 
-        // Create one ALLOW rule which matches and zero or more DENY rules 
which do not
-        // match. The final result should be ALLOWED.
+    @Test
+    public void matchingPrefixDenyOverridesAllAllowRules() {
+        forTopicSuffixesRandom((topic, suffixes, random) -> {
+            StandardAuthorizer authorizer = buildAuthorizer();
+
+            // Create one DENY rule which matches and zero or more ALLOW rules 
which may or
+            // may not match. Regardless of the ALLOW rules, the final result 
should be DENIED.
+            String topicPrefix = topic.substring(0, 
random.nextInt(topic.length()));
+            StandardAcl denyRule = buildTopicWriteAcl(topicPrefix, 
PatternType.PREFIXED, AclPermissionType.DENY);
+            authorizer.addAcl(Uuid.randomUuid(), denyRule);
+            addRandomPrefixAllowAcls(authorizer, topic, suffixes);
+
+            assertAuthorizationResult(
+                authorizer,
+                AuthorizationResult.DENIED,
+                AclOperation.WRITE,
+                new ResourcePattern(ResourceType.TOPIC, topic, 
PatternType.LITERAL));
+        });
+    }
 
-        StandardAcl denyRule = buildTopicWriteAcl(topic, PatternType.LITERAL, 
AclPermissionType.ALLOW);
-        authorizer.addAcl(Uuid.randomUuid(), denyRule);
+    @Test
+    public void matchingLiteralDenyOverridesAllAllowRules() {
+        forTopicSuffixes((topic, suffixes) -> {
+            StandardAuthorizer authorizer = buildAuthorizer();
+
+            // Create one DENY rule which matches and zero or more ALLOW rules 
which may or
+            // may not match. Regardless of the ALLOW rules, the final result 
should be DENIED.
+            StandardAcl denyRule = buildTopicWriteAcl(topic, 
PatternType.LITERAL, AclPermissionType.DENY);
+            authorizer.addAcl(Uuid.randomUuid(), denyRule);
+            addRandomPrefixAllowAcls(authorizer, topic, suffixes);
+
+            assertAuthorizationResult(
+                authorizer,
+                AuthorizationResult.DENIED,
+                AclOperation.WRITE,
+                new ResourcePattern(ResourceType.TOPIC, topic, 
PatternType.LITERAL));
+        });
+    }
 
-        addRandomNonMatchingPrefixDenyAcls(authorizer, topic, randomSuffixes);
+    @Test
+    public void matchingPrefixAllowWithNoMatchingDenyRules() {
+        forTopicSuffixesRandom((topic, suffixes, random) -> {
+            StandardAuthorizer authorizer = buildAuthorizer();
+
+            // Create one ALLOW rule which matches and zero or more DENY rules 
which do not
+            // match. The final result should be ALLOWED.
+            String topicPrefix = topic.substring(0, 
random.nextInt(topic.length()));
+            StandardAcl allowRule = buildTopicWriteAcl(topicPrefix, 
PatternType.PREFIXED, AclPermissionType.ALLOW);
+            authorizer.addAcl(Uuid.randomUuid(), allowRule);
+            addRandomNonMatchingPrefixDenyAcls(authorizer, topic, suffixes);
+
+            assertAuthorizationResult(
+                authorizer,
+                AuthorizationResult.ALLOWED,
+                AclOperation.WRITE,
+                new ResourcePattern(ResourceType.TOPIC, topic, 
PatternType.LITERAL));
+        });
+    }
 
-        assertAuthorizationResult(
-            authorizer,
-            AuthorizationResult.ALLOWED,
-            AclOperation.WRITE,
-            new ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL)
-        );
+    @Test
+    public void matchingLiteralAllowWithNoMatchingDenyRules() {
+        forTopicSuffixes((topic, suffixes) -> {
+            StandardAuthorizer authorizer = buildAuthorizer();
+
+            // Create one ALLOW rule which matches and zero or more DENY rules 
which do not
+            // match. The final result should be ALLOWED.
+            StandardAcl allowRule = buildTopicWriteAcl(topic, 
PatternType.LITERAL, AclPermissionType.ALLOW);
+            authorizer.addAcl(Uuid.randomUuid(), allowRule);
+            addRandomNonMatchingPrefixDenyAcls(authorizer, topic, suffixes);
+
+            assertAuthorizationResult(
+                authorizer,
+                AuthorizationResult.ALLOWED,
+                AclOperation.WRITE,
+                new ResourcePattern(ResourceType.TOPIC, topic, 
PatternType.LITERAL));
+        });
     }
 
-    private StandardAuthorizer buildAuthorizer() {
+    private static StandardAuthorizer buildAuthorizer() {
         StandardAuthorizer authorizer = new StandardAuthorizer();
         authorizer.start(new AuthorizerTestServerInfo(List.of(PLAINTEXT)));
+        authorizer.withPluginMetrics(new PluginMetricsImpl(new Metrics(), 
Map.of()));
         authorizer.completeInitialLoad();
         return authorizer;
     }
 
-    private void assertAuthorizationResult(
+    private static String randomTopicString(Random random, int maxLength) {
+        int length = random.nextInt(maxLength) + 1;
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            
sb.append(VALID_TOPIC_CHARS.charAt(random.nextInt(VALID_TOPIC_CHARS.length())));
+        }
+        return sb.toString();
+    }
+
+    private static Set<String> randomSuffixes(Random random) {
+        int size = random.nextInt(11);
+        Set<String> suffixes = new HashSet<>();
+        for (int i = 0; i < size; i++) {
+            suffixes.add(randomTopicString(random, 10));
+        }
+        return suffixes;
+    }
+
+    private static void assertAuthorizationResult(
         StandardAuthorizer authorizer,
         AuthorizationResult expectedResult,
         AclOperation operation,
@@ -183,29 +203,18 @@ public class StandardAuthorizerPropertyTest {
 
         try {
             assertEquals(expectedResult, actualResult);
-        } catch (Throwable e) {
-            printCounterExample(authorizer, operation, pattern, actualResult);
+        } catch (AssertionError e) {
+            System.out.println("Assertion FAILED: Operation " + operation + " 
on " +
+                    pattern + " is " + actualResult + ". Current ACLS:");
+            Iterable<AclBinding> allAcls = authorizer.acls(new 
AclBindingFilter(
+                new ResourcePatternFilter(ResourceType.ANY, null, 
PatternType.ANY),
+                new AccessControlEntryFilter(null, null, AclOperation.ANY, 
AclPermissionType.ANY)
+            ));
+            allAcls.forEach(System.out::println);
             throw e;
         }
     }
 
-    private void printCounterExample(
-        StandardAuthorizer authorizer,
-        AclOperation operation,
-        ResourcePattern resourcePattern,
-        AuthorizationResult result
-    ) {
-        System.out.println("Assertion FAILED: Operation " + operation + " on " 
+
-            resourcePattern + " is " + result + ". Current ACLS:");
-
-        Iterable<AclBinding> allAcls = authorizer.acls(new AclBindingFilter(
-            new ResourcePatternFilter(ResourceType.ANY, null, PatternType.ANY),
-            new AccessControlEntryFilter(null, null, AclOperation.ANY, 
AclPermissionType.ANY)
-        ));
-
-        allAcls.forEach(System.out::println);
-    }
-
     private static AuthorizableRequestContext newRequestContext() throws 
Exception {
         return new MockAuthorizableRequestContext.Builder()
             .setPrincipal(new KafkaPrincipal(USER_TYPE, "user"))
@@ -228,7 +237,7 @@ public class StandardAuthorizerPropertyTest {
         );
     }
 
-    private boolean isPrefix(
+    private static boolean isPrefix(
         String value,
         String prefix
     ) {
@@ -240,7 +249,7 @@ public class StandardAuthorizerPropertyTest {
         }
     }
 
-    private void addRandomNonMatchingPrefixDenyAcls(
+    private static void addRandomNonMatchingPrefixDenyAcls(
         StandardAuthorizer authorizer,
         String topic,
         Set<String> randomSuffixes
@@ -254,7 +263,7 @@ public class StandardAuthorizerPropertyTest {
         );
     }
 
-    private void addRandomPrefixAllowAcls(
+    private static void addRandomPrefixAllowAcls(
         StandardAuthorizer authorizer,
         String topic,
         Set<String> randomSuffixes
@@ -267,8 +276,8 @@ public class StandardAuthorizerPropertyTest {
             pattern -> !pattern.isEmpty()
         );
     }
-    
-    private void addRandomPrefixRules(
+
+    private static void addRandomPrefixRules(
         StandardAuthorizer authorizer,
         String topic,
         Set<String> randomSuffixes,
@@ -293,7 +302,7 @@ public class StandardAuthorizerPropertyTest {
                 PatternType.PREFIXED,
                 permissionType
             ));
-        }        
+        }
     }
 
 }
diff --git 
a/raft/src/test/java/org/apache/kafka/raft/KafkaRaftClientFetchTest.java 
b/raft/src/test/java/org/apache/kafka/raft/KafkaRaftClientFetchTest.java
index cb228820869..60090e0c0e0 100644
--- a/raft/src/test/java/org/apache/kafka/raft/KafkaRaftClientFetchTest.java
+++ b/raft/src/test/java/org/apache/kafka/raft/KafkaRaftClientFetchTest.java
@@ -28,10 +28,6 @@ import org.apache.kafka.common.record.internal.SimpleRecord;
 import org.apache.kafka.common.requests.FetchResponse;
 import org.apache.kafka.server.common.KRaftVersion;
 
-import net.jqwik.api.AfterFailureMode;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -49,11 +45,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public final class KafkaRaftClientFetchTest {
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void testRandomRecords(
-        @ForAll(supplier = ArbitraryMemoryRecords.class) MemoryRecords 
memoryRecords
-    ) throws Exception {
-        testFetchResponseWithInvalidRecord(memoryRecords, Integer.MAX_VALUE);
+
+    @Test
+    void testRandomRecords() {
+        ArbitraryMemoryRecords.forRandomRecords(100, memoryRecords ->
+            testFetchResponseWithInvalidRecord(memoryRecords, 
Integer.MAX_VALUE)
+        );
     }
 
     @ParameterizedTest
diff --git a/raft/src/test/java/org/apache/kafka/raft/MockLogTest.java 
b/raft/src/test/java/org/apache/kafka/raft/MockLogTest.java
index a004e3a09af..68ffc33792f 100644
--- a/raft/src/test/java/org/apache/kafka/raft/MockLogTest.java
+++ b/raft/src/test/java/org/apache/kafka/raft/MockLogTest.java
@@ -36,10 +36,6 @@ import org.apache.kafka.server.common.OffsetAndEpoch;
 import org.apache.kafka.snapshot.RawSnapshotReader;
 import org.apache.kafka.snapshot.RawSnapshotWriter;
 
-import net.jqwik.api.AfterFailureMode;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -453,20 +449,20 @@ public class MockLogTest {
         assertEquals(previousEndOffset, log.endOffset().offset());
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void testRandomRecords(
-        @ForAll(supplier = ArbitraryMemoryRecords.class) MemoryRecords records
-    ) {
-        try (MockLog log = new MockLog(topicPartition, topicId, new 
LogContext())) {
-            long previousEndOffset = log.endOffset().offset();
+    @Test
+    void testRandomRecords() {
+        ArbitraryMemoryRecords.forRandomRecords(100, records -> {
+            try (MockLog log = new MockLog(topicPartition, topicId, new 
LogContext())) {
+                long previousEndOffset = log.endOffset().offset();
 
-            assertThrows(
-                CorruptRecordException.class,
-                () -> log.appendAsFollower(records, Integer.MAX_VALUE)
-            );
+                assertThrows(
+                    CorruptRecordException.class,
+                    () -> log.appendAsFollower(records, Integer.MAX_VALUE)
+                );
 
-            assertEquals(previousEndOffset, log.endOffset().offset());
-        }
+                assertEquals(previousEndOffset, log.endOffset().offset());
+            }
+        });
     }
 
     @Test
diff --git 
a/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java 
b/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java
index 7b79ec9e2de..3852921f7e1 100644
--- a/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java
+++ b/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java
@@ -40,12 +40,8 @@ import 
org.apache.kafka.server.common.serialization.RecordSerde;
 import org.apache.kafka.snapshot.RecordsSnapshotReader;
 import org.apache.kafka.snapshot.SnapshotReader;
 
-import net.jqwik.api.AfterFailureMode;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-import net.jqwik.api.Tag;
-import net.jqwik.api.constraints.IntRange;
-
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import java.net.InetSocketAddress;
@@ -69,6 +65,7 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -78,26 +75,26 @@ import static org.junit.jupiter.api.Assertions.fail;
  * The simulation testing framework provides a way to verify quorum behavior 
under
  * different conditions. It is similar to system testing in that the test 
involves
  * independently executing nodes, but there are several important differences:
- *
- * 1. Simulation behavior is deterministic provided an initial random seed. 
This
+ * <ol>
+ * <li>Simulation behavior is deterministic provided an initial random seed. 
This
  *    makes it easy to reproduce and debug test failures.
- * 2. The simulation uses an in-memory message router instead of a real 
network.
+ * <li>The simulation uses an in-memory message router instead of a real 
network.
  *    Not only is this much cheaper and faster, it provides an easy way to 
create
  *    flaky network conditions or even network partitions without losing the
  *    simulation determinism.
- * 3. Similarly, persistent state is stored in memory. We can nevertheless 
simulate
+ * <li>Similarly, persistent state is stored in memory. We can nevertheless 
simulate
  *    different kinds of failures, such as the loss of unflushed data after a 
hard
  *    node restart using {@link MockLog}.
  *
- * The framework uses a single event scheduler in order to provide 
deterministic
+ * <p>The framework uses a single event scheduler in order to provide 
deterministic
  * executions. Each test is setup as a specific scenario with a variable 
number of
  * voters and observers. Much like system tests, there is typically a warmup
  * period, followed by some cluster event (such as a node failure), and then 
some
  * logic to validate behavior after recovery.
  *
- * If any of the tests fail, the output will indicate the arguments that 
failed.
+ * <p>If any of the tests fail, the output will indicate the arguments that 
failed.
  * The easiest way to reproduce the failure for debugging is to create a 
separate
- * `@Test` case which invokes the `@Property` method with those arguments 
directly.
+ * test case which invokes the test method with those arguments directly.
  * This ensures that logging output will only include output from a single
  * simulation execution.
  */
@@ -112,366 +109,317 @@ public class RaftEventSimulationTest {
     private static final int FETCH_MAX_WAIT_MS = 100;
     private static final int LINGER_MS = 0;
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canElectInitialLeader(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 1, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 5) int numObservers
-    ) {
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 1);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(10));
+    private interface SeedObservers {
+        void accept(long seed, int observer);
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canElectNewLeaderAfterOldLeaderFailure(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 3, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 5) int numObservers,
-        @ForAll boolean isGracefulShutdown
-    ) {
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 1);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
-
-        // Shutdown the leader and write some more data. We can verify the new 
leader has been elected
-        // by verifying that the high watermark can still advance.
-        int leaderId = cluster.latestLeader().orElseThrow(() ->
-            new AssertionError("Failed to find current leader")
-        );
-
-        if (isGracefulShutdown) {
-            cluster.shutdown(leaderId);
-        } else {
-            cluster.kill(leaderId);
-        }
-
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(20));
-        long highWatermark = cluster.maxHighWatermarkReached();
+    private interface SeedVotersObservers {
+        void accept(long seed, int voter, int observer);
+    }
 
-        // Restart the node and verify it catches up
-        cluster.start(leaderId);
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(highWatermark 
+ 10));
+    private interface SeedVotersObserversIsGracefulShutdown {
+        void accept(long seed, int voter, int observer, boolean 
isGracefulShutdown);
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canRecoverAfterAllNodesKilled(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 1, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 5) int numObservers
-    ) {
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 1);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
-        long highWatermark = cluster.maxHighWatermarkReached();
-
-        // We kill all of the nodes. Then we bring back a majority and verify 
that
-        // they are able to elect a leader and continue making progress
-        cluster.killAll();
-
-        Iterator<Integer> nodeIdsIterator = cluster.nodeIds().iterator();
-        for (int i = 0; i < cluster.majoritySize(); i++) {
-            Integer nodeId = nodeIdsIterator.next();
-            cluster.start(nodeId);
-        }
-
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(highWatermark 
+ 10));
+    private static void withObservers(int minObservers, int maxObservers, 
SeedObservers so) {
+        withVotersObserversIsGracefulShutdown(0, 0, minObservers, 
maxObservers, (seed, numVoters, numObservers, isGracefulShutdown) ->
+            so.accept(seed, numObservers)
+        );
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canElectNewLeaderAfterOldLeaderPartitionedAway(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 3, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 5) int numObservers
-    ) {
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 2);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
-
-        // The leader gets partitioned off. We can verify the new leader has 
been elected
-        // by writing some data and ensuring that it gets replicated
-        int leaderId = cluster.latestLeader().orElseThrow(() ->
-            new AssertionError("Failed to find current leader")
+    private static void withVotersObservers(int minVoters, int maxVoters, int 
minObservers, int maxObservers, SeedVotersObservers svo) {
+        withVotersObserversIsGracefulShutdown(minVoters, maxVoters, 
minObservers, maxObservers, (seed, numVoters, numObservers, isGracefulShutdown) 
->
+            svo.accept(seed, numVoters, numObservers)
         );
-        router.filter(leaderId, new DropAllTraffic());
+    }
 
-        Set<Integer> nonPartitionedNodes = new HashSet<>(cluster.nodeIds());
-        nonPartitionedNodes.remove(leaderId);
+    private static void withVotersObserversIsGracefulShutdown(int minVoters, 
int maxVoters, int minObservers, int maxObservers, 
SeedVotersObserversIsGracefulShutdown svos) {
+        for (int run = 0; run < 100; run++) {
+            long seed = System.nanoTime() + run;
+            Random random = new Random(seed);
+            int numVoters = random.nextInt(minVoters, maxVoters + 1);
+            int numObservers = random.nextInt(minObservers, maxObservers + 1);
+            boolean isGracefulShutdown = random.nextBoolean();
+            assertDoesNotThrow(
+                    () -> svos.accept(seed, numVoters, numObservers, 
isGracefulShutdown),
+                    () -> "Failed with seed=" + seed +
+                        ", numVoters=" + numVoters + ", numObservers=" + 
numObservers +
+                        ", isGracefulShutdown=" + isGracefulShutdown);
+        }
+    }
 
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
nonPartitionedNodes));
+    @Test
+    void canElectInitialLeader() {
+        withVotersObservers(1, 5, 0, 5, (seed, numVoters, numObservers) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 1);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(10));
+        });
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canMakeProgressIfMajorityIsReachable(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 0, max = 3) int numObservers
-    ) {
-        int numVoters = 5;
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 2);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
-
-        // Partition the nodes into two sets. Nodes are reachable within each 
set,
-        // but the two sets cannot communicate with each other. We should be 
able
-        // to make progress even if an election is needed in the larger set.
-        router.filter(
-            0,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(2, 3, 
4)))
-        );
-        router.filter(
-            1,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(2, 3, 
4)))
-        );
-        router.filter(2, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
-        router.filter(3, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
-        router.filter(4, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
-
-        long partitionLogEndOffset = cluster.maxLogEndOffset();
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(2 * 
partitionLogEndOffset));
-
-        long minorityHighWatermark = cluster.maxHighWatermarkReached(Set.of(0, 
1));
-        long majorityHighWatermark = cluster.maxHighWatermarkReached(Set.of(2, 
3, 4));
-
-        assertTrue(
-            majorityHighWatermark > minorityHighWatermark,
-            String.format(
-                "majorityHighWatermark = %s, minorityHighWatermark = %s",
-                majorityHighWatermark,
-                minorityHighWatermark
-            )
-        );
+    @Test
+    void canElectNewLeaderAfterOldLeaderFailure() {
+        withVotersObserversIsGracefulShutdown(3, 5, 0, 5, (seed, numVoters, 
numObservers, isGracefulShutdown) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 1);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+
+            int leaderId = cluster.latestLeader().orElseThrow(() ->
+                new AssertionError("Failed to find current leader")
+            );
+
+            if (isGracefulShutdown) {
+                cluster.shutdown(leaderId);
+            } else {
+                cluster.kill(leaderId);
+            }
 
-        // Now restore the partition and verify everyone catches up
-        router.filter(0, new PermitAllTraffic());
-        router.filter(1, new PermitAllTraffic());
-        router.filter(2, new PermitAllTraffic());
-        router.filter(3, new PermitAllTraffic());
-        router.filter(4, new PermitAllTraffic());
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(20));
+            long highWatermark = cluster.maxHighWatermarkReached();
 
-        long restoredLogEndOffset = cluster.maxLogEndOffset();
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(2 * 
restoredLogEndOffset));
+            cluster.start(leaderId);
+            scheduler.runUntil(() -> 
cluster.allReachedHighWatermark(highWatermark + 10));
+        });
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void leadershipAssignedOnlyOnceWithNetworkPartitionIfThereExistsMajority(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 0, max = 3) int numObservers
-    ) {
-        int numVoters = 5;
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-        scheduler.addInvariant(new StableLeadership(cluster));
-
-        // Create network partition which would result in ping-pong of 
leadership between nodes 2 and 3 without PreVote
-        // Scenario explained in detail in KIP-996
-        // 0   1
-        // |   |
-        // 2 - 3
-        //  \ /
-        //   4
-        router.filter(
-            0,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(1, 3, 
4)))
-        );
-        router.filter(
-            1,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 2, 
4)))
-        );
-        router.filter(2, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(1))));
-        router.filter(3, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0))));
-        router.filter(4, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
-
-        // Start cluster
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 1);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 1);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-
-        // Check that leadership remains stable after majority processes some 
data
-        int leaderId = cluster.latestLeader().getAsInt();
-        // Determine the voters in the majority based on the leader
-        Set<Integer> majority = new HashSet<>(Set.of(0, 1, 2, 3, 4));
-        switch (leaderId) {
-            case 2 -> majority.remove(1);
-            case 3 -> majority.remove(0);
-            case 4 -> {
-                majority.remove(0);
-                majority.remove(1);
+    @Test
+    void canRecoverAfterAllNodesKilled() {
+        withVotersObservers(1, 5, 0, 5, (seed, numVoters, numObservers) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 1);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+            long highWatermark = cluster.maxHighWatermarkReached();
+
+            cluster.killAll();
+
+            Iterator<Integer> nodeIdsIterator = cluster.nodeIds().iterator();
+            for (int i = 0; i < cluster.majoritySize(); i++) {
+                Integer nodeId = nodeIdsIterator.next();
+                cluster.start(nodeId);
             }
-            default -> throw new IllegalStateException("Unexpected leader: " + 
leaderId);
-        }
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
majority));
+
+            scheduler.runUntil(() -> 
cluster.allReachedHighWatermark(highWatermark + 10));
+        });
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void leadershipWillNotChangeDuringNetworkPartitionIfMajorityStillReachable(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 0, max = 3) int numObservers
-    ) {
-        int numVoters = 5;
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-        scheduler.addInvariant(new StableLeadership(cluster));
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 1);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 1);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(5));
-
-        int leaderId = cluster.latestLeader().orElseThrow(() ->
-            new AssertionError("Failed to find current leader during setup")
-        );
+    @Test
+    void canElectNewLeaderAfterOldLeaderPartitionedAway() {
+        withVotersObservers(3, 5, 0, 5, (seed, numVoters, numObservers) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 2);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+
+            int leaderId = cluster.latestLeader().orElseThrow(() ->
+                new AssertionError("Failed to find current leader")
+            );
+            router.filter(leaderId, new DropAllTraffic());
 
-        // Create network partition which would result in ping-pong of 
leadership between nodes C and D without PreVote
-        // Scenario explained in detail in KIP-996
-        // A   B
-        // |   |
-        // C - D  (have leader start in position C)
-        //  \ /
-        //   E
-        int nodeA = (leaderId + 1) % numVoters;
-        int nodeB = (leaderId + 2) % numVoters;
-        int nodeD = (leaderId + 3) % numVoters;
-        int nodeE = (leaderId + 4) % numVoters;
-        router.filter(
-            nodeA,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeB, 
nodeD, nodeE)))
-        );
-        router.filter(
-            nodeB,
-            new DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA, 
leaderId, nodeE)))
-        );
-        router.filter(leaderId, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeB))));
-        router.filter(nodeD, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA))));
-        router.filter(nodeE, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA, nodeB))));
+            Set<Integer> nonPartitionedNodes = new 
HashSet<>(cluster.nodeIds());
+            nonPartitionedNodes.remove(leaderId);
 
-        // Check that leadership remains stable
-        scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
Set.of(nodeA, leaderId, nodeD, nodeE)));
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
nonPartitionedNodes));
+        });
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canMakeProgressAfterBackToBackLeaderFailures(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 3, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 5) int numObservers
-    ) {
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        MessageRouter router = new MessageRouter(cluster);
-        EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
-
-        // Seed the cluster with some data
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 5);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(cluster::hasConsistentLeader);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
-
-        int leaderId = cluster.latestLeader().getAsInt();
-        router.filter(leaderId, new DropAllTraffic());
-        scheduler.runUntil(() -> cluster.latestLeader().isPresent() && 
cluster.latestLeader().getAsInt() != leaderId);
-
-        // As soon as we have a new leader, restore traffic to the old leader 
and partition the new leader
-        int newLeaderId = cluster.latestLeader().getAsInt();
-        router.filter(leaderId, new PermitAllTraffic());
-        router.filter(newLeaderId, new DropAllTraffic());
-
-        // Verify now that we can make progress
-        long targetHighWatermark = cluster.maxHighWatermarkReached() + 10;
-        scheduler.runUntil(() -> 
cluster.anyReachedHighWatermark(targetHighWatermark));
+    @Test
+    void canMakeProgressIfMajorityIsReachable() {
+        withObservers(0, 3, (seed, numObservers) -> {
+            int numVoters = 5;
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 2);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+
+            router.filter(0, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(2, 3, 4))));
+            router.filter(1, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(2, 3, 4))));
+            router.filter(2, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
+            router.filter(3, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
+            router.filter(4, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
+
+            long partitionLogEndOffset = cluster.maxLogEndOffset();
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(2 * 
partitionLogEndOffset));
+
+            long minorityHighWatermark = 
cluster.maxHighWatermarkReached(Set.of(0, 1));
+            long majorityHighWatermark = 
cluster.maxHighWatermarkReached(Set.of(2, 3, 4));
+
+            assertTrue(
+                majorityHighWatermark > minorityHighWatermark,
+                String.format(
+                    "majorityHighWatermark = %s, minorityHighWatermark = %s",
+                    majorityHighWatermark,
+                    minorityHighWatermark
+                )
+            );
+
+            router.filter(0, new PermitAllTraffic());
+            router.filter(1, new PermitAllTraffic());
+            router.filter(2, new PermitAllTraffic());
+            router.filter(3, new PermitAllTraffic());
+            router.filter(4, new PermitAllTraffic());
+
+            long restoredLogEndOffset = cluster.maxLogEndOffset();
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(2 * 
restoredLogEndOffset));
+        });
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    void canRecoverFromSingleNodeCommittedDataLoss(
-        @ForAll int seed,
-        @ForAll @IntRange(min = 3, max = 5) int numVoters,
-        @ForAll @IntRange(min = 0, max = 2) int numObservers
-    ) {
-        // We run this test without the `MonotonicEpoch` and 
`MajorityReachedHighWatermark`
-        // invariants since the loss of committed data on one node can violate 
them.
-
-        Random random = new Random(seed);
-        Cluster cluster = new Cluster(numVoters, numObservers, random);
-        EventScheduler scheduler = new EventScheduler(cluster.random, 
cluster.time);
-        scheduler.addInvariant(new MonotonicHighWatermark(cluster));
-        scheduler.addInvariant(new SingleLeader(cluster));
-        scheduler.addValidation(new ConsistentCommittedData(cluster));
+    @Test
+    void leadershipAssignedOnlyOnceWithNetworkPartitionIfThereExistsMajority() 
{
+        withObservers(0, 3, (seed, numObservers) -> {
+            int numVoters = 5;
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+            scheduler.addInvariant(new StableLeadership(cluster));
+
+            router.filter(0, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(1, 3, 4))));
+            router.filter(1, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 2, 4))));
+            router.filter(2, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(1))));
+            router.filter(3, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0))));
+            router.filter(4, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(0, 1))));
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 1);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 1);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+
+            int leaderId = cluster.latestLeader().getAsInt();
+            Set<Integer> majority = new HashSet<>(Set.of(0, 1, 2, 3, 4));
+            switch (leaderId) {
+                case 2 -> majority.remove(1);
+                case 3 -> majority.remove(0);
+                case 4 -> {
+                    majority.remove(0);
+                    majority.remove(1);
+                }
+                default -> throw new IllegalStateException("Unexpected leader: 
" + leaderId);
+            }
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
majority));
+        });
+    }
 
-        MessageRouter router = new MessageRouter(cluster);
+    @Test
+    void 
leadershipWillNotChangeDuringNetworkPartitionIfMajorityStillReachable() {
+        withObservers(0, 3, (seed, numObservers) -> {
+            int numVoters = 5;
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+            scheduler.addInvariant(new StableLeadership(cluster));
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 1);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 1);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(5));
+
+            int leaderId = cluster.latestLeader().orElseThrow(() ->
+                new AssertionError("Failed to find current leader during 
setup")
+            );
 
-        cluster.startAll();
-        schedulePolling(scheduler, cluster, 3, 5);
-        scheduler.schedule(router::deliverAll, 0, 2, 5);
-        scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
-        scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+            int nodeA = (leaderId + 1) % numVoters;
+            int nodeB = (leaderId + 2) % numVoters;
+            int nodeD = (leaderId + 3) % numVoters;
+            int nodeE = (leaderId + 4) % numVoters;
+            router.filter(nodeA, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeB, nodeD, nodeE))));
+            router.filter(nodeB, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA, leaderId, 
nodeE))));
+            router.filter(leaderId, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeB))));
+            router.filter(nodeD, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA))));
+            router.filter(nodeE, new 
DropOutboundRequestsTo(cluster.endpointsFromIds(Set.of(nodeA, nodeB))));
+
+            scheduler.runUntil(() -> cluster.allReachedHighWatermark(20, 
Set.of(nodeA, leaderId, nodeD, nodeE)));
+        });
+    }
 
-        RaftNode node = cluster.randomRunning().orElseThrow(() ->
-            new AssertionError("Failed to find running node")
-        );
+    @Test
+    void canMakeProgressAfterBackToBackLeaderFailures() {
+        withVotersObservers(3, 5, 0, 5, (seed, numVoters, numObservers) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            MessageRouter router = new MessageRouter(cluster);
+            EventScheduler scheduler = schedulerWithDefaultInvariants(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 5);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(cluster::hasConsistentLeader);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+
+            int leaderId = cluster.latestLeader().getAsInt();
+            router.filter(leaderId, new DropAllTraffic());
+            scheduler.runUntil(() -> cluster.latestLeader().isPresent() && 
cluster.latestLeader().getAsInt() != leaderId);
+
+            int newLeaderId = cluster.latestLeader().getAsInt();
+            router.filter(leaderId, new PermitAllTraffic());
+            router.filter(newLeaderId, new DropAllTraffic());
+
+            long targetHighWatermark = cluster.maxHighWatermarkReached() + 10;
+            scheduler.runUntil(() -> 
cluster.anyReachedHighWatermark(targetHighWatermark));
+        });
+    }
 
-        // Kill a random node and drop all of its persistent state. The Raft
-        // protocol guarantees should still ensure we lose no committed data
-        // as long as a new leader is elected before the failed node is 
restarted.
-        cluster.killAndDeletePersistentState(node.nodeId);
-        scheduler.runUntil(() -> !cluster.hasLeader(node.nodeId) && 
cluster.hasConsistentLeader());
+    @Test
+    void canRecoverFromSingleNodeCommittedDataLoss() {
+        withVotersObservers(3, 5, 0, 2, (seed, numVoters, numObservers) -> {
+            Cluster cluster = new Cluster(numVoters, numObservers, seed);
+            EventScheduler scheduler = new EventScheduler(cluster.random, 
cluster.time);
+            scheduler.addInvariant(new MonotonicHighWatermark(cluster));
+            scheduler.addInvariant(new SingleLeader(cluster));
+            scheduler.addValidation(new ConsistentCommittedData(cluster));
+
+            MessageRouter router = new MessageRouter(cluster);
+
+            cluster.startAll();
+            schedulePolling(scheduler, cluster, 3, 5);
+            scheduler.schedule(router::deliverAll, 0, 2, 5);
+            scheduler.schedule(new SequentialAppendAction(cluster), 0, 2, 3);
+            scheduler.runUntil(() -> cluster.anyReachedHighWatermark(10));
+
+            RaftNode node = cluster.randomRunning().orElseThrow(() ->
+                new AssertionError("Failed to find running node")
+            );
+
+            cluster.killAndDeletePersistentState(node.nodeId);
+            scheduler.runUntil(() -> !cluster.hasLeader(node.nodeId) && 
cluster.hasConsistentLeader());
 
-        // Now restart the failed node and ensure that it recovers.
-        long highWatermarkBeforeRestart = cluster.maxHighWatermarkReached();
-        cluster.start(node.nodeId);
-        scheduler.runUntil(() -> 
cluster.allReachedHighWatermark(highWatermarkBeforeRestart + 10));
+            long highWatermarkBeforeRestart = 
cluster.maxHighWatermarkReached();
+            cluster.start(node.nodeId);
+            scheduler.runUntil(() -> 
cluster.allReachedHighWatermark(highWatermarkBeforeRestart + 10));
+        });
     }
 
     private EventScheduler schedulerWithDefaultInvariants(Cluster cluster) {
@@ -643,8 +591,8 @@ public class RaftEventSimulationTest {
         final Map<Integer, PersistentState> nodes = new HashMap<>();
         final Map<Integer, RaftNode> running = new HashMap<>();
 
-        private Cluster(int numVoters, int numObservers, Random random) {
-            this.random = random;
+        private Cluster(int numVoters, int numObservers, long seed) {
+            this.random = new Random(seed);
 
             for (int nodeId = 0; nodeId < numVoters; nodeId++) {
                 voters.put(nodeId, nodeFromId(nodeId));
@@ -911,8 +859,6 @@ public class RaftEventSimulationTest {
                 messageQueue,
                 persistentState.store,
                 logContext,
-                time,
-                random,
                 serde
             );
             node.initialize(voterAddressMap, metrics);
@@ -940,8 +886,6 @@ public class RaftEventSimulationTest {
             MockMessageQueue messageQueue,
             MockQuorumStateStore store,
             LogContext logContext,
-            Time time,
-            Random random,
             RecordSerde<Integer> intSerde
         ) {
             this.logContext = logContext;
diff --git 
a/raft/src/test/java/org/apache/kafka/raft/internals/KafkaRaftLogTest.java 
b/raft/src/test/java/org/apache/kafka/raft/internals/KafkaRaftLogTest.java
index ba3f39cdeb0..e905e77a357 100644
--- a/raft/src/test/java/org/apache/kafka/raft/internals/KafkaRaftLogTest.java
+++ b/raft/src/test/java/org/apache/kafka/raft/internals/KafkaRaftLogTest.java
@@ -55,10 +55,6 @@ import 
org.apache.kafka.storage.internals.log.LogStartOffsetIncrementReason;
 import org.apache.kafka.storage.internals.log.UnifiedLog;
 import org.apache.kafka.test.TestUtils;
 
-import net.jqwik.api.AfterFailureMode;
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -80,6 +76,7 @@ import java.util.Optional;
 import java.util.Properties;
 
 import static org.apache.kafka.test.TestUtils.assertOptional;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -179,22 +176,24 @@ public class KafkaRaftLogTest {
         assertEquals(previousEndOffset, log.endOffset().offset());
     }
 
-    @Property(tries = 100, afterFailure = AfterFailureMode.SAMPLE_ONLY)
-    public void testRandomRecords(@ForAll(supplier = 
ArbitraryMemoryRecords.class) MemoryRecords records) throws IOException {
-        File tempDir = TestUtils.tempDirectory();
-        try {
-            KafkaRaftLog log = buildMetadataLog(tempDir, mockTime);
-            long previousEndOffset = log.endOffset().offset();
-
-            assertThrows(
-                    CorruptRecordException.class,
-                    () -> log.appendAsFollower(records, Integer.MAX_VALUE)
-            );
-
-            assertEquals(previousEndOffset, log.endOffset().offset());
-        } finally {
-            Utils.delete(tempDir);
-        }
+    @Test
+    public void testRandomRecords() {
+        ArbitraryMemoryRecords.forRandomRecords(100, records -> {
+            File tempDir = TestUtils.tempDirectory();
+            try {
+                KafkaRaftLog log = buildMetadataLog(tempDir, mockTime);
+                long previousEndOffset = log.endOffset().offset();
+
+                assertThrows(
+                        CorruptRecordException.class,
+                        () -> log.appendAsFollower(records, Integer.MAX_VALUE)
+                );
+
+                assertEquals(previousEndOffset, log.endOffset().offset());
+            } finally {
+                assertDoesNotThrow(() -> Utils.delete(tempDir));
+            }
+        });
     }
 
     @Test
diff --git 
a/raft/src/test/java/org/apache/kafka/raft/internals/RecordsIteratorTest.java 
b/raft/src/test/java/org/apache/kafka/raft/internals/RecordsIteratorTest.java
index c64a5fb953f..d4c18943238 100644
--- 
a/raft/src/test/java/org/apache/kafka/raft/internals/RecordsIteratorTest.java
+++ 
b/raft/src/test/java/org/apache/kafka/raft/internals/RecordsIteratorTest.java
@@ -50,9 +50,6 @@ import org.apache.kafka.snapshot.MockRawSnapshotWriter;
 import org.apache.kafka.snapshot.RecordsSnapshotWriter;
 import org.apache.kafka.test.TestUtils;
 
-import net.jqwik.api.ForAll;
-import net.jqwik.api.Property;
-
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -98,67 +95,71 @@ public final class RecordsIteratorTest {
         testIterator(List.of(), records, true);
     }
 
-    @Property(tries = 50)
-    public void testMemoryRecords(
-        @ForAll CompressionType compressionType,
-        @ForAll long seed
-    ) {
-        List<TestBatch<String>> batches = createBatches(seed);
-
-        MemoryRecords memRecords = buildRecords(compressionType, batches);
-        testIterator(batches, memRecords, true);
+    private interface CompressionTypeSeed {
+        void accept(CompressionType compressionType, long seed) throws 
IOException;
     }
 
-    @Property(tries = 50)
-    public void testFileRecords(
-        @ForAll CompressionType compressionType,
-        @ForAll long seed
-    ) throws IOException {
-        List<TestBatch<String>> batches = createBatches(seed);
+    private void forCompressionTypeSeed(CompressionTypeSeed 
compressionTypeSeed) {
+        for (CompressionType ct : CompressionType.values()) {
+            for (int i = 0; i < 50; i++) {
+                long seed = new Random(System.nanoTime() + i).nextLong();
+                assertDoesNotThrow(
+                        () -> compressionTypeSeed.accept(ct, seed),
+                        () -> "Failed with compressionType=" + ct + ", seed=" 
+ seed);
+            }
+        }
+    }
 
-        MemoryRecords memRecords = buildRecords(compressionType, batches);
-        FileRecords fileRecords = FileRecords.open(TestUtils.tempFile());
-        fileRecords.append(memRecords);
+    @Test
+    public void testMemoryRecords() {
+        forCompressionTypeSeed((compressionType, seed) -> {
+            List<TestBatch<String>> batches = createBatches(seed);
+            MemoryRecords memRecords = buildRecords(compressionType, batches);
+            testIterator(batches, memRecords, true);
+        });
+    }
 
-        testIterator(batches, fileRecords, true);
-        fileRecords.close();
+    @Test
+    public void testFileRecords() {
+        forCompressionTypeSeed((compressionType, seed) -> {
+            List<TestBatch<String>> batches = createBatches(seed);
+            MemoryRecords memRecords = buildRecords(compressionType, batches);
+            FileRecords fileRecords = FileRecords.open(TestUtils.tempFile());
+            fileRecords.append(memRecords);
+            testIterator(batches, fileRecords, true);
+            fileRecords.close();
+        });
     }
 
-    @Property(tries = 50)
-    public void testCrcValidation(
-        @ForAll CompressionType compressionType,
-        @ForAll long seed
-    ) throws IOException {
-        List<TestBatch<String>> batches = createBatches(seed);
-        MemoryRecords memRecords = buildRecords(compressionType, batches);
-        // Read the Batch CRC for the first batch from the buffer
-        ByteBuffer readBuf = memRecords.buffer();
-        readBuf.position(DefaultRecordBatch.CRC_OFFSET);
-        int actualCrc = readBuf.getInt();
-        // Corrupt the CRC on the first batch
-        memRecords.buffer().putInt(DefaultRecordBatch.CRC_OFFSET, actualCrc + 
1);
-
-        assertThrows(CorruptRecordException.class, () -> testIterator(batches, 
memRecords, true));
-
-        FileRecords fileRecords = FileRecords.open(TestUtils.tempFile());
-        fileRecords.append(memRecords);
-        assertThrows(CorruptRecordException.class, () -> testIterator(batches, 
fileRecords, true));
-
-        // Verify check does not trigger when doCrcValidation is false
-        assertDoesNotThrow(() -> testIterator(batches, memRecords, false));
-        assertDoesNotThrow(() -> testIterator(batches, fileRecords, false));
-
-        // Fix the corruption
-        memRecords.buffer().putInt(DefaultRecordBatch.CRC_OFFSET, actualCrc);
-
-        // Verify check does not trigger when the corruption is fixed
-        assertDoesNotThrow(() -> testIterator(batches, memRecords, true));
-        FileRecords moreFileRecords = FileRecords.open(TestUtils.tempFile());
-        moreFileRecords.append(memRecords);
-        assertDoesNotThrow(() -> testIterator(batches, moreFileRecords, true));
-
-        fileRecords.close();
-        moreFileRecords.close();
+    @Test
+    public void testCrcValidation() {
+        forCompressionTypeSeed((compressionType, seed) -> {
+            List<TestBatch<String>> batches = createBatches(seed);
+            MemoryRecords memRecords = buildRecords(compressionType, batches);
+            ByteBuffer readBuf = memRecords.buffer();
+            readBuf.position(DefaultRecordBatch.CRC_OFFSET);
+            int actualCrc = readBuf.getInt();
+            memRecords.buffer().putInt(DefaultRecordBatch.CRC_OFFSET, 
actualCrc + 1);
+
+            assertThrows(CorruptRecordException.class, () -> 
testIterator(batches, memRecords, true));
+
+            FileRecords fileRecords = FileRecords.open(TestUtils.tempFile());
+            fileRecords.append(memRecords);
+            assertThrows(CorruptRecordException.class, () -> 
testIterator(batches, fileRecords, true));
+
+            assertDoesNotThrow(() -> testIterator(batches, memRecords, false));
+            assertDoesNotThrow(() -> testIterator(batches, fileRecords, 
false));
+
+            memRecords.buffer().putInt(DefaultRecordBatch.CRC_OFFSET, 
actualCrc);
+
+            assertDoesNotThrow(() -> testIterator(batches, memRecords, true));
+            FileRecords moreFileRecords = 
FileRecords.open(TestUtils.tempFile());
+            moreFileRecords.append(memRecords);
+            assertDoesNotThrow(() -> testIterator(batches, moreFileRecords, 
true));
+
+            fileRecords.close();
+            moreFileRecords.close();
+        });
     }
 
     @Test
diff --git 
a/raft/src/testFixtures/java/org/apache/kafka/common/record/internal/ArbitraryMemoryRecords.java
 
b/raft/src/testFixtures/java/org/apache/kafka/common/record/internal/ArbitraryMemoryRecords.java
index 573bbbe1d40..1316ae3f1fd 100644
--- 
a/raft/src/testFixtures/java/org/apache/kafka/common/record/internal/ArbitraryMemoryRecords.java
+++ 
b/raft/src/testFixtures/java/org/apache/kafka/common/record/internal/ArbitraryMemoryRecords.java
@@ -16,24 +16,29 @@
  */
 package org.apache.kafka.common.record.internal;
 
-import net.jqwik.api.Arbitraries;
-import net.jqwik.api.Arbitrary;
-import net.jqwik.api.ArbitrarySupplier;
+import org.junit.jupiter.api.function.ThrowingConsumer;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Random;
 
-public final class ArbitraryMemoryRecords implements 
ArbitrarySupplier<MemoryRecords> {
-    @Override
-    public Arbitrary<MemoryRecords> get() {
-        return 
Arbitraries.randomValue(ArbitraryMemoryRecords::buildRandomRecords);
-    }
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+public final class ArbitraryMemoryRecords {
 
-    private static MemoryRecords buildRandomRecords(Random random) {
-        int size = random.nextInt(128) + 1;
-        byte[] bytes = new byte[size];
-        random.nextBytes(bytes);
+    private ArbitraryMemoryRecords() {}
 
-        return MemoryRecords.readableRecords(ByteBuffer.wrap(bytes));
+    public static void forRandomRecords(int tries, 
ThrowingConsumer<MemoryRecords> test) {
+        for (int i = 0; i < tries; i++) {
+            long seed = System.nanoTime() + i;
+            Random random = new Random(seed);
+            int size = random.nextInt(128) + 1;
+            byte[] bytes = new byte[size];
+            random.nextBytes(bytes);
+            MemoryRecords records = 
MemoryRecords.readableRecords(ByteBuffer.wrap(bytes));
+            assertDoesNotThrow(
+                    () -> test.accept(records),
+                    () -> "Failed with seed=" + seed + ", size=" + size + ", 
bytes=" + Arrays.toString(bytes));
+        }
     }
 }

Reply via email to