Skip to content
Open
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
6 changes: 2 additions & 4 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ jobs:
run: |
total=0; failures=0; errors=0; skipped=0
for dir_name in ${{ matrix.test-results-dirs }}; do
dir="build/test-results/$dir_name"
for f in "$dir"/TEST-*.xml; do
[ -f "$f" ] || continue
while IFS= read -r -d '' f; do
t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
fl=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
e=$(grep -o 'errors="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
Expand All @@ -68,7 +66,7 @@ jobs:
failures=$((failures + ${fl:-0}))
errors=$((errors + ${e:-0}))
skipped=$((skipped + ${s:-0}))
done
done < <(find . -type f -path "*/build/test-results/$dir_name/TEST-*.xml" -print0)
done
passed=$((total - failures - errors - skipped))
mkdir -p /tmp/test-stats
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ jobs:
run: |
total=0; failures=0; errors=0; skipped=0
for dir_name in ${{ matrix.test-results-dirs }}; do
dir="build/test-results/$dir_name"
for f in "$dir"/TEST-*.xml; do
[ -f "$f" ] || continue
while IFS= read -r -d '' f; do
t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
fl=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
e=$(grep -o 'errors="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
Expand All @@ -83,7 +81,7 @@ jobs:
failures=$((failures + ${fl:-0}))
errors=$((errors + ${e:-0}))
skipped=$((skipped + ${s:-0}))
done
done < <(find . -type f -path "*/build/test-results/$dir_name/TEST-*.xml" -print0)
done
passed=$((total - failures - errors - skipped))
mkdir -p /tmp/test-stats
Expand Down
24 changes: 2 additions & 22 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import aQute.bnd.gradle.BundleTaskExtension
import net.ltgt.gradle.errorprone.CheckSeverity
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

import java.text.SimpleDateFormat

Expand All @@ -27,9 +25,8 @@ plugins {
id "io.github.reyerizo.gradle.jcstress" version "0.9.0"
id "net.ltgt.errorprone" version '5.1.0'
id 'jacoco'
//
// Kotlin just for tests - not production code
id 'org.jetbrains.kotlin.jvm' version '2.4.0'
id 'org.jetbrains.kotlin.jvm' version '2.4.0' apply false
id 'org.jetbrains.dokka-javadoc' version '2.2.0' apply false
}

java {
Expand All @@ -38,19 +35,6 @@ java {
}
}

kotlin {
compilerOptions {
apiVersion = KotlinVersion.KOTLIN_2_0
languageVersion = KotlinVersion.KOTLIN_2_0
jvmTarget = JvmTarget.JVM_11
javaParameters = true
freeCompilerArgs = [
'-Xemit-jvm-type-annotations',
'-Xjspecify-annotations=strict',
]
}
}

def makeDevelopmentVersion(parts) {
def version = String.join("-", parts)
println "created development version: $version"
Expand Down Expand Up @@ -174,8 +158,6 @@ dependencies {
errorprone 'com.uber.nullaway:nullaway:0.13.8'
errorprone 'com.google.errorprone:error_prone_core:2.50.0'

// just tests - no Kotlin otherwise
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}

shadowJar {
Expand Down Expand Up @@ -801,5 +783,3 @@ tasks.withType(PublishToMavenRepository) {
tasks.withType(GenerateModuleMetadata) {
enabled = false
}


131 changes: 131 additions & 0 deletions graphql-java-arbitrary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Arbitrary

This module generates streams of arbitrary GraphQL objects that are suitable for
use in fuzzing and property-based testing.

The generators produce graphql-java objects that can be used to test any GraphQL
system built on graphql-java.

The arbitrary object streams are exposed as a Kotest property
[Arb](https://kotest.io/docs/proptest/property-test-generators.html#arbitrary);
properties of an `Arb` can be tested using JUnit, Kotest, or other unit testing
frameworks.

## Dependency

Use the same version as graphql-java:

```kotlin
testImplementation("com.graphql-java:graphql-java-arbitrary:<version>")
```

Reusable test base classes such as `DeepArbSuite` and `ArbPropertyBase` are
published as Gradle test fixtures:

```kotlin
testImplementation(testFixtures("com.graphql-java:graphql-java-arbitrary:<version>"))
```

## Quick Start

```kotlin
import graphql.arbitrary.graphQLSchema
import graphql.schema.idl.SchemaParser
import graphql.schema.idl.SchemaPrinter
import io.kotest.property.Arb
import io.kotest.property.checkAll
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test

class QuickStartTest {
@Test
fun `arbitrary schemas can be roundtripped through SDL`(): Unit = runBlocking {
Arb.graphQLSchema().checkAll { schema ->
val sdl = SchemaPrinter().print(schema)
SchemaParser().parse(sdl)
}
}
}
```

## Configuration

All generator functions in this module accept an optional `Config` parameter.

This is used to customize the shape of objects that are produced, and is
primarily oriented around "weights" that control how often a GraphQL feature
will be explored by a generator.

Omitting a `Config` will use a default configuration that approximates a
real-world probability distribution. The generators in this module are tuned
to have acceptable performance for approximately 1000 generations when using
the default `Config`.

A `Config` may be modified to steer the distribution of generated objects
towards an area of interest. This library includes configuration knobs for
controlling the probabilities of a field definition taking arguments, a
resolver throwing an exception, an inline fragment omitting a type condition,
and more.

See [`Configs.kt`](src/main/kotlin/graphql/arbitrary/Configs.kt) for a full list
of configuration knobs.

### Example

```kotlin
import graphql.arbitrary.Config
import graphql.arbitrary.ObjectTypeSize
import graphql.arbitrary.SchemaSize
import graphql.arbitrary.graphQLSchema
import io.kotest.property.Arb

// Create a custom Config that generates wide object types.
val extraLargeObjectConfig = Config(
SchemaSize(10),
ObjectTypeSize(200..1000)
)

val arbSchema = Arb.graphQLSchema(extraLargeObjectConfig)
```

## Library

Useful generators and methods provided by this library.

### Schema and Documents

| Generator | Description |
|-----------------------------|-------------------------------------------------------------------|
| `Arb.graphQLSchema` | Generate arbitrary `graphql.schema.GraphQLSchema` objects |
| `Arb.graphQLDocument` | Generate valid `graphql.language.Document` objects for a schema |
| `Arb.graphQLExecutionInput` | Generate valid `graphql.ExecutionInput` objects for a schema |
| `Arb.graphQLTypes` | Generate compatible collections of graphql-java schema types |
| `Arb.graphQLName` | Generate names suitable for use in GraphQL |

### Values

| Generator | Description |
|--------------------------|--------------------------------------------------------------------|
| `Arb.graphQLOutputValue` | Generate native result values for a type, selection, or operation |

### Execution

| Generator | Description |
|--------------------|--------------------------------------------------------------------|
| `arbGraphQL` | Create an executable `GraphQL` backed by deterministic arbitrary data |
| `arbCodeRegistry` | Create a `GraphQLCodeRegistry` that serves arbitrary data |
| `arbRuntimeWiring` | Create a `RuntimeWiring` that serves arbitrary data for an SDL schema |

## History

This code was originally created by
[James Bellenger](https://github.com/jbellenger) at Airbnb as part of the
[Viaduct project](https://github.com/airbnb/viaduct).

It was adapted from Viaduct's
[`core/shared/arbitrary` module](https://github.com/airbnb/viaduct/tree/e91e45f1976d7311729af32d48fcd4aab8652516/core/shared/arbitrary)
to generate graphql-java's native schema and language AST types.

The original source is licensed under the Apache License 2.0. The license text
is included in the published artifact and is also available in this repository
at `additionallicenses/APACHE-LICENSE-2.0.txt`.
152 changes: 152 additions & 0 deletions graphql-java-arbitrary/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
id 'java-library'
id 'java-test-fixtures'
id 'org.jetbrains.kotlin.jvm'
id 'org.jetbrains.dokka-javadoc'
id 'maven-publish'
id 'signing'
}

def kotlinVersion = '2.4.0'
def kotestVersion = '6.2.3'

group = rootProject.group
version = rootProject.version

repositories {
mavenCentral()
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
withSourcesJar()
}

kotlin {
compilerOptions {
apiVersion = KotlinVersion.KOTLIN_2_0
languageVersion = KotlinVersion.KOTLIN_2_0
jvmTarget = JvmTarget.JVM_11
javaParameters = true
freeCompilerArgs = [
'-Xemit-jvm-type-annotations',
'-Xjspecify-annotations=strict',
]
}
}

dependencies {
api project(':')
api "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}"
api "io.kotest:kotest-property:${kotestVersion}"

testFixturesApi 'org.junit.jupiter:junit-jupiter:5.14.4'

testImplementation "io.kotest:kotest-assertions-core:${kotestVersion}"
testImplementation 'org.junit.jupiter:junit-jupiter:5.14.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.14.4'
}

jar {
from rootProject.file('LICENSE.md')
from(rootProject.file('additionallicenses/APACHE-LICENSE-2.0.txt')) {
into 'additionallicenses'
}
from 'README.md'
manifest {
attributes('Automatic-Module-Name': 'com.graphqljava.arbitrary')
}
}

tasks.named('sourcesJar') {
from rootProject.file('LICENSE.md')
from(rootProject.file('additionallicenses/APACHE-LICENSE-2.0.txt')) {
into 'additionallicenses'
}
from 'README.md'
}

def dokkaJavadocJar = tasks.register('dokkaJavadocJar', Jar) {
dependsOn tasks.named('dokkaGeneratePublicationJavadoc')
from(tasks.named('dokkaGeneratePublicationJavadoc').flatMap { it.outputDirectory })
archiveClassifier = 'javadoc'
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
maxHeapSize = '1g'
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'
systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
testLogging {
events 'FAILED', 'SKIPPED'
exceptionFormat = 'FULL'
}
}

[11, 17, 21].each { javaVersion ->
tasks.register("testWithJava${javaVersion}", Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
dependsOn tasks.named('testClasses')
}
}

publishing {
publications {
arbitrary(MavenPublication) {
artifactId = 'graphql-java-arbitrary'
from components.java
artifact dokkaJavadocJar
pom {
name = 'graphql-java arbitrary'
description = 'Kotest property generators for graphql-java schemas, documents, and values'
url = 'https://github.com/graphql-java/graphql-java'
scm {
url = 'https://github.com/graphql-java/graphql-java'
connection = 'https://github.com/graphql-java/graphql-java'
developerConnection = 'https://github.com/graphql-java/graphql-java'
}
licenses {
license {
name = 'MIT'
url = 'https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md'
distribution = 'repo'
}
license {
name = 'Apache License 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
developers {
developer {
id = 'andimarek'
name = 'Andreas Marek'
}
}
}
}
}
}

signing {
setRequired { !rootProject.hasProperty('publishToMavenLocal') }
def signingKey = System.env.MAVEN_CENTRAL_PGP_KEY
useInMemoryPgpKeys(signingKey, '')
sign publishing.publications
}

tasks.withType(PublishToMavenRepository).configureEach {
dependsOn tasks.named('build')
}
Loading
Loading