diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index dcb8b0e153..d4da18ac6b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: gradle/actions/wrapper-validation@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '21' distribution: 'corretto' - name: build test and publish run: ./gradlew assemble && ./gradlew check --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace @@ -28,4 +28,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2.20.0 if: always() with: - files: '**/build/test-results/test/TEST-*.xml' + files: | + **/build/test-results/test/TEST-*.xml + **/build/test-results/testWithJava11/TEST-*.xml + **/build/test-results/testWithJava17/TEST-*.xml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 30d202f664..1300df6a17 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: gradle/actions/wrapper-validation@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '21' distribution: 'corretto' - name: build and test run: ./gradlew assemble && ./gradlew check --info --stacktrace diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d630314c0..d48e8cb66d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,10 +19,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: gradle/actions/wrapper-validation@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '21' distribution: 'corretto' - name: build test and publish run: ./gradlew assemble && ./gradlew check --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace diff --git a/build.gradle b/build.gradle index 7e0aeac055..200fd95c6d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,8 @@ -import java.text.SimpleDateFormat +import net.ltgt.gradle.errorprone.CheckSeverity +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import java.text.SimpleDateFormat plugins { id 'java' @@ -12,11 +15,28 @@ plugins { id "io.github.gradle-nexus.publish-plugin" version "2.0.0" id "groovy" id "me.champeau.jmh" version "0.7.3" + id "net.ltgt.errorprone" version '4.2.0' + // + // Kotlin just for tests - not production code + id 'org.jetbrains.kotlin.jvm' version '2.1.21' } java { toolchain { - languageVersion = JavaLanguageVersion.of(11) + languageVersion = JavaLanguageVersion.of(21) // build on 21 - release on 11 + } +} + +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', + ] } } @@ -97,19 +117,15 @@ jar { attributes('Automatic-Module-Name': 'com.graphqljava') } } -tasks.withType(GroovyCompile) { - // Options when compiling Java using the Groovy plugin. - // (Groovy itself defaults to UTF-8 for Groovy code) - options.encoding = 'UTF-8' - groovyOptions.forkOptions.memoryMaximumSize = "4g" -} + dependencies { - implementation 'org.antlr:antlr4-runtime:' + antlrVersion api 'com.graphql-java:java-dataloader:5.0.0' api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion api "org.jspecify:jspecify:1.0.0" - antlr 'org.antlr:antlr4:' + antlrVersion + + implementation 'org.antlr:antlr4-runtime:' + antlrVersion implementation 'com.google.guava:guava:' + guavaVersion + testImplementation group: 'junit', name: 'junit', version: '4.13.2' testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' testImplementation 'net.bytebuddy:byte-buddy:1.17.5' @@ -129,9 +145,17 @@ dependencies { testImplementation 'org.testng:testng:7.11.0' // use for reactive streams test inheritance testImplementation "com.tngtech.archunit:archunit-junit5:1.4.1" + antlr 'org.antlr:antlr4:' + antlrVersion + // this is needed for the idea jmh plugin to work correctly jmh 'org.openjdk.jmh:jmh-core:1.37' jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + + errorprone 'com.uber.nullaway:nullaway:0.12.6' + errorprone 'com.google.errorprone:error_prone_core:2.37.0' + + // just tests - no Kotlin otherwise + testCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' } shadowJar { @@ -218,6 +242,36 @@ compileJava { source file("build/generated-src"), sourceSets.main.java } +tasks.withType(GroovyCompile) { + // Options when compiling Java using the Groovy plugin. + // (Groovy itself defaults to UTF-8 for Groovy code) + options.encoding = 'UTF-8' + sourceCompatibility = '11' + targetCompatibility = '11' + groovyOptions.forkOptions.memoryMaximumSize = "4g" +} + +tasks.withType(JavaCompile) { + options.release = 11 + options.errorprone { + disableAllChecks = true + check("NullAway", CheckSeverity.ERROR) + // + // end state has us with this config turned on - eg all classes + // + //option("NullAway:AnnotatedPackages", "graphql") + option("NullAway:CustomContractAnnotations", "graphql.Contract") + option("NullAway:OnlyNullMarked", "true") + option("NullAway:JSpecifyMode", "true") + } + // Include to disable NullAway on test code + if (name.toLowerCase().contains("test")) { + options.errorprone { + disable("NullAway") + } + } +} + generateGrammarSource { includes = ['Graphql.g4'] maxHeapSize = "64m" @@ -253,6 +307,7 @@ artifacts { List failedTests = [] test { + useJUnitPlatform() testLogging { events "FAILED", "SKIPPED" exceptionFormat = "FULL" @@ -265,6 +320,43 @@ test { } } +tasks.register('testWithJava17', Test) { + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(17) + } + useJUnitPlatform() + testLogging { + events "FAILED", "SKIPPED" + exceptionFormat = "FULL" + } + + afterTest { TestDescriptor descriptor, TestResult result -> + if (result.getFailedTestCount() > 0) { + failedTests.add(descriptor) + } + } + +} +tasks.register('testWithJava11', Test) { + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(11) + } + useJUnitPlatform() + testLogging { + events "FAILED", "SKIPPED" + exceptionFormat = "FULL" + } + + afterTest { TestDescriptor descriptor, TestResult result -> + if (result.getFailedTestCount() > 0) { + failedTests.add(descriptor) + } + } +} +test.dependsOn testWithJava17 +test.dependsOn testWithJava11 + + /* * The gradle.buildFinished callback is deprecated BUT there does not seem to be a decent alternative in gradle 7 * So progress over perfection here @@ -374,6 +466,5 @@ tasks.withType(GenerateModuleMetadata) { enabled = false } -test { - useJUnitPlatform() -} + + diff --git a/src/main/java/graphql/Assert.java b/src/main/java/graphql/Assert.java index 90af6820b8..399cc8c1ef 100644 --- a/src/main/java/graphql/Assert.java +++ b/src/main/java/graphql/Assert.java @@ -1,6 +1,7 @@ package graphql; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.function.Supplier; @@ -20,42 +21,48 @@ public static T assertNotNullWithNPE(T object, Supplier msg) { throw new NullPointerException(msg.get()); } - public static T assertNotNull(T object) { + @Contract("null -> fail") + public static T assertNotNull(@Nullable T object) { if (object != null) { return object; } return throwAssert("Object required to be not null"); } - public static T assertNotNull(T object, Supplier msg) { + @Contract("null,_ -> fail") + public static T assertNotNull(@Nullable T object, Supplier msg) { if (object != null) { return object; } return throwAssert(msg.get()); } - public static T assertNotNull(T object, String constantMsg) { + @Contract("null,_ -> fail") + public static T assertNotNull(@Nullable T object, String constantMsg) { if (object != null) { return object; } return throwAssert(constantMsg); } - public static T assertNotNull(T object, String msgFmt, Object arg1) { + @Contract("null,_,_ -> fail") + public static T assertNotNull(@Nullable T object, String msgFmt, Object arg1) { if (object != null) { return object; } return throwAssert(msgFmt, arg1); } - public static T assertNotNull(T object, String msgFmt, Object arg1, Object arg2) { + @Contract("null,_,_,_ -> fail") + public static T assertNotNull(@Nullable T object, String msgFmt, Object arg1, Object arg2) { if (object != null) { return object; } return throwAssert(msgFmt, arg1, arg2); } - public static T assertNotNull(T object, String msgFmt, Object arg1, Object arg2, Object arg3) { + @Contract("null,_,_,_,_ -> fail") + public static T assertNotNull(@Nullable T object, String msgFmt, Object arg1, Object arg2, Object arg3) { if (object != null) { return object; } @@ -63,28 +70,33 @@ public static T assertNotNull(T object, String msgFmt, Object arg1, Object a } - public static void assertNull(T object, Supplier msg) { + @Contract("!null,_ -> fail") + public static void assertNull(@Nullable T object, Supplier msg) { if (object == null) { return; } throwAssert(msg.get()); } - public static void assertNull(T object) { + @Contract("!null -> fail") + public static void assertNull(@Nullable Object object) { if (object == null) { return; } throwAssert("Object required to be null"); } + @Contract("-> fail") public static T assertNeverCalled() { return throwAssert("Should never been called"); } + @Contract("_,_-> fail") public static T assertShouldNeverHappen(String format, Object... args) { return throwAssert("Internal error: should never happen: %s", format(format, args)); } + @Contract("-> fail") public static T assertShouldNeverHappen() { return throwAssert("Internal error: should never happen"); } @@ -96,6 +108,7 @@ public static Collection assertNotEmpty(Collection collection) { return collection; } + // @Contract("null,_-> fail") public static Collection assertNotEmpty(Collection collection, Supplier msg) { if (collection == null || collection.isEmpty()) { throwAssert(msg.get()); diff --git a/src/main/java/graphql/Contract.java b/src/main/java/graphql/Contract.java new file mode 100644 index 0000000000..93ba900bf2 --- /dev/null +++ b/src/main/java/graphql/Contract.java @@ -0,0 +1,27 @@ +package graphql; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Custom contract annotation used for jspecify and NullAway checks. + * + * This is the same as Spring does: we don't want any additional dependencies, therefore we define our own Contract annotation. + * + * @see Spring Framework Contract + * @see org.jetbrains.annotations.Contract + * @see + * NullAway custom contract annotations + */ +@Documented +@Target(ElementType.METHOD) +@Internal +public @interface Contract { + + /** + * Describing the contract between call arguments and the returned value. + */ + String value() default ""; + +} diff --git a/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategy.java b/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategy.java index 61b0914d31..68da3a3528 100644 --- a/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategy.java +++ b/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategy.java @@ -305,6 +305,7 @@ public void deferredOnFieldValue(String resultKey, FieldValueInfo fieldValueInfo CallStack callStack = getCallStack(parameters); boolean ready = callStack.lock.callLocked(() -> { callStack.deferredFragmentRootFieldsFetched.add(fieldValueInfo); + Assert.assertNotNull(parameters.getDeferredCallContext()); return callStack.deferredFragmentRootFieldsFetched.size() == parameters.getDeferredCallContext().getFields(); }); if (ready) { @@ -367,7 +368,7 @@ private void onFieldValuesInfoDispatchIfNeeded(List fieldValueIn // // thread safety: called with callStack.lock // - private Integer handleSubSelectionFetched(List fieldValueInfos, int subSelectionLevel, CallStack + private @Nullable Integer handleSubSelectionFetched(List fieldValueInfos, int subSelectionLevel, CallStack callStack) { callStack.increaseHappenedOnFieldValueCalls(subSelectionLevel); int expectedOnObjectCalls = getObjectCountForList(fieldValueInfos); @@ -435,7 +436,7 @@ private boolean dispatchIfNeeded(int level, CallStack callStack) { // // thread safety: called with callStack.lock // - private Integer getHighestReadyLevel(int startFrom, CallStack callStack) { + private @Nullable Integer getHighestReadyLevel(int startFrom, CallStack callStack) { int curLevel = callStack.highestReadyLevel; while (true) { if (!checkLevelImpl(curLevel + 1, callStack)) { @@ -508,11 +509,14 @@ void dispatch(int level, CallStack callStack) { } - public void dispatchDLCFImpl(Set resultPathsToDispatch, Integer level, CallStack callStack) { + private void dispatchDLCFImpl(Set resultPathsToDispatch, @Nullable Integer level, CallStack callStack) { // filter out all DataLoaderCFS that are matching the fields we want to dispatch List relevantResultPathWithDataLoader = new ArrayList<>(); - for (ResultPathWithDataLoader resultPathWithDataLoader : callStack.allResultPathWithDataLoader) { + // we need to copy the list because the callStack.allResultPathWithDataLoader can be modified concurrently + // while iterating over it + ArrayList resultPathWithDataLoaders = new ArrayList<>(callStack.allResultPathWithDataLoader); + for (ResultPathWithDataLoader resultPathWithDataLoader : resultPathWithDataLoaders) { if (resultPathsToDispatch.contains(resultPathWithDataLoader.resultPath)) { relevantResultPathWithDataLoader.add(resultPathWithDataLoader); } @@ -579,7 +583,7 @@ public void run() { callStack.batchWindowOfDelayedDataLoaderToDispatch.clear(); callStack.batchWindowOpen = false; }); - dispatchDLCFImpl(resultPathToDispatch.get(), null, callStack); + dispatchDLCFImpl(Assert.assertNotNull(resultPathToDispatch.get()), null, callStack); } } diff --git a/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java b/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java index b15e639492..9447f9cc90 100644 --- a/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java +++ b/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java @@ -22,6 +22,7 @@ import org.dataloader.DataLoader; import org.dataloader.DataLoaderRegistry; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.List; @@ -33,11 +34,15 @@ @Internal @NullMarked public class DataFetchingEnvironmentImpl implements DataFetchingEnvironment { + @Nullable private final Object source; private final Supplier> arguments; + @Nullable private final Object context; private final GraphQLContext graphQLContext; + @Nullable private final Object localContext; + @Nullable private final Object root; private final GraphQLFieldDefinition fieldDefinition; private final MergedField mergedField; @@ -265,6 +270,7 @@ public String toString() { '}'; } + @NullUnmarked public static class Builder { private Object source; diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy index 27f884f2c4..300c3e9371 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy @@ -6,10 +6,11 @@ import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment import graphql.schema.StaticDataFetcher import graphql.schema.idl.RuntimeWiring +import org.awaitility.Awaitility import org.dataloader.BatchLoader -import org.dataloader.DataLoader import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry +import spock.lang.RepeatUntilFailure import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -20,8 +21,8 @@ import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring class Issue1178DataLoaderDispatchTest extends Specification { - public static final int NUM_OF_REPS = 100 + @RepeatUntilFailure(maxAttempts = 100) def "shouldn't dispatch twice in multithreaded env"() { setup: def sdl = """ @@ -67,10 +68,10 @@ class Issue1178DataLoaderDispatchTest extends Specification { def wiring = RuntimeWiring.newRuntimeWiring() .type(newTypeWiring("Query") - .dataFetcher("getTodos", new StaticDataFetcher([[id: '1'], [id: '2'], [id: '3'], [id: '4'], [id: '5']]))) + .dataFetcher("getTodos", new StaticDataFetcher([[id: '1'], [id: '2'], [id: '3'], [id: '4'], [id: '5']]))) .type(newTypeWiring("Todo") - .dataFetcher("related", relatedDf) - .dataFetcher("related2", relatedDf2)) + .dataFetcher("related", relatedDf) + .dataFetcher("related2", relatedDf2)) .build() @@ -79,11 +80,10 @@ class Issue1178DataLoaderDispatchTest extends Specification { .build() then: "execution shouldn't error" - for (int i = 0; i < NUM_OF_REPS; i++) { - def result = graphql.execute(ExecutionInput.newExecutionInput() - .graphQLContext([(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING): enableDataLoaderChaining]) - .dataLoaderRegistry(dataLoaderRegistry) - .query(""" + def ei = ExecutionInput.newExecutionInput() + .graphQLContext([(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING): enableDataLoaderChaining]) + .dataLoaderRegistry(dataLoaderRegistry) + .query(""" query { getTodos { __typename id related { id __typename @@ -115,9 +115,10 @@ class Issue1178DataLoaderDispatchTest extends Specification { } } } - }""").build()) - assert result.errors.empty - } + }""").build() + def resultCF = graphql.executeAsync(ei) + Awaitility.await().until { resultCF.isDone() } + assert resultCF.get().errors.empty where: enableDataLoaderChaining << [true, false] diff --git a/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy index 7dd5e4cb93..9ad4df39ef 100644 --- a/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy +++ b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy @@ -4,6 +4,7 @@ import graphql.Scalars import graphql.schema.GraphQLFieldDefinition import graphql.schema.PropertyDataFetcher import graphql.util.javac.DynamicJavacSupport +import spock.lang.IgnoreIf import spock.lang.Specification class LambdaFetchingSupportTest extends Specification { @@ -150,6 +151,7 @@ class LambdaFetchingSupportTest extends Specification { return GraphQLFieldDefinition.newFieldDefinition().name(fldName).type(Scalars.GraphQLString).build() } + @IgnoreIf({ System.getProperty("java.version").split('\\.')[0] as Integer > 11 }) def "different class loaders induce certain behaviours"() { String sourceCode = ''' package com.dynamic; diff --git a/src/test/groovy/graphql/util/AnonymizerTest.groovy b/src/test/groovy/graphql/util/AnonymizerTest.groovy index 48f5ecde40..6865879f55 100644 --- a/src/test/groovy/graphql/util/AnonymizerTest.groovy +++ b/src/test/groovy/graphql/util/AnonymizerTest.groovy @@ -722,46 +722,45 @@ type Object1 { .print(result) then: - newSchema == """\ - schema @Directive1(argument1 : "stringValue1"){ - query: Object1 - } - - directive @Directive1(argument1: String! = "stringValue4") repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION - - "Marks the field, argument, input field or enum value as deprecated" - directive @deprecated( - "The reason for the deprecation" - reason: String! = "No longer supported" - ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION - - interface Interface1 @Directive1(argument1 : "stringValue12") { - field2: String - field3: Enum1 - } - - union Union1 @Directive1(argument1 : "stringValue21") = Object2 - - type Object1 @Directive1(argument1 : "stringValue8") { - field1: Interface1 @Directive1(argument1 : "stringValue9") - field4: Union1 @deprecated - } - - type Object2 implements Interface1 { - field2: String - field3: Enum1 - field5(argument2: InputObject1): String - } - - enum Enum1 @Directive1(argument1 : "stringValue15") { - EnumValue1 @Directive1(argument1 : "stringValue18") - EnumValue2 - } - - input InputObject1 @Directive1(argument1 : "stringValue24") { - inputField1: Int @Directive1(argument1 : "stringValue27") - } - """.stripIndent() + newSchema == """schema @Directive1(argument1 : "stringValue1"){ + query: Object1 +} + +directive @Directive1(argument1: String! = "stringValue4") repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +"Marks the field, argument, input field or enum value as deprecated" +directive @deprecated( + "The reason for the deprecation" + reason: String! = "No longer supported" + ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION + +interface Interface1 @Directive1(argument1 : "stringValue12") { + field2: String + field3: Enum1 +} + +union Union1 @Directive1(argument1 : "stringValue21") = Object2 + +type Object1 @Directive1(argument1 : "stringValue8") { + field1: Interface1 @Directive1(argument1 : "stringValue9") + field4: Union1 @deprecated +} + +type Object2 implements Interface1 { + field2: String + field3: Enum1 + field5(argument2: InputObject1): String +} + +enum Enum1 @Directive1(argument1 : "stringValue15") { + EnumValue1 @Directive1(argument1 : "stringValue18") + EnumValue2 +} + +input InputObject1 @Directive1(argument1 : "stringValue24") { + inputField1: Int @Directive1(argument1 : "stringValue27") +} +""" } def "query with directives"() { diff --git a/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy b/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy index 4b0e278487..4002b14ab4 100644 --- a/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy @@ -6,6 +6,7 @@ import graphql.validation.SpecValidationSchema import graphql.validation.ValidationError import graphql.validation.ValidationErrorType import graphql.validation.Validator +import org.codehaus.groovy.runtime.StringGroovyMethods import spock.lang.Specification class ExecutableDefinitionsTest extends Specification { @@ -46,7 +47,7 @@ class ExecutableDefinitionsTest extends Specification { } def 'Executable Definitions with type definition'() { - def query = """ + def query = StringGroovyMethods.stripIndent(""" query Foo { dog { name @@ -60,7 +61,7 @@ class ExecutableDefinitionsTest extends Specification { extend type Dog { color: String } - """.stripIndent() + """) when: def validationErrors = validate(query) @@ -76,7 +77,7 @@ class ExecutableDefinitionsTest extends Specification { } def 'Executable Definitions with schema definition'() { - def query = """ + def query = StringGroovyMethods.stripIndent(""" schema { query: QueryRoot } @@ -84,7 +85,7 @@ class ExecutableDefinitionsTest extends Specification { type QueryRoot { test: String } - """.stripIndent() + """) when: def validationErrors = validate(query) @@ -117,9 +118,9 @@ class ExecutableDefinitionsTest extends Specification { } def 'Executable Definitions with no directive definition'() { - def query = """ + def query = StringGroovyMethods.stripIndent(""" directive @nope on INPUT_OBJECT - """.stripIndent() + """) when: def document = new Parser().parseDocument(query) def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH)