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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @Monforton @thestuckster
80 changes: 80 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Create new release

on:
Comment thread
Monforton marked this conversation as resolved.
workflow_dispatch:
inputs:
tag:
description: 'Tag for the release (e.g., 1.0.0)'
required: true
default: '0.0.0'
draft-release:
description: 'Should this release be a draft?'
type: boolean
required: false
default: true

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
include:
- os: ubuntu-latest
graalvm-home: /usr/lib/graalvm
- os: windows-latest
graalvm-home: C:\graalvm
- os: macos-latest
graalvm-home: /Library/Java/JavaVirtualMachines/graalvm

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup GraalVM
uses: graalvm/setup-graalvm@v1
Comment thread
Monforton marked this conversation as resolved.
with:
java-version: '17.0.12'
distribution: 'graalvm'
#github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Build native executable
env:
VERSION_TAG: ${{ github.event.inputs.tag }}
run: ./gradlew nativeImage -Pversion="$VERSION_TAG"

- name: Upload native executable
uses: actions/upload-artifact@v4
with:
name: native-executable-${{ matrix.os }}
path: build/native-image/fern-junit-client*

release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
Comment thread
Monforton marked this conversation as resolved.

- name: Make artifacts executable
run: chmod +x artifacts/native-executable-*

- name: Create GitHub Release
id: create_release
uses: ncipollo/release-action@v1
Comment thread
Monforton marked this conversation as resolved.
with:
allowUpdates: true
draft: "${{ github.event.inputs.draft-release }}"
generateReleaseNotes: true
artifacts: artifacts/**
#token: ${{ secrets.GITHUB_TOKEN }}
tag: 'v${{ github.event.inputs.tag }}'
name: 'v${{ github.event.inputs.tag }}'

- name: Add release URL to job summary
if: success()
run: |
echo "Release URL: ${{ steps.create_release.outputs.html_url }}"
echo "Release URL: ${{ steps.create_release.outputs.html_url }}" >> $GITHUB_STEP_SUMMARY
60 changes: 56 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Fern JUnit Gradle Plugin

A Gradle plugin for publishing JUnit test results to a Fern test reporting instance.
A Gradle plugin and CLI for publishing JUnit test results to a Fern test reporting instance.

![example workflow](https://github.com/guidewire-oss/fern-junit-gradle-plugin/actions/workflows/gradle.yml/badge.svg?event=push)
![plugin](https://img.shields.io/gradle-plugin-portal/v/io.github.guidewire-oss.fern-publisher?label=Gradle%20Plugins%20Portal&color=blue)
Expand Down Expand Up @@ -35,7 +35,7 @@ Or in `build.gradle`:

```groovy
plugins {
id 'io.github.guidewire-oss.fern-publisher' version '1.0.0'
id 'io.github.guidewire-oss.fern-publisher' version '1.0.0'
}
```

Expand All @@ -45,7 +45,8 @@ plugins {

Newer versions of the Fern Reporter server require you to pre-register your application to receive a UUID (your project id)

1. To register your project send a `POST` request to `<yourFernUrl>/api/project` with JSON body of
1. To register your project send a `POST` request to `<yourFernUrl>/api/project` with JSON body of

```json
{
"name": "my-project",
Expand All @@ -55,6 +56,7 @@ Newer versions of the Fern Reporter server require you to pre-register your appl
```

Here is an example curl command for ease of use:

```shell
curl -X POST "https://yourFernUrl.com/api/project" \
-H "Content-Type: application/json" \
Expand All @@ -66,6 +68,7 @@ curl -X POST "https://yourFernUrl.com/api/project" \
```

2. You will receive a successful response that looks like:

```json
{
"uuid": "59e06cf8-f390-5093-af2e-3685be593a25",
Expand All @@ -79,7 +82,7 @@ curl -X POST "https://yourFernUrl.com/api/project" \

You will need to take note of the returned `ProjectID` UUID for use in configuring the plugin, as described below

### Plugin Setup
### Plugin Setup

Configure the plugin in your build script:

Expand Down Expand Up @@ -158,6 +161,45 @@ fernPublisher {
- Kotlin 1.4 or later
- JUnit 4 and JUnit 5 XML report formats

## Command-Line Interface (CLI)

This project also provides a CLI tool, `fern-junit-client`, for collecting and publishing JUnit XML test reports to a Fern Reporter instance. It is designed to work the same way as the Gradle plugin, but can be used independently of Gradle and Java.

Builds of the CLI binary are available for Linux, macOS, and Windows on the [Releases page](https://github.com/guidewire-oss/fern-junit-gradle-plugin/releases).

### Usage

```sh
fern-junit-client send \
--fern-url <FERN_URL> \
--project-name <PROJECT_NAME> \
--project-id <PROJECT_ID> \
--file-pattern <REPORT_PATTERN> \
[--tags <TAGS>] \
[--verbose]
```

#### Options

- `--fern-url` (`-u`): Base URL of the Fern Reporter instance to send test reports to (required)
- `--project-name` (`-n`): Name of the project to associate test reports with (required)
- `--project-id` (`-i`): Project ID to associate test reports with (required)
- `--file-pattern` (`-f`): Glob pattern(s) for JUnit XML reports (can be repeated, required)
- `--tags` (`-t`): (Optional) Comma-separated tags to include on the test run
- `--verbose` (`-v`): (Optional) Enable verbose output for debugging

### Example

```sh
fern-junit-client send \
--fern-url https://fern.example.com \
--project-name my-service \
--project-id 1234 \
--file-pattern "build/test-results/**/*.xml" \
--tags "ci,nightly" \
--verbose
```

## Building from Source

```bash
Expand Down Expand Up @@ -186,4 +228,14 @@ pluginManagement {
mavenCentral()
}
}
```
### Building the CLI
Native CLI binaries are built using GraalVM's native-image tool. This allows the CLI to run without requiring a Java runtime, making it lightweight and portable.

For building the CLI, you will need to have GraalVM installed and set the environment variable `GRAALVM_HOME` as your GraalVM install location.

Once that is set up, run the following command in the project root:

```bash
./gradlew nativeImage -Pversion="1.0.0-SNAPSHOT"
```
166 changes: 133 additions & 33 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,64 +1,164 @@
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform

plugins {
id 'org.jetbrains.kotlin.jvm' version '2.1.20'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.20'
id 'java-gradle-plugin'
id 'com.gradle.plugin-publish' version '1.2.1'
id 'com.gradleup.shadow' version "8.3.6"
id 'maven-publish' // Add this plugin to use publishing block
id 'org.jetbrains.kotlin.jvm' version '2.1.20'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.20'
id 'java-gradle-plugin'
id "org.graalvm.buildtools.native" version "0.10.6" // GraalVM plugin
id 'application'
id 'com.gradle.plugin-publish' version '1.2.1'
id 'com.gradleup.shadow' version "8.3.6"
id 'maven-publish' // Add this plugin to use publishing block
}

group = 'io.github.guidewire-oss'
version = '1.0.0'
version = project.hasProperty('version') ? project.version : '1.0.0'

def os = DefaultNativePlatform.currentOperatingSystem
def arch = DefaultNativePlatform.currentArchitecture

gradlePlugin {
website = "https://github.com/guidewire-oss/fern-junit-gradle-plugin"
vcsUrl = "https://github.com/guidewire-oss/fern-junit-gradle-plugin"
plugins {
create("fernPublisher") {
id = "io.github.guidewire-oss.fern-publisher"
displayName = 'fern-publisher'
description = 'This plugin simplifies the process of collecting JUnit XML test reports and publishing them to a Fern test reporting service. It parses JUnit XML reports, converts them to Fern\'s data model, and sends them to your Fern instance through its API. To learn more about Fern, check out its repository: https://github.com/guidewire-oss/fern-reporter'
tags = ['testing', 'testing-tools', 'reporter', 'fern', 'test-reporter']
implementationClass = "io.github.guidewire.oss.plugin.FernPublisherPlugin"
}
website = "https://github.com/guidewire-oss/fern-junit-gradle-plugin"
vcsUrl = "https://github.com/guidewire-oss/fern-junit-gradle-plugin"
plugins {
create("fernPublisher") {
id = "io.github.guidewire-oss.fern-publisher"
displayName = 'fern-publisher'
description = 'This plugin simplifies the process of collecting JUnit XML test reports and publishing them to a Fern test reporting service. It parses JUnit XML reports, converts them to Fern\'s data model, and sends them to your Fern instance through its API. To learn more about Fern, check out its repository: https://github.com/guidewire-oss/fern-reporter'
tags = ['testing', 'testing-tools', 'reporter', 'fern', 'test-reporter']
implementationClass = "io.github.guidewire.oss.plugin.FernPublisherPlugin"
}
}
}

publishing {
repositories {
mavenLocal()
}
repositories {
mavenLocal()
}
}

repositories {
mavenLocal()
mavenCentral()
mavenLocal()
mavenCentral()
}

shadowJar {
archiveClassifier.set('') // This removes the default 'all' classifier
archiveClassifier.set('') // This removes the default 'all' classifier
}

dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0"
implementation "com.github.ajalt.clikt:clikt:5.0.1"

testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.assertj:assertj-core:3.27.3"
testImplementation "org.junit.jupiter:junit-jupiter:5.9.2"
testImplementation "org.wiremock:wiremock:3.12.1"
testImplementation gradleTestKit()

testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

application {
mainClass = "io.github.guidewire.oss.MainKt"
}

jar {
manifest {
attributes 'Main-Class': 'io.github.guidewire.oss.MainKt'
}
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' // NECESSARY TO BUILD JAR

// Include all dependencies in the jar (fat jar)
from {
configurations.runtimeClasspath.collect {
it.isDirectory() ? it : zipTree(it)
}
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

// Currently just used to pass the version. If you can find a better way of doing this, then go for it
task generateProps {
def propsFile = layout.buildDirectory.file("resources/main/app.properties")

doLast {
file(propsFile.get().asFile).write("appVersion=$version")
}
}

task nativeImage(type: Exec) {
group = 'build'
description = 'Builds a native image using GraalVM'

def outputName = "fern-junit-client-${version}-${os.toFamilyName()}-${arch.name}"
def graalVmHome = System.getenv('GRAALVM_HOME')

dependsOn("generateProps")
dependsOn tasks.jar

doFirst {
if (graalVmHome == null) {
throw new GradleException('GRAALVM_HOME environment variable not set')
}

def nativeImageCmd = "${graalVmHome}/bin/native-image${os.isWindows() ? '.cmd' : ''}"
if (!new File(nativeImageCmd).exists()) {
throw new GradleException("Native image command not found at: $nativeImageCmd")
}

// Create a special jar for native image building, excluding Gradle plugin classes
Comment thread
Monforton marked this conversation as resolved.
copy {
from jar.outputs
into "${buildDir}/native-image"
rename {fileName ->
fileName.replace(jar.archiveFileName.get(), "native-${jar.archiveFileName.get()}")
}
}

// Remove Gradle plugin classes from the jar
fileTree("${buildDir}/native-image").matching {
include "native-${jar.archiveFileName.get()}"
}.each {jarFile ->
ant.zip(destfile: "${buildDir}/native-image/temp.jar") {
zipfileset(src: jarFile) {
exclude(name: "org/gradle/internal/impldep/org/bouncycastle/jcajce/provider/drbg/**")
exclude(name: "com/guidewire/plugin/**")
exclude(name: "META-INF/gradle-plugins/**")
}
}
delete jarFile
file("${buildDir}/native-image/temp.jar").renameTo(jarFile)
}
}

testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation("org.assertj:assertj-core:3.27.3")
testImplementation "org.junit.jupiter:junit-jupiter:5.9.2"
testImplementation "org.wiremock:wiremock:3.12.1"
testImplementation(gradleTestKit())
workingDir = "${buildDir}/native-image"
def jarName = "native-${project.name}-${project.version}-main.jar"
Comment thread
Monforton marked this conversation as resolved.

testRuntimeOnly "org.junit.platform:junit-platform-launcher"
commandLine = [
"${graalVmHome}/bin/native-image${os.isWindows() ? '.cmd' : ''}",
'--no-fallback',
'--enable-url-protocols=https',
'-H:+ReportExceptionStackTraces',
'-H:ReflectionConfigurationFiles=../../src/main/resources/reflect-config.json',
Comment thread
Monforton marked this conversation as resolved.
'--initialize-at-build-time=kotlin',
'--initialize-at-build-time=kotlinx',
'--initialize-at-build-time=io.github.guidewire.oss',
'--initialize-at-build-time=org.slf4j',
'--initialize-at-build-time=org.gradle.internal',
'-jar', jarName,
outputName
]
}

tasks.test {
useJUnitPlatform()
useJUnitPlatform()
}

test {
useJUnitPlatform()
useJUnitPlatform()
}

kotlin {
jvmToolchain(17)
jvmToolchain(17)
}
Loading
Loading