From 5e896ae1a9ccb5932629b4a3c76d43c988bc5846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Lamar=C3=A3o?= Date: Wed, 24 Jun 2026 11:43:13 -0300 Subject: [PATCH 1/5] Define Automatic-Module-Name via maven-jar-plugin --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 90b2553..5b3d35b 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,19 @@ 3.13.0 + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + dev.hegel + + + + + From 6b3b0a829bb5b2fd858fabc80f0d81e4e2d806e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Lamar=C3=A3o?= Date: Wed, 24 Jun 2026 12:35:00 -0300 Subject: [PATCH 2/5] Add RELEASE.md --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..8d34ff4 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +RELEASE_TYPE: minor + +Define Automatic-Module-Name with Maven maven-jar-plugin. \ No newline at end of file From 31c5133297be24f8c2625a067de19ab3b7e424da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Lamar=C3=A3o?= Date: Wed, 24 Jun 2026 12:35:15 -0300 Subject: [PATCH 3/5] Add RELEASE.md --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 8d34ff4..49dd50d 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,3 @@ RELEASE_TYPE: minor -Define Automatic-Module-Name with Maven maven-jar-plugin. \ No newline at end of file +Define Automatic-Module-Name with maven-jar-plugin. \ No newline at end of file From f5cc36181ee7b84cf22d3320b57fe12c505ce5af Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Jun 2026 11:16:02 -0400 Subject: [PATCH 4/5] add integration test --- .gitignore | 8 +++ pom.xml | 20 ++++++++ src/it/module-consumer/invoker.properties | 3 ++ src/it/module-consumer/pom.xml | 51 +++++++++++++++++++ .../src/main/java/module-info.java | 6 +++ .../java/dev/hegel/consumer/ConsumerTest.java | 16 ++++++ 6 files changed, 104 insertions(+) create mode 100644 src/it/module-consumer/invoker.properties create mode 100644 src/it/module-consumer/pom.xml create mode 100644 src/it/module-consumer/src/main/java/module-info.java create mode 100644 src/it/module-consumer/src/test/java/dev/hegel/consumer/ConsumerTest.java diff --git a/.gitignore b/.gitignore index 5a0e1d8..59a41e1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ target/ .hegel/ *.class *.pyc +CLAUDE.local.md +.claude/settings.local.json + +# whitelist .claude files, rather than blacklist. Some local claude files +# (skills, agents) have to live in .claude - as opposed to eg CLAUDE.local.md, +# which we gitignore at the top level. +.claude/* +!.claude/CLAUDE.md diff --git a/pom.xml b/pom.xml index 5b3d35b..2507004 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,26 @@ + + org.apache.maven.plugins + maven-invoker-plugin + 3.9.0 + + src/it + ${project.build.directory}/it + true + + + + module-consumer-it + + install + run + + + + + com.diffplug.spotless spotless-maven-plugin diff --git a/src/it/module-consumer/invoker.properties b/src/it/module-consumer/invoker.properties new file mode 100644 index 0000000..bb145c5 --- /dev/null +++ b/src/it/module-consumer/invoker.properties @@ -0,0 +1,3 @@ +# `test` compiles the module (resolving `requires dev.hegel;`, the module-name guard) and runs +# the @HegelTest against the real engine, consuming hegel on the module path end to end. +invoker.goals = test diff --git a/src/it/module-consumer/pom.xml b/src/it/module-consumer/pom.xml new file mode 100644 index 0000000..788429a --- /dev/null +++ b/src/it/module-consumer/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + dev.hegel.it + module-consumer + 1.0 + jar + + + UTF-8 + 22 + + + + + dev.hegel + hegel + + @project.version@ + + + + org.junit.jupiter + junit-jupiter + 5.11.4 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + --enable-native-access=dev.hegel + + + + + diff --git a/src/it/module-consumer/src/main/java/module-info.java b/src/it/module-consumer/src/main/java/module-info.java new file mode 100644 index 0000000..43617f6 --- /dev/null +++ b/src/it/module-consumer/src/main/java/module-info.java @@ -0,0 +1,6 @@ +// The presence of `module-info.java` puts hegel on the module path. `requires dev.hegel;` +// names the automatic module by its committed `Automatic-Module-Name`; if that name ever +// changes, this no longer resolves and the build fails — which is the regression we guard. +module dev.hegel.consumer { + requires dev.hegel; +} diff --git a/src/it/module-consumer/src/test/java/dev/hegel/consumer/ConsumerTest.java b/src/it/module-consumer/src/test/java/dev/hegel/consumer/ConsumerTest.java new file mode 100644 index 0000000..a4f4922 --- /dev/null +++ b/src/it/module-consumer/src/test/java/dev/hegel/consumer/ConsumerTest.java @@ -0,0 +1,16 @@ +package dev.hegel.consumer; + +import static dev.hegel.Generators.integers; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dev.hegel.HegelTest; +import dev.hegel.TestCase; + +class ConsumerTest { + @HegelTest + void additionCommutes(TestCase tc) { + int x = tc.draw(integers().min(0).max(100)); + int y = tc.draw(integers().min(0).max(100)); + assertEquals(x + y, y + x); + } +} From 2921d252bdda504400b65af6fc14cd2367cf8e9a Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Jun 2026 11:31:40 -0400 Subject: [PATCH 5/5] move to ReflectionSupport --- .claude/CLAUDE.md | 5 ++--- RELEASE.md | 7 ++++++- .../java/dev/hegel/HegelTestExtension.java | 21 ++----------------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 47e45f8..e71d414 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -79,8 +79,7 @@ public `Generator`/`TestCase`/`Generators`/`Hegel` surface stays in `dev.hegel`. ## Coverage notes -Two genuinely-unreachable defensive catch blocks are excluded via `@Generated` (JaCoCo ignores -`*Generated*`-named annotations): the `NoSuchAlgorithmException` for SHA-256 and the reflective -dispatch in `HegelTestExtension`. Everything else is covered by real-engine integration tests plus +One genuinely-unreachable defensive catch block is excluded via `@Generated` (JaCoCo ignores +`*Generated*`-named annotations): the `NoSuchAlgorithmException` for SHA-256. Everything else is covered by real-engine integration tests plus `FakeLibhegel`-driven error-path tests. The engine's `collection_more`/`new_collection` out-params are read unconditionally (the engine signals exhaustion on the following draw, not at those calls). diff --git a/RELEASE.md b/RELEASE.md index 49dd50d..478d6dd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,8 @@ RELEASE_TYPE: minor -Define Automatic-Module-Name with maven-jar-plugin. \ No newline at end of file +Improve Java Platform Module System support: + +- Define `Automatic-Module-Name: dev.hegel` in the jar manifest, giving the artifact a stable + module name on the module path. +- `@HegelTest` now invokes the test method through the JUnit platform's reflection support, so + modular consumers no longer have to open their test package to `dev.hegel`. diff --git a/src/main/java/dev/hegel/HegelTestExtension.java b/src/main/java/dev/hegel/HegelTestExtension.java index e05169f..5a9bb5c 100644 --- a/src/main/java/dev/hegel/HegelTestExtension.java +++ b/src/main/java/dev/hegel/HegelTestExtension.java @@ -1,6 +1,5 @@ package dev.hegel; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.EnumSet; @@ -14,6 +13,7 @@ import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.support.ReflectionSupport; /** * JUnit 5 extension backing {@link HegelTest}. Reports the property as a single test entry and @@ -76,7 +76,7 @@ public void interceptTestTemplateMethod( HegelTest ann = method.getAnnotation(HegelTest.class); Settings settings = settingsFrom(ann, method.getName()); Object target = invocationContext.getTarget().orElse(null); - Hegel.test(tc -> invoke(method, target, tc), settings); + Hegel.test(tc -> ReflectionSupport.invokeMethod(method, target, tc), settings); } } @@ -120,21 +120,4 @@ static boolean isDefaultPhases(Phase[] phases) { static boolean isTestCaseParam(Class type) { return type == TestCase.class; } - - @Generated // thin reflective dispatch; failure propagation is verified end-to-end. - private static void invoke(Method method, Object target, TestCase tc) { - try { - method.setAccessible(true); - method.invoke(target, tc); - } catch (InvocationTargetException e) { - sneakyThrow(e.getCause()); - } catch (IllegalAccessException e) { - throw new HegelException("Cannot invoke @HegelTest method " + method.getName(), e); - } - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable t) throws E { - throw (E) t; - } }