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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Run guards
run: |
./mvnw -B -ntp clean \
"-Dtest=EnginePdfBoundaryTest,CanonicalTemplateComposerPdfBoundaryTest,PdfRenderInterfaceGuardTest,PdfRenderingSystemECSDispatchTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,TemplateComposeApiTest" \
"-Dtest=EnginePdfBoundaryTest,CanonicalTemplateComposerPdfBoundaryTest,PdfRenderInterfaceGuardTest,PdfRenderingSystemECSDispatchTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,TemplateComposeApiTest,VersionConsistencyGuardTest" \
test

build-and-test:
Expand Down
30 changes: 30 additions & 0 deletions aggregator/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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>io.github.demchaav</groupId>
<artifactId>graphcompose-build</artifactId>
<version>1.6.4</version>
<packaging>pom</packaging>

<name>GraphCompose Build Aggregator</name>
<description>
Non-published reactor aggregator. Builds the library plus the examples
and benchmarks modules in one pass and gives a single entry point for
`versions:set`, so a version bump propagates to every module at once.
This is NOT a Maven parent and is NOT published to JitPack: the library
root pom.xml stays standalone so consumer coordinates never change.

Usage:
mvn -f aggregator/pom.xml -DskipTests install # build everything
mvn -f aggregator/pom.xml versions:set -DnewVersion=X # bump all modules
</description>

<modules>
<module>..</module>
<module>../examples</module>
<module>../benchmarks</module>
</modules>
</project>
11 changes: 8 additions & 3 deletions benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.github.demchaav</groupId>
<parent>
<groupId>io.github.demchaav</groupId>
<artifactId>graphcompose-build</artifactId>
<version>1.6.4</version>
<relativePath>../aggregator/pom.xml</relativePath>
</parent>

<artifactId>graphcompose-benchmarks</artifactId>
<version>1.6.4</version>
<name>GraphCompose Benchmarks</name>
<description>
Performance benchmarks, stress tests, and endurance harnesses for
Expand All @@ -16,7 +21,7 @@
</description>

<properties>
<graphcompose.version>1.6.4</graphcompose.version>
<graphcompose.version>${project.version}</graphcompose.version>
<maven.compiler.release>17</maven.compiler.release>

<junit.bom.version>6.1.0</junit.bom.version>
Expand Down
9 changes: 7 additions & 2 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.github.demchaav</groupId>
<parent>
<groupId>io.github.demchaav</groupId>
<artifactId>graphcompose-build</artifactId>
<version>1.6.4</version>
<relativePath>../aggregator/pom.xml</relativePath>
</parent>

<artifactId>graphcompose-examples</artifactId>
<version>1.6.4</version>

<name>GraphCompose Examples</name>
<description>Runnable file-render examples for GraphCompose templates.</description>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.demcha.documentation;

import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Guards that the GraphCompose version propagates identically to every module
* and to the README install snippets.
*
* <p>This is the safety net behind the aggregator-reactor version model
* (see {@code aggregator/pom.xml}): the library {@code pom.xml} is the single
* version source, the aggregator bumps every module in lockstep via
* {@code versions:set}, and the child modules inherit their version rather than
* pinning a literal. This test fails the build the moment a bump leaves any
* module — or the README copy-paste snippet — pointing at a different version,
* which is the drift class that previously let the benchmarks module run
* against the previous release.
*/
class VersionConsistencyGuardTest {

private static final Path PROJECT_ROOT = Path.of("").toAbsolutePath().normalize();

@Test
void everyModuleResolvesToTheRootProjectVersion() throws Exception {
String root = effectiveVersion(PROJECT_ROOT.resolve("pom.xml"));

assertThat(effectiveVersion(PROJECT_ROOT.resolve("aggregator/pom.xml")))
.describedAs("aggregator/pom.xml version must equal root pom.xml version (%s)", root)
.isEqualTo(root);
assertThat(effectiveVersion(PROJECT_ROOT.resolve("examples/pom.xml")))
.describedAs("examples must inherit the root version (%s)", root)
.isEqualTo(root);
assertThat(effectiveVersion(PROJECT_ROOT.resolve("benchmarks/pom.xml")))
.describedAs("benchmarks must inherit the root version (%s)", root)
.isEqualTo(root);
}

@Test
void childModulesInheritVersionInsteadOfDeclaringTheirOwn() throws Exception {
assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("examples/pom.xml")))
.describedAs("examples/pom.xml must inherit <version> from the aggregator parent, not declare its own")
.isFalse();
assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("benchmarks/pom.xml")))
.describedAs("benchmarks/pom.xml must inherit <version> from the aggregator parent, not declare its own")
.isFalse();
}

@Test
void benchmarksDependencyDerivesVersionAndIsNotHardcoded() throws IOException {
String pom = Files.readString(PROJECT_ROOT.resolve("benchmarks/pom.xml"));

assertThat(pom)
.describedAs("benchmarks graphcompose.version must derive from ${project.version}")
.contains("<graphcompose.version>${project.version}</graphcompose.version>");
assertThat(Pattern.compile("<graphcompose\\.version>\\s*\\d").matcher(pom).find())
.describedAs("benchmarks must not hardcode a numeric graphcompose.version — that is the original drift source")
.isFalse();
}

@Test
void readmeInstallSnippetsMatchTheProjectVersion() throws Exception {
String root = effectiveVersion(PROJECT_ROOT.resolve("pom.xml"));
String readme = Files.readString(PROJECT_ROOT.resolve("README.md"));

String mavenSnippetVersion = firstGroup(readme,
"<artifactId>GraphCompose</artifactId>\\s*<version>v?([0-9][^<]*)</version>");
String gradleSnippetVersion = firstGroup(readme,
"GraphCompose:v?([0-9][^\")]*)");

assertThat(mavenSnippetVersion)
.describedAs("README Maven/JitPack snippet must reference the current project version (%s)", root)
.isEqualTo(root);
assertThat(gradleSnippetVersion)
.describedAs("README Gradle/JitPack snippet must reference the current project version (%s)", root)
.isEqualTo(root);
}

private static String firstGroup(String text, String regex) {
Matcher matcher = Pattern.compile(regex).matcher(text);
assertThat(matcher.find())
.describedAs("Expected a GraphCompose install-snippet version matching /%s/ in README.md", regex)
.isTrue();
return matcher.group(1);
}

private static boolean declaresOwnVersion(Path pom) throws Exception {
return directChild(parse(pom).getDocumentElement(), "version") != null;
}

private static String effectiveVersion(Path pom) throws Exception {
Element project = parse(pom).getDocumentElement();

Element ownVersion = directChild(project, "version");
if (ownVersion != null) {
return ownVersion.getTextContent().trim();
}
Element parent = directChild(project, "parent");
if (parent != null) {
Element parentVersion = directChild(parent, "version");
if (parentVersion != null) {
return parentVersion.getTextContent().trim();
}
}
throw new IllegalStateException("No <project>/<version> or inherited <parent>/<version> in " + pom);
}

private static Element directChild(Element parent, String name) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name)) {
return (Element) node;
}
}
return null;
}

private static Document parse(Path pom) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(pom.toFile());
}
}