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
41 changes: 41 additions & 0 deletions .github/workflows/release-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Release Smoke (consumer verification)

# Manually verifies that a PUBLISHED GraphCompose release resolves and works for
# real external consumers straight from Maven Central (scripts/release-smoke/).
# Not tied to a tag or push — dispatch it after a publish (allowing for Central
# indexing delay), or any time, to re-check a shipped version. It never touches a
# -SNAPSHOT: release smoke tests published artifacts only.

on:
workflow_dispatch:
inputs:
version:
description: 'Published GraphCompose version to smoke-test from Maven Central'
required: true
type: string
default: '2.0.0'

permissions:
contents: read

jobs:
smoke:
name: Smoke ${{ inputs.version }} from Maven Central
runs-on: ubuntu-latest
env:
JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Temurin JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'

- name: Run release smoke against Maven Central
# The harness forces Central-only resolution (its settings.xml) and evicts
# the GraphCompose artifacts before each scenario, so this genuinely tests
# the published ${{ inputs.version }} coordinates end to end.
run: bash scripts/release-smoke/run.sh --version "${{ inputs.version }}"
71 changes: 71 additions & 0 deletions scripts/release-smoke/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Release smoke — external consumer projects

Standalone Maven projects that consume the **published** GraphCompose
coordinates from Maven Central, proving the modular 2.0 release resolves and
behaves as documented *outside* this repository — no reactor, no local
`mvn install` of GraphCompose beforehand.

These projects are **not** part of the root reactor (the root `pom.xml`
`<modules>` does not list them and each pom has no `<parent>`), so a normal
build never touches them. Run them explicitly with the harness below.

## Scenarios

| Dir | Coordinate(s) under test | Proves |
|---|---|---|
| `s1-graph-compose` | `graph-compose` | the drop-in wrapper renders a PDF out of the box |
| `s2-core-only` | `graph-compose-core` | a lean core throws `MissingBackendException` naming `graph-compose-render-pdf`, and its dependency tree pulls no PDFBox / POI / ZXing / templates / fonts / emoji (enforced by `maven-enforcer` bannedDependencies) |
| `s3-core-render-pdf` | `graph-compose-core` + `graph-compose-render-pdf` | the explicit lean + backend combination renders a PDF |
| `s4-templates` | `graph-compose-templates` | a built-in template composes and renders through the PDF stack |
| `s5-testing` | `graph-compose` + `graph-compose-testing` | the consumer testing helper (`LayoutSnapshotAssertions`) resolves and round-trips a layout snapshot |
| `s6-bundle` | `graph-compose-bundle` | the batteries-included aggregate renders a templated document, exposes the bundled fonts (`DefaultFonts.bundledFontNames()`), and makes the colour-emoji set resolvable (`GraphComposeEmoji.isAvailable()`) |

The "must pull core + render-pdf" (wrapper) and "must pull the documented
aggregate" (bundle) assertions are proven positively: `s1` / `s6` can only
render because the backend and companions were pulled transitively.

## Running

```bash
# Evict the GraphCompose artifacts before each scenario (Central-only, isolated):
./scripts/release-smoke/run.sh

# Smoke-test a different published version:
./scripts/release-smoke/run.sh --version 2.0.1

# Fast dev iteration — keep everything cached:
./scripts/release-smoke/run.sh --warm
```

```powershell
pwsh ./scripts/release-smoke/run.ps1 # isolated
pwsh ./scripts/release-smoke/run.ps1 -Version 2.0.1 # a different published version
pwsh ./scripts/release-smoke/run.ps1 -Warm # warm
```

Or dispatch the **Release Smoke (consumer verification)** GitHub Actions workflow
(`.github/workflows/release-smoke.yml`) with a `version` input — handy after a
publish, once Central has indexed the release.

The harness passes an isolated `settings.xml` (`-s`) whose `mirrorOf=*` mirror
forces **every** artifact and plugin request through Maven Central, and uses one
dedicated local repository under `target/` that never receives an `mvn install`
of GraphCompose. In the default (isolated) mode it **evicts the GraphCompose
artifacts** (`io/github/demchaav/**`) before each scenario — hard-failing if the
eviction does not take — so every scenario must re-resolve `graph-compose-*` from
Central, proving the release resolves with no local reactor build behind it.
Maven's own plugins and third-party libraries (PDFBox, JUnit, …) stay cached,
because re-downloading Maven's core plugins onto an empty repository is heavy,
flaky, and tests Maven rather than this release. Classpath isolation between
scenarios (e.g. the lean core never seeing the PDF backend) comes from each
project's declared dependencies and the `s2` enforcer rule, not from the
repository state.

