Skip to content
Merged
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
5 changes: 2 additions & 3 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
RELEASE_TYPE: minor

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`.
33 changes: 33 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@
<version>3.13.0</version>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>dev.hegel</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>

<!-- Filter src/main/java-templates into generated-sources, substituting ${libhegel.version}
into BuildInfo.ENGINE_VERSION. build-helper (below) adds the output to the compile path. -->
<plugin>
Expand Down Expand Up @@ -184,6 +197,26 @@
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.9.0</version>
<configuration>
<projectsDirectory>src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
<streamLogs>true</streamLogs>
</configuration>
<executions>
<execution>
<id>module-consumer-it</id>
<goals>
<goal>install</goal>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
Expand Down
3 changes: 3 additions & 0 deletions src/it/module-consumer/invoker.properties
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions src/it/module-consumer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>dev.hegel.it</groupId>
<artifactId>module-consumer</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>22</maven.compiler.release>
</properties>

<dependencies>
<dependency>
<groupId>dev.hegel</groupId>
<artifactId>hegel</artifactId>
<!-- Filtered by the invoker plugin to the version under test. -->
<version>@project.version@</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<argLine>--enable-native-access=dev.hegel</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
6 changes: 6 additions & 0 deletions src/it/module-consumer/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
21 changes: 2 additions & 19 deletions src/main/java/dev/hegel/HegelTestExtension.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 <E extends Throwable> void sneakyThrow(Throwable t) throws E {
throw (E) t;
}
}
Loading