The harness prints one `RESULT <scenario> PASS|FAIL` line per project and ends
with a machine-readable `SUMMARY {"version":"…","passed":N,"failed":M,"total":T}`
line. Exit code is non-zero if any scenario fails.

The version under test defaults to the current published release (`2.0.0`) and is
overridable with `--version` / `-Version` (or the workflow's `version` input); it
is passed to Maven as `-Dgc.version`, overriding the `gc.version` property in each
pom. Release smoke always tests **published** artifacts — never a `-SNAPSHOT`.
68 changes: 68 additions & 0 deletions scripts/release-smoke/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<#
Release smoke harness (PowerShell) — runs every external consumer project
against the published GraphCompose coordinates on Maven Central. See README.md.

Isolation model: a dedicated local repository under target/ that never receives
an `mvn install` of GraphCompose, plus an isolated settings.xml whose mirror
forces ALL resolution through Maven Central. Before each scenario the GraphCompose
artifacts (io\github\demchaav\**) are EVICTED — and the harness hard-fails if the
eviction does not take — so every scenario must re-resolve graph-compose-* from
Central. Maven's own plugins and third-party libraries stay cached: re-downloading
Maven's core plugins on an empty repo is heavy, flaky, and tests Maven, not this
release.

Usage:
pwsh ./scripts/release-smoke/run.ps1 # isolated, tests 2.0.0
pwsh ./scripts/release-smoke/run.ps1 -Version 2.0.1 # test a different published version
pwsh ./scripts/release-smoke/run.ps1 -Warm # keep everything cached (fast dev iteration)
#>
param(
[switch]$Warm,
# Default version under test: the currently published release. Release smoke
# must test PUBLISHED artifacts — never a -SNAPSHOT.
[string]$Version = '2.0.0'
)

$ErrorActionPreference = 'Continue'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = (Resolve-Path (Join-Path $here '..\..')).Path
$mvnw = Join-Path $repoRoot 'mvnw.cmd'
$settings = Join-Path $here 'settings.xml'

$scenarios = @('s1-graph-compose', 's2-core-only', 's3-core-render-pdf', 's4-templates', 's5-testing', 's6-bundle')
$repo = Join-Path $repoRoot 'target\release-smoke-m2\repo'
New-Item -ItemType Directory -Force -Path $repo | Out-Null

$pass = 0
$fail = 0
$results = @()

foreach ($s in $scenarios) {
if (-not $Warm) {
# Evict only the GraphCompose coordinates, then hard-fail if it did not take.
$gc = Join-Path $repo 'io\github\demchaav'
if (Test-Path $gc) { Remove-Item -Recurse -Force $gc }
if (Test-Path $gc) {
Write-Error "FATAL: could not remove the GraphCompose cache directory: $gc"
exit 3
}
}
Write-Host ""
Write-Host "=================================================================="
Write-Host "=== SMOKE $s (version=$Version, repo=$repo, evicted=$(if ($Warm) { 'no' } else { 'yes' }))"
Write-Host "=================================================================="
& $mvnw -B -ntp -s $settings "-Dgc.version=$Version" -f (Join-Path $here "$s\pom.xml") "-Dmaven.repo.local=$repo" clean verify
if ($LASTEXITCODE -eq 0) {
$results += "$s PASS"; $pass++
} else {
$results += "$s FAIL"; $fail++
}
}

Write-Host ""
Write-Host "===================== RELEASE SMOKE SUMMARY ====================="
Write-Host "version-under-test: $Version"
foreach ($r in $results) { Write-Host "RESULT $r" }
Write-Host ("SUMMARY {""version"":""$Version"",""passed"":$pass,""failed"":$fail,""total"":$($pass + $fail)}")

if ($fail -ne 0) { exit 1 } else { exit 0 }
83 changes: 83 additions & 0 deletions scripts/release-smoke/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
#
# Release smoke harness — runs every external consumer project against the
# published GraphCompose coordinates on Maven Central. See README.md.
#
# Isolation model: a dedicated local repository under target/ that never receives
# an `mvn install` of GraphCompose, plus an isolated settings.xml whose mirror
# forces ALL resolution through Maven Central. Before each scenario the GraphCompose
# artifacts (io/github/demchaav/**) are EVICTED — and the harness hard-fails if the
# eviction does not take — so every scenario must re-resolve graph-compose-* from
# Central. Maven's own plugins and third-party libraries stay cached: re-downloading
# Maven's core plugins on an empty repo is heavy, flaky, and tests Maven, not this
# release.
#
# Usage:
# ./scripts/release-smoke/run.sh # isolated, tests gc.version=2.0.0
# ./scripts/release-smoke/run.sh --version 2.0.1 # test a different published version
# ./scripts/release-smoke/run.sh --warm # keep everything cached (fast dev iteration)
#
set -u

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$HERE/../.." && pwd)"
MVNW="$REPO_ROOT/mvnw"
SETTINGS="$HERE/settings.xml"

SCENARIOS=(s1-graph-compose s2-core-only s3-core-render-pdf s4-templates s5-testing s6-bundle)
REPO="$REPO_ROOT/target/release-smoke-m2/repo"

# Default version under test: the currently published release. Release smoke must
# test PUBLISHED artifacts — never a -SNAPSHOT.
GC_VERSION="2.0.0"
WARM=0
while [ $# -gt 0 ]; do
case "$1" in
--warm) WARM=1; shift ;;
--version) GC_VERSION="${2:?--version needs a value}"; shift 2 ;;
--version=*) GC_VERSION="${1#*=}"; shift ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
mkdir -p "$REPO"

pass=0
fail=0
declare -a results

for s in "${SCENARIOS[@]}"; do
if [ "$WARM" = "0" ]; then
# Evict only the GraphCompose coordinates so they must come from Central,
# then hard-fail if the eviction did not take (a stale cache would mask a
# broken publish).
gc_dir="$REPO/io/github/demchaav"
rm -rf "$gc_dir"
if [ -d "$gc_dir" ]; then
echo "FATAL: could not remove the GraphCompose cache directory: $gc_dir" >&2
exit 3
fi
fi
echo ""
echo "=================================================================="
echo "=== SMOKE $s (version=$GC_VERSION, repo=$REPO, evicted=$([ "$WARM" = "0" ] && echo yes || echo no))"
echo "=================================================================="
"$MVNW" -B -ntp -s "$SETTINGS" -Dgc.version="$GC_VERSION" \
-f "$HERE/$s/pom.xml" -Dmaven.repo.local="$REPO" clean verify
if [ $? -eq 0 ]; then
results+=("$s PASS")
pass=$((pass + 1))
else
results+=("$s FAIL")
fail=$((fail + 1))
fi
done

echo ""
echo "===================== RELEASE SMOKE SUMMARY ====================="
echo "version-under-test: $GC_VERSION"
for r in "${results[@]}"; do
echo "RESULT $r"
done
echo "SUMMARY {\"version\":\"$GC_VERSION\",\"passed\":$pass,\"failed\":$fail,\"total\":$((pass + fail))}"

[ "$fail" = "0" ]
52 changes: 52 additions & 0 deletions scripts/release-smoke/s1-graph-compose/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?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>

<!-- Standalone consumer smoke project: no <parent>, so it is never part of
the GraphCompose reactor. Resolves graph-compose from Maven Central. -->
<groupId>com.demcha.smoke</groupId>
<artifactId>graph-compose-smoke-graph-compose</artifactId>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<gc.version>2.0.0</gc.version>
<junit.version>5.11.4</junit.version>
<assertj.version>3.27.3</assertj.version>
<surefire.version>3.5.2</surefire.version>
</properties>

<dependencies>
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>${gc.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.demcha.smoke;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import org.junit.jupiter.api.Test;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;

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

/**
* Scenario 1 — the drop-in {@code graph-compose} wrapper renders a PDF out of
* the box, exactly as a 1.x caller expects after upgrading to 2.0. The wrapper
* pulls {@code graph-compose-core} + {@code graph-compose-render-pdf}
* transitively, so a successful render is positive proof both were resolved.
*/
class WrapperRendersPdfTest {

@Test
void graphComposeWrapperRendersPdfOutOfTheBox() throws Exception {
Path out = Files.createTempFile("gc-smoke-wrapper", ".pdf");
try (DocumentSession document = GraphCompose.document(out)
.pageSize(DocumentPageSize.A4)
.margin(36f, 36f, 36f, 36f)
.create()) {
document.add(document.dsl().paragraph()
.text("graph-compose 2.0.0 renders PDF out of the box.")
.build());
document.buildPdf();
}

assertThat(Files.size(out)).isGreaterThan(0L);
byte[] head = Arrays.copyOf(Files.readAllBytes(out), 5);
assertThat(new String(head)).isEqualTo("%PDF-");
}
}
Loading
Loading