From da17c4ddef607d7cb953a48e634a31df606c680f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:59:04 +0000 Subject: [PATCH 1/5] Update plugin org.asciidoctor.jvm.convert to v4.0.4 --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 6d94396d61..f6dcb99ae2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,7 +9,7 @@ pluginManagement { plugins { id "com.gradle.develocity" version "3.19" id "com.gradle.common-custom-user-data-gradle-plugin" version "2.0.2" - id "org.asciidoctor.jvm.convert" version "4.0.3" + id "org.asciidoctor.jvm.convert" version "4.0.4" id "net.nemerosa.versioning" version "3.1.0" id "io.github.gradle-nexus.publish-plugin" version "2.0.0" id "com.github.ben-manes.versions" version "0.51.0" From 01f3c4e4eca97dc4707f9ff9aad703fa3aa04527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Thu, 26 Dec 2024 17:05:11 +0100 Subject: [PATCH 2/5] Fix compile error with single explicit assert in switch expression branch (#2033) Fixes #1845 --- docs/release_notes.adoc | 3 ++- .../compiler/AbstractDeepBlockRewriter.java | 10 ++++++++++ .../smoke/condition/ConditionG4Spec.groovy | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc index 7f78d7aa30..6f3cd71548 100644 --- a/docs/release_notes.adoc +++ b/docs/release_notes.adoc @@ -28,8 +28,9 @@ include::include.adoc[] * Fix mocking issue with the ByteBuddy MockMaker when using multiple classloaders in Java 21 spockIssue:2017[] * Fix mocking of final classes via `@SpringBean` and `@SpringSpy` spockIssue:1960[] * Size of data providers is no longer calculated multiple times but only once -* Fix exception when using `@RepeatUntilFailure` with a data provider with unknown iteration amount. spockPull:2031[] +* Fix exception when using `@RepeatUntilFailure` with a data provider with unknown iteration amount spockPull:2031[] * Clarified documentation about data providers and `size()` calls spockIssue:2022[] +* Fix compile error with single explicit assert in switch expression branch spockIssue:1845[] == 2.4-M4 (2024-03-21) diff --git a/spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java b/spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java index f4129615f3..9a8c1ba184 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java +++ b/spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java @@ -149,6 +149,16 @@ public final void visitClosureExpression(ClosureExpression expr) { currSpecialMethodCall = NoSpecialMethodCall.INSTANCE; // unrelated closure terminates currSpecialMethodCall scope } try { + Statement code = expr.getCode(); + // the code of a closure is not necessarily a block statement but could for example + // also be a single assert statement in cases like `case 3 -> assert 1 == 1` + // to uniformly treat the closure code as block statement and later be able to + // add statements to it, wrap non-block statements in a block statement here + if (!(code instanceof BlockStatement)) { + BlockStatement block = new BlockStatement(); + block.addStatement(code); + expr.setCode(block); + } doVisitClosureExpression(expr); } finally { currClosure = oldClosure; diff --git a/spock-specs/src/test-groovy-ge-4.0/groovy/org/spockframework/smoke/condition/ConditionG4Spec.groovy b/spock-specs/src/test-groovy-ge-4.0/groovy/org/spockframework/smoke/condition/ConditionG4Spec.groovy index b598d8a54e..71a228a4cc 100644 --- a/spock-specs/src/test-groovy-ge-4.0/groovy/org/spockframework/smoke/condition/ConditionG4Spec.groovy +++ b/spock-specs/src/test-groovy-ge-4.0/groovy/org/spockframework/smoke/condition/ConditionG4Spec.groovy @@ -13,4 +13,14 @@ class ConditionG4Spec extends Specification { (0..<5) == [0, 1, 2, 3, 4] (0<..<5) == [1, 2, 3, 4] } + + @Issue("https://github.com/spockframework/spock/issues/1845") + def "explicit assert in switch expression"() { + expect: + def b = 3 + !!switch (b) { + case 3 -> assert 1 == 1 + default -> assert 1 == 1 + } + } } From 2c4e38cf260527164575e2fc6ab59714a9c4ea64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 27 Dec 2024 01:28:33 +0100 Subject: [PATCH 3/5] Add BlockListener support... (#1575) This feature allows extension authors to register an `IBlockListener` for a feature to observe the execution of a feature in more detail. This surfaces some of Spock's idiosyncrasies, for example interaction assertions are actually setup right before entering the preceding `when`-block as well as being evaluated on leaving the `when`-block before actually entering the `then`-block. The only valid block description is a constant `String`, although some users mistakenly try to use a dynamic `GString`. Using anything other than a `String`, will be treated as a separate statement and thus ignored. Expose `IErrorContext` in `ErrorInfo` to provide more information in `IRunListener.error(ErrorInfo)` about where the error happened. fixes #538 fixes #111 --- docs/extensions.adoc | 23 +++ docs/release_notes.adoc | 1 + .../spockframework/compiler/AstNodeCache.java | 13 ++ .../org/spockframework/compiler/AstUtil.java | 9 ++ .../compiler/SpecAnnotator.java | 10 +- .../spockframework/compiler/SpecParser.java | 6 + .../spockframework/compiler/SpecRewriter.java | 117 +++++++++++---- .../spockframework/compiler/SpockNames.java | 6 +- .../compiler/model/AnonymousBlock.java | 5 + .../spockframework/compiler/model/Block.java | 21 +++ .../compiler/model/BlockParseInfo.java | 63 ++++++++ .../lang/ISpecificationContext.java | 9 +- .../runtime/DataIteratorFactory.java | 32 ++-- .../spockframework/runtime/ErrorContext.java | 70 +++++++++ .../spockframework/runtime/IRunListener.java | 1 + .../runtime/MasterRunSupervisor.java | 4 +- .../runtime/PlatformSpecRunner.java | 7 +- .../runtime/SpecInfoBuilder.java | 5 +- .../runtime/SpecificationContext.java | 31 +++- .../spockframework/runtime/SpockRuntime.java | 28 ++++ .../runtime/extension/IBlockListener.java | 57 ++++++++ .../runtime/model/BlockInfo.java | 18 +++ .../runtime/model/ErrorInfo.java | 22 ++- .../runtime/model/FeatureInfo.java | 21 ++- .../runtime/model/IErrorContext.java | 40 +++++ .../runtime/AsyncRunListenerSpec.groovy | 2 +- .../runtime/BlockListenerSpec.groovy | 81 ++++++++++ .../runtime/RunListenerSpec.groovy | 138 +++++++++++++++++- .../spockframework/smoke/ast/BlocksAst.groovy | 88 +++++++++++ .../smoke/ast/mock/MocksAstSpec.groovy | 44 ++++++ .../parameterization/DataProviders.groovy | 61 ++------ ...used_in_AST_transformation_Groovy____3.txt | 65 ++++++++- ...used_in_AST_transformation_Groovy____4.txt | 91 +++++++++++- ...ceFeatureBody_can_render_everything.groovy | 2 + ...n_render_everything__Groovy_4_0_2__.groovy | 2 + ...thods_and_its_annotation_by_default.groovy | 2 + ...ers_and_their_annotation_by_default.groovy | 2 + ...servable_blocks_with_GString_labels.groovy | 40 +++++ ...observable_blocks_with_empty_labels.groovy | 50 +++++++ ...vable_blocks_with_labels_and_blocks.groovy | 52 +++++++ ...rite_keeps_correct_method_reference.groovy | 13 ++ ...hod_reference_for_multi_assignments.groovy | 13 ++ .../DataAstSpec/multi_parameterization.groovy | 2 + .../nested_multi_parameterization.groovy | 2 + ...ith__separators_can_be_combined-[0].groovy | 2 + ...ith__separators_can_be_combined-[1].groovy | 2 + ...ith__separators_can_be_combined-[2].groovy | 2 + ...filter_block_becomes_its_own_method.groovy | 2 + ...tionsAsSet_is_transformed_correctly.groovy | 4 + ...InAnyOrder_is_transformed_correctly.groovy | 4 + ...onditions_are_transformed_correctly.groovy | 4 + ...onditions_are_transformed_correctly.groovy | 4 + ...rite_keeps_correct_method_reference.groovy | 4 + ...hod_reference_for_multi_assignments.groovy | 4 + .../MocksAstSpec/simple_interaction.groovy | 24 +++ ...er_variable_in_data_provider_method.groovy | 30 ++++ ...r_variable_in_data_processor_method.groovy | 30 ++++ 57 files changed, 1360 insertions(+), 125 deletions(-) create mode 100644 spock-core/src/main/java/org/spockframework/runtime/ErrorContext.java create mode 100644 spock-core/src/main/java/org/spockframework/runtime/extension/IBlockListener.java create mode 100644 spock-core/src/main/java/org/spockframework/runtime/model/IErrorContext.java create mode 100644 spock-specs/src/test/groovy/org/spockframework/runtime/BlockListenerSpec.groovy create mode 100644 spock-specs/src/test/groovy/org/spockframework/smoke/ast/BlocksAst.groovy create mode 100644 spock-specs/src/test/groovy/org/spockframework/smoke/ast/mock/MocksAstSpec.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_GString_labels.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_empty_labels.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_labels_and_blocks.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/mock/MocksAstSpec/simple_interaction.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_provider_with_asserting_closure_produces_error_rethrower_variable_in_data_provider_method.groovy create mode 100644 spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_variable_with_asserting_closure_produces_error_rethrower_variable_in_data_processor_method.groovy diff --git a/docs/extensions.adoc b/docs/extensions.adoc index cf4eb85155..781b3ca5f8 100644 --- a/docs/extensions.adoc +++ b/docs/extensions.adoc @@ -1380,3 +1380,26 @@ It is primarily for framework developers who want to provide a default value for Or users of a framework that doesn't provide default values for their special types. If you want to change the default response behavior for `Stub` have a look at <> and how to use your own `org.spockframework.mock.IDefaultResponse`. + +=== Listeners + +Extensions can register listeners to receive notifications about the progress of the test run. +These listeners are intended to be used for reporting, logging, or other monitoring purposes. +They are not intended to modify the test run in any way. +You can register the same listener instance on multiple specifications or features. +Please consult the JavaDoc of the respective listener interfaces for more information. + +==== `IRunListener` + +The `org.spockframework.runtime.IRunListener` can be registered via `SpecInfo.addListener(IRunListener)` and will receive notifications about the progress of the test run of a single specification. + +[#block-listener] +==== `IBlockListener` + +The `org.spockframework.runtime.extension.IBlockListener` can be registered on a feature via, `FeatureInfo.addBlockListener(IBlockListener)` and will receive notifications about the progress of the feature. + +It will be called once when entering a block (`blockEntered`) and once when exiting a block (`blockExited`). + +When an exception is thrown in a block, the `blockExited` will not be called for that block. +The failed block will be part of the `ErrorContext` in `ErrorInfo` that is passed to `IRunListener.error(ErrorInfo)`. +If a `cleanup` block is present the cleanup block listener methods will still be called. diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc index 6f3cd71548..b489c3b179 100644 --- a/docs/release_notes.adoc +++ b/docs/release_notes.adoc @@ -9,6 +9,7 @@ include::include.adoc[] * Add support for combining two or more data providers using cartesian product spockIssue:1062[] * Add support for a `filter` block after a `where` block to filter out unwanted iterations spockPull:1927[] +* Add <> extension point to listen to block execution events within feature methods spockPull:1575[] === Misc diff --git a/spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java b/spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java index 5c3990af3f..bbe9395a20 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java +++ b/spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java @@ -46,6 +46,7 @@ public class AstNodeCache { public final ClassNode SpecificationContext = ClassHelper.makeWithoutCaching(SpecificationContext.class); public final ClassNode DataVariableMultiplication = ClassHelper.makeWithoutCaching(DataVariableMultiplication.class); public final ClassNode DataVariableMultiplicationFactor = ClassHelper.makeWithoutCaching(DataVariableMultiplicationFactor.class); + public final ClassNode BlockInfo = ClassHelper.makeWithoutCaching(BlockInfo.class); public final MethodNode SpecInternals_GetSpecificationContext = SpecInternals.getDeclaredMethods(Identifiers.GET_SPECIFICATION_CONTEXT).get(0); @@ -71,6 +72,12 @@ public class AstNodeCache { public final MethodNode SpockRuntime_DespreadList = SpockRuntime.getDeclaredMethods(org.spockframework.runtime.SpockRuntime.DESPREAD_LIST).get(0); + public final MethodNode SpockRuntime_CallBlockEntered = + SpockRuntime.getDeclaredMethods(org.spockframework.runtime.SpockRuntime.CALL_BLOCK_ENTERED).get(0); + + public final MethodNode SpockRuntime_CallBlockExited = + SpockRuntime.getDeclaredMethods(org.spockframework.runtime.SpockRuntime.CALL_BLOCK_EXITED).get(0); + public final MethodNode ValueRecorder_Reset = ValueRecorder.getDeclaredMethods(org.spockframework.runtime.ValueRecorder.RESET).get(0); @@ -107,6 +114,12 @@ public class AstNodeCache { public final MethodNode SpecificationContext_GetSharedInstance = SpecificationContext.getDeclaredMethods(org.spockframework.runtime.SpecificationContext.GET_SHARED_INSTANCE).get(0); + public final MethodNode SpecificationContext_GetCurrentBlock = + SpecificationContext.getDeclaredMethods(org.spockframework.runtime.SpecificationContext.GET_CURRENT_BLOCK).get(0); + + public final MethodNode SpecificationContext_SetCurrentBlock = + SpecificationContext.getDeclaredMethods(org.spockframework.runtime.SpecificationContext.SET_CURRENT_BLOCK).get(0); + public final MethodNode List_Get = ClassHelper.LIST_TYPE.getDeclaredMethods("get").get(0); diff --git a/spock-core/src/main/java/org/spockframework/compiler/AstUtil.java b/spock-core/src/main/java/org/spockframework/compiler/AstUtil.java index 61c1287c40..14554eacfe 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/AstUtil.java +++ b/spock-core/src/main/java/org/spockframework/compiler/AstUtil.java @@ -17,6 +17,7 @@ package org.spockframework.compiler; import org.codehaus.groovy.syntax.Token; +import org.codehaus.groovy.syntax.Types; import org.spockframework.lang.Wildcard; import org.spockframework.util.*; import spock.lang.Specification; @@ -390,4 +391,12 @@ public static ConstantExpression primitiveConstExpression(int value) { public static ConstantExpression primitiveConstExpression(boolean value) { return new ConstantExpression(value, true); } + + public static BinaryExpression createVariableIsNotNullExpression(VariableExpression var) { + return new BinaryExpression( + var, + Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1), + new ConstantExpression(null)); + } + } diff --git a/spock-core/src/main/java/org/spockframework/compiler/SpecAnnotator.java b/spock-core/src/main/java/org/spockframework/compiler/SpecAnnotator.java index 877203e093..3a7a180112 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/SpecAnnotator.java +++ b/spock-core/src/main/java/org/spockframework/compiler/SpecAnnotator.java @@ -30,6 +30,7 @@ import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; +import org.spockframework.util.Assert; import static java.util.stream.Collectors.*; import static org.spockframework.compiler.AstUtil.*; @@ -190,8 +191,9 @@ private void addFeatureMetadata(FeatureMethod feature) { ann.setMember(FeatureMetadata.BLOCKS, blockAnnElems = new ListExpression()); ListExpression paramNames = new ListExpression(); - for (Parameter param : feature.getAst().getParameters()) + for (Parameter param : feature.getAst().getParameters()) { paramNames.addExpression(new ConstantExpression(param.getName())); + } ann.setMember(FeatureMetadata.PARAMETER_NAMES, paramNames); feature.getAst().addAnnotation(ann); @@ -202,9 +204,13 @@ private void addBlockMetadata(Block block, BlockKind kind) { blockAnn.setMember(BlockMetadata.KIND, new PropertyExpression( new ClassExpression(nodeCache.BlockKind), kind.name())); ListExpression textExprs = new ListExpression(); - for (String text : block.getDescriptions()) + for (String text : block.getDescriptions()) { textExprs.addExpression(new ConstantExpression(text)); + } blockAnn.setMember(BlockMetadata.TEXTS, textExprs); + int index = blockAnnElems.getExpressions().size(); + Assert.that(index == block.getBlockMetaDataIndex(), + () -> kind + " block mismatch of index: " + index + ", block.getBlockMetaDataIndex(): " + block.getBlockMetaDataIndex()); blockAnnElems.addExpression(new AnnotationConstantExpression(blockAnn)); } diff --git a/spock-core/src/main/java/org/spockframework/compiler/SpecParser.java b/spock-core/src/main/java/org/spockframework/compiler/SpecParser.java index f7a32ffb8b..f26d76efab 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/SpecParser.java +++ b/spock-core/src/main/java/org/spockframework/compiler/SpecParser.java @@ -200,6 +200,12 @@ private void buildBlocks(Method method) throws InvalidSpecCompileException { checkIsValidSuccessor(method, BlockParseInfo.METHOD_END, method.getAst().getLastLineNumber(), method.getAst().getLastColumnNumber()); + // set the block metadata index for each block this must be equal to the index of the block in the @BlockMetadata annotation + int i = -1; + for (Block block : method.getBlocks()) { + if(!block.hasBlockMetadata()) continue; + block.setBlockMetaDataIndex(++i); + } // now that statements have been copied to blocks, the original statement // list is cleared; statements will be copied back after rewriting is done stats.clear(); diff --git a/spock-core/src/main/java/org/spockframework/compiler/SpecRewriter.java b/spock-core/src/main/java/org/spockframework/compiler/SpecRewriter.java index 3b02908707..254d1d04ae 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/SpecRewriter.java +++ b/spock-core/src/main/java/org/spockframework/compiler/SpecRewriter.java @@ -16,19 +16,22 @@ package org.spockframework.compiler; -import org.spockframework.compiler.model.*; -import org.spockframework.runtime.SpockException; -import org.spockframework.util.*; - -import java.lang.reflect.InvocationTargetException; -import java.util.*; - import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.*; import org.codehaus.groovy.runtime.MetaClassHelper; -import org.codehaus.groovy.syntax.*; +import org.codehaus.groovy.syntax.Token; +import org.codehaus.groovy.syntax.Types; +import org.jetbrains.annotations.NotNull; import org.objectweb.asm.Opcodes; +import org.spockframework.compiler.model.*; +import org.spockframework.runtime.SpockException; +import org.spockframework.util.InternalIdentifiers; +import org.spockframework.util.ObjectUtil; +import org.spockframework.util.ReflectionUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -159,7 +162,7 @@ private void createFinalFieldGetter(Field field) { private void createSharedFieldSetter(Field field) { String setterName = "set" + MetaClassHelper.capitalize(field.getName()); - Parameter[] params = new Parameter[] { new Parameter(field.getAst().getType(), "$spock_value") }; + Parameter[] params = new Parameter[] { new Parameter(field.getAst().getType(), SpockNames.SPOCK_VALUE) }; MethodNode setter = spec.getAst().getMethod(setterName, params); if (setter != null) { errorReporter.error(field.getAst(), @@ -180,7 +183,7 @@ private void createSharedFieldSetter(Field field) { // use internal name new ConstantExpression(field.getAst().getName())), Token.newSymbol(Types.ASSIGN, -1, -1), - new VariableExpression("$spock_value")))); + new VariableExpression(SpockNames.SPOCK_VALUE)))); setter.setSourcePosition(field.getAst()); spec.getAst().addMethod(setter); @@ -390,13 +393,20 @@ private void handleWhereBlock(Method method) { public void visitMethodAgain(Method method) { this.block = null; - if (!movedStatsBackToMethod) - for (Block b : method.getBlocks()) + if (!movedStatsBackToMethod) { + for (Block b : method.getBlocks()) { + // This will only run if there was no 'cleanup' block in the method. + // Otherwise, the blocks have already been copied to try block by visitCleanupBlock. + // We need to run as late as possible, so we'll have to do the handling here and in visitCleanupBlock. + addBlockListeners(b); method.getStatements().addAll(b.getAst()); + } + } // for global required interactions - if (method instanceof FeatureMethod) + if (method instanceof FeatureMethod) { method.getStatements().add(createMockControllerCall(nodeCache.MockController_LeaveScope)); + } if (methodHasCondition) { defineValueRecorder(method.getStatements(), ""); @@ -406,6 +416,56 @@ public void visitMethodAgain(Method method) { } } + + private void addBlockListeners(Block block) { + BlockParseInfo blockType = block.getParseInfo(); + if (!blockType.isSupportingBlockListeners()) return; + + // SpockRuntime.callBlockEntered(getSpecificationContext(), blockMetadataIndex) + MethodCallExpression blockEnteredCall = createBlockListenerCall(block, blockType, nodeCache.SpockRuntime_CallBlockEntered); + // SpockRuntime.callBlockExited(getSpecificationContext(), blockMetadataIndex) + MethodCallExpression blockExitedCall = createBlockListenerCall(block, blockType, nodeCache.SpockRuntime_CallBlockExited); + + block.getAst().add(0, new ExpressionStatement(blockEnteredCall)); + if (blockType == BlockParseInfo.CLEANUP) { + // In case of a cleanup block we need store a reference of the previously `currentBlock` in case that an exception occurred + // and restore it at the end of the cleanup block, so that the correct `BlockInfo` is available for the `IErrorContext`. + // The restoration happens in the `finally` statement created by `createCleanupTryCatch`. + VariableExpression failedBlock = new VariableExpression(SpockNames.FAILED_BLOCK, nodeCache.BlockInfo); + block.getAst().add(0, ifThrowableIsNotNull(storeFailedBlock(failedBlock))); + } + block.getAst().add(new ExpressionStatement(blockExitedCall)); + } + + private @NotNull Statement storeFailedBlock(VariableExpression failedBlock) { + MethodCallExpression getCurrentBlock = createDirectMethodCall(getSpecificationContext(), nodeCache.SpecificationContext_GetCurrentBlock, ArgumentListExpression.EMPTY_ARGUMENTS); + return new ExpressionStatement(new BinaryExpression(failedBlock, Token.newSymbol(Types.ASSIGN, -1, -1), getCurrentBlock)); + } + + private @NotNull Statement restoreFailedBlock(VariableExpression failedBlock) { + return new ExpressionStatement(createDirectMethodCall(new CastExpression(nodeCache.SpecificationContext, getSpecificationContext()), nodeCache.SpecificationContext_SetCurrentBlock, new ArgumentListExpression(failedBlock))); + } + + private IfStatement ifThrowableIsNotNull(Statement statement) { + return new IfStatement( + // if ($spock_feature_throwable != null) + new BooleanExpression(AstUtil.createVariableIsNotNullExpression(new VariableExpression(SpockNames.SPOCK_FEATURE_THROWABLE, nodeCache.Throwable))), + statement, + EmptyStatement.INSTANCE + ); + } + + private MethodCallExpression createBlockListenerCall(Block block, BlockParseInfo blockType, MethodNode blockListenerMethod) { + if (block.getBlockMetaDataIndex() < 0) throw new SpockException("Block metadata index not set: " + block); + return createDirectMethodCall( + new ClassExpression(nodeCache.SpockRuntime), + blockListenerMethod, + new ArgumentListExpression( + getSpecificationContext(), + new ConstantExpression(block.getBlockMetaDataIndex(), true) + )); + } + @Override public void visitAnyBlock(Block block) { this.block = block; @@ -484,12 +544,15 @@ private Statement createMockControllerCall(MethodNode method) { @Override public void visitCleanupBlock(CleanupBlock block) { for (Block b : method.getBlocks()) { + // call addBlockListeners() here, as this method will already copy the contents of the blocks, + // so we need to transform the block listeners here as they won't be copied in visitMethodAgain where we normally add them + addBlockListeners(b); if (b == block) break; moveVariableDeclarations(b.getAst(), method.getStatements()); } VariableExpression featureThrowableVar = - new VariableExpression("$spock_feature_throwable", nodeCache.Throwable); + new VariableExpression(SpockNames.SPOCK_FEATURE_THROWABLE, nodeCache.Throwable); method.getStatements().add(createVariableDeclarationStatement(featureThrowableVar)); List featureStats = new ArrayList<>(); @@ -499,9 +562,10 @@ public void visitCleanupBlock(CleanupBlock block) { } CatchStatement featureCatchStat = createThrowableAssignmentAndRethrowCatchStatement(featureThrowableVar); - - List cleanupStats = singletonList( - createCleanupTryCatch(block, featureThrowableVar)); + VariableExpression failedBlock = new VariableExpression(SpockNames.FAILED_BLOCK, nodeCache.BlockInfo); + List cleanupStats = asList( + new ExpressionStatement(new DeclarationExpression(failedBlock, Token.newSymbol(Types.ASSIGN, -1, -1), ConstantExpression.NULL)), + createCleanupTryCatch(block, featureThrowableVar, failedBlock)); TryCatchStatement tryFinally = new TryCatchStatement( @@ -517,13 +581,6 @@ public void visitCleanupBlock(CleanupBlock block) { movedStatsBackToMethod = true; } - private BinaryExpression createVariableNotNullExpression(VariableExpression var) { - return new BinaryExpression( - new VariableExpression(var), - Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1), - new ConstantExpression(null)); - } - private Statement createVariableDeclarationStatement(VariableExpression var) { DeclarationExpression throwableDecl = new DeclarationExpression( @@ -534,13 +591,13 @@ private Statement createVariableDeclarationStatement(VariableExpression var) { return new ExpressionStatement(throwableDecl); } - private TryCatchStatement createCleanupTryCatch(CleanupBlock block, VariableExpression featureThrowableVar) { + private TryCatchStatement createCleanupTryCatch(CleanupBlock block, VariableExpression featureThrowableVar, VariableExpression failedBlock) { List cleanupStats = new ArrayList<>(block.getAst()); - TryCatchStatement tryCatchStat = new TryCatchStatement( new BlockStatement(cleanupStats, null), - EmptyStatement.INSTANCE); + ifThrowableIsNotNull(restoreFailedBlock(failedBlock)) + ); tryCatchStat.addCatch(createHandleSuppressedThrowableStatement(featureThrowableVar)); @@ -548,7 +605,7 @@ private TryCatchStatement createCleanupTryCatch(CleanupBlock block, VariableExpr } private CatchStatement createThrowableAssignmentAndRethrowCatchStatement(VariableExpression assignmentVar) { - Parameter catchParameter = new Parameter(nodeCache.Throwable, "$spock_tmp_throwable"); + Parameter catchParameter = new Parameter(nodeCache.Throwable, SpockNames.SPOCK_TMP_THROWABLE); BinaryExpression assignThrowableExpr = new BinaryExpression( @@ -565,9 +622,9 @@ private CatchStatement createThrowableAssignmentAndRethrowCatchStatement(Variabl } private CatchStatement createHandleSuppressedThrowableStatement(VariableExpression featureThrowableVar) { - Parameter catchParameter = new Parameter(nodeCache.Throwable, "$spock_tmp_throwable"); + Parameter catchParameter = new Parameter(nodeCache.Throwable, SpockNames.SPOCK_TMP_THROWABLE); - BinaryExpression featureThrowableNotNullExpr = createVariableNotNullExpression(featureThrowableVar); + BinaryExpression featureThrowableNotNullExpr = AstUtil.createVariableIsNotNullExpression(featureThrowableVar); List addSuppressedStats = singletonList(new ExpressionStatement( diff --git a/spock-core/src/main/java/org/spockframework/compiler/SpockNames.java b/spock-core/src/main/java/org/spockframework/compiler/SpockNames.java index aa4edf9024..5353720184 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/SpockNames.java +++ b/spock-core/src/main/java/org/spockframework/compiler/SpockNames.java @@ -1,8 +1,12 @@ package org.spockframework.compiler; public class SpockNames { - public static final String VALUE_RECORDER = "$spock_valueRecorder"; public static final String ERROR_COLLECTOR = "$spock_errorCollector"; + public static final String FAILED_BLOCK = "$spock_failedBlock"; public static final String OLD_VALUE = "$spock_oldValue"; public static final String SPOCK_EX = "$spock_ex"; + public static final String SPOCK_FEATURE_THROWABLE = "$spock_feature_throwable"; + public static final String SPOCK_TMP_THROWABLE = "$spock_tmp_throwable"; + public static final String SPOCK_VALUE = "$spock_value"; + public static final String VALUE_RECORDER = "$spock_valueRecorder"; } diff --git a/spock-core/src/main/java/org/spockframework/compiler/model/AnonymousBlock.java b/spock-core/src/main/java/org/spockframework/compiler/model/AnonymousBlock.java index fd1a7528c6..94c5224938 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/model/AnonymousBlock.java +++ b/spock-core/src/main/java/org/spockframework/compiler/model/AnonymousBlock.java @@ -37,4 +37,9 @@ public void accept(ISpecVisitor visitor) throws Exception { public BlockParseInfo getParseInfo() { return BlockParseInfo.ANONYMOUS; } + + @Override + public boolean hasBlockMetadata() { + return false; + } } diff --git a/spock-core/src/main/java/org/spockframework/compiler/model/Block.java b/spock-core/src/main/java/org/spockframework/compiler/model/Block.java index 49ba575512..1ee316b772 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/model/Block.java +++ b/spock-core/src/main/java/org/spockframework/compiler/model/Block.java @@ -31,6 +31,7 @@ public abstract class Block extends Node> { private final List descriptions = new ArrayList<>(3); private Block prev; private Block next; + private int blockMetaDataIndex = -1; public Block(Method parent) { super(parent, new ArrayList<>()); @@ -80,5 +81,25 @@ public boolean isFirstInChain() { return isFirst() || getClass() != prev.getClass(); } + public void setBlockMetaDataIndex(int blockMetaDataIndex) { + this.blockMetaDataIndex = blockMetaDataIndex; + } + + public int getBlockMetaDataIndex() { + return blockMetaDataIndex; + } + + /** + * Returns whether this block will be written to the {@link org.spockframework.runtime.model.BlockMetadata}. + */ + public boolean hasBlockMetadata() { + return true; + } + public abstract BlockParseInfo getParseInfo(); + + @Override + public String toString() { + return "Block kind: " + getClass().getSimpleName() + ", descriptions: " + descriptions; + } } diff --git a/spock-core/src/main/java/org/spockframework/compiler/model/BlockParseInfo.java b/spock-core/src/main/java/org/spockframework/compiler/model/BlockParseInfo.java index ebf8fc0288..d57ff267b6 100644 --- a/spock-core/src/main/java/org/spockframework/compiler/model/BlockParseInfo.java +++ b/spock-core/src/main/java/org/spockframework/compiler/model/BlockParseInfo.java @@ -18,6 +18,8 @@ import java.util.*; +import org.spockframework.util.InternalSpockError; + /** * * @author Peter Niederwieser @@ -32,6 +34,10 @@ public EnumSet getSuccessors(Method method) { public Block addNewBlock(Method method) { return method.getLastBlock(); } + @Override + public boolean isSupportingBlockListeners() { + throw new InternalSpockError("AND block should have been replaced by a more specific block"); + } }, ANONYMOUS { @@ -43,6 +49,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(SETUP, GIVEN, EXPECT, WHEN, CLEANUP, WHERE, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return false; + } }, SETUP { @@ -54,6 +64,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, EXPECT, WHEN, CLEANUP, WHERE, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return true; + } }, GIVEN { @@ -65,6 +79,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return SETUP.getSuccessors(method); } + @Override + public boolean isSupportingBlockListeners() { + throw new InternalSpockError("GIVEN block should have been replaced by a SETUP block"); + } }, EXPECT { @@ -76,6 +94,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, WHEN, CLEANUP, WHERE, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return true; + } }, WHEN { @@ -87,6 +109,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, THEN); } + @Override + public boolean isSupportingBlockListeners() { + return true; + } }, THEN { @@ -98,6 +124,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, EXPECT, WHEN, THEN, CLEANUP, WHERE, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return true; + } }, CLEANUP { @@ -109,6 +139,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, WHERE, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return true; + } }, WHERE { @@ -120,6 +154,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, COMBINED, FILTER, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return false; + } }, COMBINED { @@ -131,6 +169,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return WHERE.getSuccessors(method); } + @Override + public boolean isSupportingBlockListeners() { + return false; + } }, FILTER { @@ -142,6 +184,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { return EnumSet.of(AND, METHOD_END); } + @Override + public boolean isSupportingBlockListeners() { + return false; + } }, METHOD_END { @@ -153,6 +199,10 @@ public Block addNewBlock(Method method) { public EnumSet getSuccessors(Method method) { throw new UnsupportedOperationException("getSuccessors"); } + @Override + public boolean isSupportingBlockListeners() { + return false; + } public String toString() { return "end-of-method"; } @@ -162,7 +212,20 @@ public String toString() { return super.toString().toLowerCase(Locale.ROOT); } + /** + * Adds a new block of this type to the given method. + *

+ * Allows for a block to substitute another block type. + */ public abstract Block addNewBlock(Method method); + /** + * Returns the block types that can follow this block type in the given method. + */ public abstract EnumSet getSuccessors(Method method); + + /** + * Indicates whether this block type supports block listeners. + */ + public abstract boolean isSupportingBlockListeners(); } diff --git a/spock-core/src/main/java/org/spockframework/lang/ISpecificationContext.java b/spock-core/src/main/java/org/spockframework/lang/ISpecificationContext.java index 28475c3c21..8e3b01674a 100644 --- a/spock-core/src/main/java/org/spockframework/lang/ISpecificationContext.java +++ b/spock-core/src/main/java/org/spockframework/lang/ISpecificationContext.java @@ -15,6 +15,7 @@ package org.spockframework.lang; import org.spockframework.mock.IThreadAwareMockController; +import org.spockframework.runtime.model.BlockInfo; import org.spockframework.runtime.model.FeatureInfo; import org.spockframework.runtime.model.SpecInfo; import org.spockframework.util.Beta; @@ -24,14 +25,20 @@ @Beta public interface ISpecificationContext { + @Nullable SpecInfo getCurrentSpec(); + FeatureInfo getCurrentFeature(); + IterationInfo getCurrentIteration(); + @Nullable + BlockInfo getCurrentBlock(); + @Nullable Throwable getThrownException(); IMockController getMockController(); - + IThreadAwareMockController getThreadAwareMockController(); } diff --git a/spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java b/spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java index 68b7dcb764..235b439dd8 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java +++ b/spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java @@ -44,11 +44,15 @@ protected Object invokeRaw(Object target, MethodInfo method, Object... arguments try { return method.invoke(target, arguments); } catch (Throwable throwable) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, throwable)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, throwable, getErrorContext())); return null; } } + protected IErrorContext getErrorContext() { + return ErrorContext.from((SpecificationContext) context.getCurrentInstance().getSpecificationContext()); + } + protected int estimateNumIterations(@Nullable Object dataProvider) { if (context.getErrorInfoCollector().hasErrors()) { return UNKNOWN_ITERATIONS; @@ -110,12 +114,12 @@ protected boolean haveNext(Iterator[] iterators, List dataP } else if (result != hasNext) { DataProviderInfo provider = dataProviderInfos.get(i); supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(provider.getDataProviderMethod(), - createDifferentNumberOfDataValuesException(provider, hasNext))); + createDifferentNumberOfDataValuesException(provider, hasNext), getErrorContext())); return false; } } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataProviderInfos.get(i).getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataProviderInfos.get(i).getDataProviderMethod(), t, getErrorContext())); return false; } @@ -131,12 +135,12 @@ protected Iterator createIterator(Object dataProvider, DataProviderInfo dataP Iterator iter = GroovyRuntimeUtil.asIterator(dataProvider); if (iter == null) { supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataProviderInfo.getDataProviderMethod(), - new SpockExecutionException("Data provider's iterator() method returned null"))); + new SpockExecutionException("Data provider's iterator() method returned null"), getErrorContext())); return null; } return iter; } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataProviderInfo.getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataProviderInfo.getDataProviderMethod(), t, getErrorContext())); return null; } } @@ -245,7 +249,7 @@ public Object[] next() { try { return (Object[]) invokeRaw(context.getSharedInstance(), context.getCurrentFeature().getDataProcessorMethod(), next); } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProcessorMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProcessorMethod(), t, getErrorContext())); return null; } } @@ -322,7 +326,7 @@ public Object[] next() { System.arraycopy(nextValues, 0, next, i, nextValues.length); i += nextValues.length; } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProviders().get(i).getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProviders().get(i).getDataProviderMethod(), t, getErrorContext())); return null; } } @@ -353,7 +357,7 @@ public Object[] next() { } // filter block does not like these values, try next ones if available } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(filterMethod, t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(filterMethod, t, getErrorContext())); return null; } } @@ -396,7 +400,7 @@ private Object[] createDataProviders() { break; } else if (provider == null) { SpockExecutionException error = new SpockExecutionException("Data provider is null!"); - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, error)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, error, getErrorContext())); break; } @@ -419,7 +423,7 @@ private IDataIterator[] createDataProviderIterators() { dataVariableMultiplications = Arrays.stream(((DataVariableMultiplication[]) invokeRaw(null, dataVariableMultiplicationsMethod))).iterator(); nextDataVariableMultiplication = dataVariableMultiplications.next(); } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataVariableMultiplicationsMethod, t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataVariableMultiplicationsMethod, t, getErrorContext())); return null; } } else { @@ -578,7 +582,7 @@ public boolean hasNext() { try { return iterator.hasNext(); } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(providerInfo.getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(providerInfo.getDataProviderMethod(), t, getErrorContext())); return false; } } @@ -592,7 +596,7 @@ public Object[] next() { try { return new Object[]{iterator.next()}; } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(providerInfo.getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(providerInfo.getDataProviderMethod(), t, getErrorContext())); return null; } } @@ -870,7 +874,7 @@ public Object[] next() { try { multiplicandIterators[i] = collectedMultiplicandValues.get(i).iterator(); } catch (Throwable t) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(multiplicandProviderInfos.get(i).getDataProviderMethod(), t)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(multiplicandProviderInfos.get(i).getDataProviderMethod(), t, getErrorContext())); return null; } } @@ -948,7 +952,7 @@ protected Object[] extractNextValues(Iterator[] iterators, ListSpecInfo.addListener(). See * {@link StepwiseExtension} for an example of how to use a listener. * + * @see org.spockframework.runtime.extension.IBlockListener * @author Peter Niederwieser */ public interface IRunListener { diff --git a/spock-core/src/main/java/org/spockframework/runtime/MasterRunSupervisor.java b/spock-core/src/main/java/org/spockframework/runtime/MasterRunSupervisor.java index 802a2306e1..904f5d5de6 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/MasterRunSupervisor.java +++ b/spock-core/src/main/java/org/spockframework/runtime/MasterRunSupervisor.java @@ -45,7 +45,7 @@ public void error(ErrorInfoCollector errorInfoCollector, ErrorInfo error) { exception = transform(exception); - ErrorInfo transformedError = new ErrorInfo(error.getMethod(), exception); + ErrorInfo transformedError = new ErrorInfo(error.getMethod(), exception, error.getErrorContext()); if (exception instanceof TestAbortedException || exception instanceof TestSkippedException) { // Spock has no concept of "aborted tests", so we don't notify Spock listeners } else { @@ -58,7 +58,7 @@ public void error(ErrorInfoCollector errorInfoCollector, ErrorInfo error) { private void handleMultipleFailures(ErrorInfoCollector errorInfoCollector, ErrorInfo error) { MultipleFailuresError multiFailure = (MultipleFailuresError) error.getException(); for (Throwable failure : multiFailure.getFailures()) - error(errorInfoCollector, new ErrorInfo(error.getMethod(), failure)); + error(errorInfoCollector, new ErrorInfo(error.getMethod(), failure, error.getErrorContext())); } diff --git a/spock-core/src/main/java/org/spockframework/runtime/PlatformSpecRunner.java b/spock-core/src/main/java/org/spockframework/runtime/PlatformSpecRunner.java index 9257395514..93d26f2801 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/PlatformSpecRunner.java +++ b/spock-core/src/main/java/org/spockframework/runtime/PlatformSpecRunner.java @@ -332,6 +332,7 @@ void runFeatureMethod(SpockExecutionContext context) { Object[] dataValues = context.getCurrentIteration().getDataValues(); invoke(context, context.getCurrentInstance(), featureIteration, dataValues); + getSpecificationContext(context).setCurrentBlock(null); } void runCleanup(SpockExecutionContext context) { @@ -376,7 +377,7 @@ private void runIterationCleanups(SpockExecutionContext context) { try { cleanup.run(); } catch (Throwable t) { - ErrorInfo error = new ErrorInfo(CollectionUtil.getFirstElement(context.getSpec().getCleanupMethods()), t); + ErrorInfo error = new ErrorInfo(CollectionUtil.getFirstElement(context.getSpec().getCleanupMethods()), t, ErrorContext.from(getSpecificationContext(context))); supervisor.error(context.getErrorInfoCollector(), error); } } @@ -429,7 +430,7 @@ protected void invoke(SpockExecutionContext context, Object target, MethodInfo m try { invocation.proceed(); } catch (Throwable throwable) { - ErrorInfo error = new ErrorInfo(method, throwable); + ErrorInfo error = new ErrorInfo(method, throwable, ErrorContext.from(getSpecificationContext(context))); supervisor.error(context.getErrorInfoCollector(), error); } } @@ -438,7 +439,7 @@ protected Object invokeRaw(SpockExecutionContext context, Object target, MethodI try { return method.invoke(target, arguments); } catch (Throwable throwable) { - supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, throwable)); + supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(method, throwable, ErrorContext.from(getSpecificationContext(context)))); return null; } } diff --git a/spock-core/src/main/java/org/spockframework/runtime/SpecInfoBuilder.java b/spock-core/src/main/java/org/spockframework/runtime/SpecInfoBuilder.java index 7c7f4445fb..4eb810a245 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/SpecInfoBuilder.java +++ b/spock-core/src/main/java/org/spockframework/runtime/SpecInfoBuilder.java @@ -169,10 +169,7 @@ private FeatureInfo createFeature(Method method, FeatureMetadata featureMetadata } for (BlockMetadata blockMetadata : featureMetadata.blocks()) { - BlockInfo block = new BlockInfo(); - block.setKind(blockMetadata.kind()); - block.setTexts(asList(blockMetadata.texts())); - feature.addBlock(block); + feature.addBlock(new BlockInfo(blockMetadata.kind(), asList(blockMetadata.texts()))); } return feature; diff --git a/spock-core/src/main/java/org/spockframework/runtime/SpecificationContext.java b/spock-core/src/main/java/org/spockframework/runtime/SpecificationContext.java index d54fb0f541..a8249ae00c 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/SpecificationContext.java +++ b/spock-core/src/main/java/org/spockframework/runtime/SpecificationContext.java @@ -5,6 +5,7 @@ import org.spockframework.mock.IThreadAwareMockController; import org.spockframework.mock.runtime.MockController; import org.spockframework.runtime.model.*; +import org.spockframework.util.Nullable; import spock.lang.Specification; public class SpecificationContext implements ISpecificationContext { @@ -12,6 +13,8 @@ public class SpecificationContext implements ISpecificationContext { private volatile FeatureInfo currentFeature; private volatile IterationInfo currentIteration; + private volatile BlockInfo currentBlock; + private volatile Specification sharedInstance; private volatile Throwable thrownException; @@ -44,7 +47,12 @@ public FeatureInfo getCurrentFeature() { return currentFeature; } - public void setCurrentFeature(FeatureInfo currentFeature) { + @Nullable + FeatureInfo getCurrentFeatureOrNull() { + return currentFeature; + } + + public void setCurrentFeature(@Nullable FeatureInfo currentFeature) { this.currentFeature = currentFeature; } @@ -56,10 +64,28 @@ public IterationInfo getCurrentIteration() { return currentIteration; } - public void setCurrentIteration(IterationInfo currentIteration) { + @Nullable + IterationInfo getCurrentIterationOrNull() { + return currentIteration; + } + + public void setCurrentIteration(@Nullable IterationInfo currentIteration) { this.currentIteration = currentIteration; } + public static final String SET_CURRENT_BLOCK = "setCurrentBlock"; + public void setCurrentBlock(@Nullable BlockInfo blockInfo) { + this.currentBlock = blockInfo; + } + + public static final String GET_CURRENT_BLOCK = "getCurrentBlock"; + + @Nullable + @Override + public BlockInfo getCurrentBlock() { + return currentBlock; + } + @Override public Throwable getThrownException() { return thrownException; @@ -80,4 +106,5 @@ public IMockController getMockController() { public IThreadAwareMockController getThreadAwareMockController() { return mockController; } + } diff --git a/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java b/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java index 31a58fb195..04a5163833 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java +++ b/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java @@ -24,13 +24,17 @@ import org.hamcrest.Matcher; import org.hamcrest.collection.IsIterableContainingInAnyOrder; import org.opentest4j.MultipleFailuresError; +import org.spockframework.runtime.extension.IBlockListener; +import org.spockframework.runtime.model.BlockInfo; import org.spockframework.runtime.model.ExpressionInfo; +import org.spockframework.runtime.model.IterationInfo; import org.spockframework.runtime.model.TextPosition; import org.spockframework.util.CollectionUtil; import org.spockframework.util.ExceptionUtil; import org.spockframework.util.Nullable; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -225,6 +229,30 @@ public static Object[] despreadList(Object[] args, Object[] spreads, int[] posit return GroovyRuntimeUtil.despreadList(args, spreads, positions); } + public static final String CALL_BLOCK_ENTERED = "callBlockEntered"; + + public static void callBlockEntered(SpecificationContext context, int blockInfoIndex) { + IterationInfo currentIteration = context.getCurrentIteration(); + BlockInfo blockInfo = context.getCurrentFeature().getBlocks().get(blockInfoIndex); + context.setCurrentBlock(blockInfo); + notifyBlockListener(currentIteration, blockListener -> blockListener.blockEntered(currentIteration, blockInfo)); + } + + private static void notifyBlockListener(IterationInfo currentIteration, Consumer consumer) { + List blockListeners = currentIteration.getFeature().getBlockListeners(); + if (blockListeners.isEmpty()) return; + blockListeners.forEach(consumer); + } + + public static final String CALL_BLOCK_EXITED = "callBlockExited"; + + public static void callBlockExited(SpecificationContext context, int blockInfoIndex) { + IterationInfo currentIteration = context.getCurrentIteration(); + BlockInfo blockInfo = context.getCurrentFeature().getBlocks().get(blockInfoIndex); + notifyBlockListener(currentIteration, blockListener -> blockListener.blockExited(currentIteration, blockInfo)); + context.setCurrentBlock(null); + } + private static List getValues(ValueRecorder recorder) { return recorder == null ? null : recorder.getValues(); } diff --git a/spock-core/src/main/java/org/spockframework/runtime/extension/IBlockListener.java b/spock-core/src/main/java/org/spockframework/runtime/extension/IBlockListener.java new file mode 100644 index 0000000000..df06c4c363 --- /dev/null +++ b/spock-core/src/main/java/org/spockframework/runtime/extension/IBlockListener.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.runtime.extension; + +import org.spockframework.runtime.model.BlockInfo; +import org.spockframework.runtime.model.ErrorInfo; +import org.spockframework.runtime.model.IterationInfo; +import org.spockframework.util.Beta; + +/** + * Listens to block events during the execution of a feature. + *

+ * Usually used in conjunction with {@link org.spockframework.runtime.IRunListener}. + * Currently, only extensions can register listeners. + * They do so by invoking {@link org.spockframework.runtime.model.FeatureInfo#addBlockListener(IBlockListener)}. + * It is preferred to use a single instance of this. + *

+ * It is discouraged to perform long-running operations in the listener methods, + * as they are called during the execution of the specification. + * It is discouraged to perform any side effects affecting the tests. + *

+ * When an exception is thrown in a block, the {@code blockExited} will not be called for that block. + * If a cleanup block is present the cleanup block listener methods will still be called. + * + * @see org.spockframework.runtime.IRunListener + * @author Leonard Brünings + * @since 2.4 + */ +@Beta +public interface IBlockListener { + + /** + * Called when a block is entered. + */ + default void blockEntered(IterationInfo iterationInfo, BlockInfo blockInfo) {} + + /** + * Called when a block is exited. + *

+ * This method is not called if an exception is thrown in the block. + * The block that was active will be available in the {@link org.spockframework.runtime.model.IErrorContext} + * and can be observed via {@link org.spockframework.runtime.IRunListener#error(ErrorInfo)}. + */ + default void blockExited(IterationInfo iterationInfo, BlockInfo blockInfo) {} +} diff --git a/spock-core/src/main/java/org/spockframework/runtime/model/BlockInfo.java b/spock-core/src/main/java/org/spockframework/runtime/model/BlockInfo.java index 6ef57529ec..4768b46604 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/model/BlockInfo.java +++ b/spock-core/src/main/java/org/spockframework/runtime/model/BlockInfo.java @@ -16,6 +16,8 @@ package org.spockframework.runtime.model; +import org.spockframework.util.Nullable; + import java.util.List; /** @@ -27,6 +29,21 @@ public class BlockInfo { private BlockKind kind; private List texts; + /** + * Only for backwards compatibility. + *

+ * @deprecated Use {@link #BlockInfo(BlockKind, List)} instead. + */ + @Deprecated + public BlockInfo() { + } + + public BlockInfo(BlockKind kind, List texts) { + this.kind = kind; + this.texts = texts; + } + + @Nullable public BlockKind getKind() { return kind; } @@ -35,6 +52,7 @@ public void setKind(BlockKind kind) { this.kind = kind; } + @Nullable public List getTexts() { return texts; } diff --git a/spock-core/src/main/java/org/spockframework/runtime/model/ErrorInfo.java b/spock-core/src/main/java/org/spockframework/runtime/model/ErrorInfo.java index a730779216..f9c4bf6f99 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/model/ErrorInfo.java +++ b/spock-core/src/main/java/org/spockframework/runtime/model/ErrorInfo.java @@ -17,10 +17,12 @@ public class ErrorInfo { private final MethodInfo method; private final Throwable error; + private final IErrorContext errorContext; - public ErrorInfo(MethodInfo method, Throwable error) { + public ErrorInfo(MethodInfo method, Throwable error, IErrorContext errorContext) { this.method = method; this.error = error; + this.errorContext = errorContext; } public MethodInfo getMethod() { @@ -30,4 +32,22 @@ public MethodInfo getMethod() { public Throwable getException() { return error; } + + /** + * {@return the error context} + * + * @since 2.4 + */ + public IErrorContext getErrorContext() { + return errorContext; + } + + @Override + public String toString() { + return "ErrorInfo{" + + "method=" + method + + ", errorContext=" + errorContext + + ", error=" + error + + '}'; + } } diff --git a/spock-core/src/main/java/org/spockframework/runtime/model/FeatureInfo.java b/spock-core/src/main/java/org/spockframework/runtime/model/FeatureInfo.java index 81cfab9b74..53ee0d5744 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/model/FeatureInfo.java +++ b/spock-core/src/main/java/org/spockframework/runtime/model/FeatureInfo.java @@ -15,6 +15,7 @@ package org.spockframework.runtime.model; +import org.spockframework.runtime.extension.IBlockListener; import org.spockframework.runtime.extension.IDataDriver; import org.spockframework.runtime.extension.IMethodInterceptor; import org.spockframework.runtime.model.parallel.ExclusiveResource; @@ -42,6 +43,8 @@ public class FeatureInfo extends SpecElementInfo imp private final List initializerInterceptors = new ArrayList<>(); private final Map> scopedMethodInterceptors = new HashMap<>(); + private final List blockListeners = new ArrayList<>(); + private final Set exclusiveResources = new HashSet<>(); private final Set testTags = new HashSet<>(); @@ -165,7 +168,7 @@ public List getInitializerInterceptors() { } /** - * Adds a initializer interceptor for this feature. + * Adds an initializer interceptor for this feature. *

* The feature-scoped interceptors will execute before the spec interceptors. * @@ -221,6 +224,22 @@ public void addIterationInterceptor(IMethodInterceptor interceptor) { iterationInterceptors.add(interceptor); } + /** + * @since 2.4 + */ + @Beta + public List getBlockListeners() { + return blockListeners; + } + + /** + * @since 2.4 + */ + @Beta + public void addBlockListener(IBlockListener blockListener) { + blockListeners.add(blockListener); + } + public MethodInfo getFeatureMethod() { return featureMethod; } diff --git a/spock-core/src/main/java/org/spockframework/runtime/model/IErrorContext.java b/spock-core/src/main/java/org/spockframework/runtime/model/IErrorContext.java new file mode 100644 index 0000000000..a2eb30c24b --- /dev/null +++ b/spock-core/src/main/java/org/spockframework/runtime/model/IErrorContext.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.runtime.model; + +import org.spockframework.util.Beta; +import org.spockframework.util.Nullable; + +/** + * Provides context information for an error that occurred during the execution of a specification. + *

+ * Depending on the context in which the error occurred, some of the methods may return {@code null}. + * + * @since 2.4 + */ +@Beta +public interface IErrorContext { + @Nullable + SpecInfo getSpec(); + + @Nullable + FeatureInfo getFeature(); + + @Nullable + IterationInfo getIteration(); + + @Nullable + BlockInfo getBlock(); +} diff --git a/spock-specs/src/test/groovy/org/spockframework/runtime/AsyncRunListenerSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/runtime/AsyncRunListenerSpec.groovy index 3a33687d87..b33cfb6493 100644 --- a/spock-specs/src/test/groovy/org/spockframework/runtime/AsyncRunListenerSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/runtime/AsyncRunListenerSpec.groovy @@ -13,7 +13,7 @@ class AsyncRunListenerSpec extends Specification { def specInfo = new SpecInfoBuilder(getClass()).build() def featureInfo = specInfo.features[0] def iterationInfo = new IterationInfo(featureInfo, 0, [] as Object[], 1) - def errorInfo = new ErrorInfo(featureInfo.featureMethod, new Exception()) + def errorInfo = new ErrorInfo(featureInfo.featureMethod, new Exception(), new ErrorContext(specInfo, featureInfo, iterationInfo, null)) when: asyncListener.start() diff --git a/spock-specs/src/test/groovy/org/spockframework/runtime/BlockListenerSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/runtime/BlockListenerSpec.groovy new file mode 100644 index 0000000000..59326f9a02 --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/runtime/BlockListenerSpec.groovy @@ -0,0 +1,81 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.runtime + +import org.spockframework.runtime.extension.IBlockListener +import org.spockframework.runtime.model.BlockInfo +import org.spockframework.runtime.model.BlockKind +import org.spockframework.runtime.model.IterationInfo +import spock.lang.Specification + +class BlockListenerSpec extends Specification { + + List blocks = [] + List exitBlocks = [] + + def setup() { + specificationContext.currentIteration.feature.addBlockListener([ + blockEntered: { IterationInfo i, BlockInfo b -> + blocks << b + }, + blockExited: { IterationInfo i, BlockInfo b -> + exitBlocks << b + }] as IBlockListener) + } + + def "BlockListener is called for each Block with text"() { + given: "setup" + expect: "precondition" + when: "action" + then: "assertion" + + cleanup: "cleanup" + assert blocks.kind == [BlockKind.SETUP, BlockKind.EXPECT, BlockKind.WHEN, BlockKind.THEN, BlockKind.CLEANUP] + assert blocks.texts == [["setup"], ["precondition"], ["action"], ["assertion"], ["cleanup"]] + assert exitBlocks.kind == [BlockKind.SETUP, BlockKind.EXPECT, BlockKind.WHEN, BlockKind.THEN] + } + + def "SpecificationContext holds a reference to the current block"() { + assert specificationContext.currentBlock == null + given: "setup" + assert specificationContext.currentBlock.kind == BlockKind.SETUP + expect: "precondition" + specificationContext.currentBlock.kind == BlockKind.EXPECT + when: "action" + assert specificationContext.currentBlock.kind == BlockKind.WHEN + then: "assertion" + specificationContext.currentBlock.kind == BlockKind.THEN + + cleanup: "cleanup" + assert specificationContext.currentBlock.kind == BlockKind.CLEANUP + } + + def "blocks extended with and: are treated as singular block with multiple texts"() { + given: "setup" + and: "setup2" + expect: "precondition" + and: "precondition2" + when: "action" + and: "action2" + then: "assertion" + and: "assertion2" + + cleanup: "cleanup" + assert blocks.kind == [BlockKind.SETUP, BlockKind.EXPECT, BlockKind.WHEN, BlockKind.THEN, BlockKind.CLEANUP] + and: "cleanup2" + assert blocks.texts == [["setup", "setup2"], ["precondition", "precondition2"], ["action", "action2"], ["assertion", "assertion2"], ["cleanup", "cleanup2"]] + assert exitBlocks.kind == [BlockKind.SETUP, BlockKind.EXPECT, BlockKind.WHEN, BlockKind.THEN] + } +} diff --git a/spock-specs/src/test/groovy/org/spockframework/runtime/RunListenerSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/runtime/RunListenerSpec.groovy index 2f8255ed25..e061e8e2aa 100644 --- a/spock-specs/src/test/groovy/org/spockframework/runtime/RunListenerSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/runtime/RunListenerSpec.groovy @@ -1,15 +1,22 @@ package org.spockframework.runtime import org.spockframework.EmbeddedSpecification -import org.spockframework.runtime.extension.* +import org.spockframework.runtime.extension.ExtensionAnnotation +import org.spockframework.runtime.extension.IAnnotationDrivenExtension +import org.spockframework.runtime.model.BlockKind +import org.spockframework.runtime.model.ErrorInfo import org.spockframework.runtime.model.SpecInfo import org.spockframework.runtime.model.parallel.ExecutionMode import spock.lang.Execution import spock.lang.Ignore import spock.lang.Issue import spock.lang.Specification +import spock.lang.Unroll -import java.lang.annotation.* +import java.lang.annotation.ElementType +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import java.lang.annotation.Target @Execution( value = ExecutionMode.SAME_THREAD, @@ -120,6 +127,133 @@ class ASpec extends Specification { cleanup: RunListenerDelegate.delegate = null } + + def "IRunListener gets called for errors"() { + given: + RunListenerDelegate.delegate = runListener + runner.addPackageImport(Specification.package) + runner.addClassImport(RegisterRunListener) + runner.throwFailure = false + + when: + runner.runWithImports ''' +@RegisterRunListener +class ASpec extends Specification { + def "a test"() { + expect: "failing expect" + false + + cleanup: "failing cleanup" + throw new RuntimeException("failing cleanup") + } +} +''' + + then: + 1 * runListener.beforeSpec(_) + then: + 1 * runListener.beforeFeature(_) + then: + 1 * runListener.beforeIteration(_) + then: + 1 * runListener.error(_) >> { ErrorInfo errorInfo -> + with(errorInfo.errorContext.block) { + it.kind == BlockKind.EXPECT + it.texts == ["failing expect"] + } + assert errorInfo.exception instanceof AssertionError + assert errorInfo.exception.suppressed[0].message == "failing cleanup" + } + then: + 1 * runListener.afterIteration(_) + then: + 1 * runListener.afterFeature(_) + then: + 1 * runListener.afterSpec(_) + then: + 0 * runListener._ + + cleanup: + RunListenerDelegate.delegate = null + } + + @Unroll("IRunListener.error gets called for #errorLocation") + def "IRunListener gets called for different error locations"() { + given: + RunListenerDelegate.delegate = runListener + runner.addPackageImport(Specification.package) + runner.addClassImport(RegisterRunListener) + runner.throwFailure = false + ErrorInfo errorInfo + + when: + runner.runWithImports """ +@RegisterRunListener +class ASpec extends Specification { + def setupSpec() { + assert "$errorLocation" != "setupSpec" + } + def setup() { + assert "$errorLocation" != "setup" + } + + def "a test"() { + assert "$errorLocation" != "feature start" + + given: "setup label" + assert "$errorLocation" != "feature setup" + + expect: "expect label" + "$errorLocation" != "feature expect" + + when: "when label" + assert "$errorLocation" != "feature when" + + then: "then label" + "$errorLocation" != "feature then" + + cleanup: "cleanup label" + assert "$errorLocation" != "feature cleanup" + } + + def cleanup() { + assert "$errorLocation" != "cleanup" + } + + def cleanupSpec() { + assert "$errorLocation" != "cleanupSpec" + } +} +""" + + then: + 1 * runListener.error(_) >> { ErrorInfo it -> errorInfo = it } + + if (block != null) { + with(errorInfo.errorContext.block) { + it.kind == block + it.texts == blockTexts + } + } else { + assert errorInfo.errorContext.block == null + } + + cleanup: + RunListenerDelegate.delegate = null + + where: + errorLocation | block | blockTexts + "setupSpec" | null | [] + "setup" | null | [] + "feature start" | null | [] + "feature setup" | BlockKind.SETUP | ["setup label"] + "feature expect" | BlockKind.EXPECT | ["expect label"] + "feature when" | BlockKind.WHEN | ["when label"] + "feature then" | BlockKind.THEN | ["then label"] + "feature cleanup" | BlockKind.CLEANUP | ["cleanup label"] + "cleanup" | null | [] + "cleanupSpec" | null | [] + } } @Target(ElementType.TYPE) diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/ast/BlocksAst.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/ast/BlocksAst.groovy new file mode 100644 index 0000000000..ccc4ba3571 --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/ast/BlocksAst.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.spockframework.smoke.ast + +import org.spockframework.EmbeddedSpecification +import org.spockframework.specs.extension.SpockSnapshotter +import spock.lang.Snapshot + +class BlocksAst extends EmbeddedSpecification { + @Snapshot(extension = 'groovy') + SpockSnapshotter snapshotter + + def "all observable blocks with empty labels"() { + given: + snapshotter.featureBody() + + when: + def result = compiler.transpileFeatureBody(''' + given: '' + expect: '' + when: '' + then: '' + cleanup: '' + where: '' + combined: '' + filter: '' + ''') + + then: + snapshotter.assertThat(result.source).matchesSnapshot() + } + def "all observable blocks with labels and blocks"() { + given: + snapshotter.featureBody() + + when: + def result = compiler.transpileFeatureBody(''' + given: 'given' + and: 'and given' + expect: 'expect' + and: 'and expect' + when: 'when' + and: 'and when' + then: 'then' + and: 'and then' + then: 'then2' + and: 'and then2' + cleanup: 'cleanup' + and: 'and cleanup' + where: 'where' + combined: 'combine' + filter: 'only one execution' + ''') + + then: + snapshotter.assertThat(result.source).matchesSnapshot() + } + + def "all observable blocks with GString labels"() { + given: + snapshotter.featureBody() + + when: + def result = compiler.transpileFeatureBody(''' + int idx = 0 + given: "given ${idx++}" + expect: "expect ${idx++}" + when: "when ${idx++}" + then: "then ${idx++}" + ''') + + then: + snapshotter.assertThat(result.source).matchesSnapshot() + } +} diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/ast/mock/MocksAstSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/ast/mock/MocksAstSpec.groovy new file mode 100644 index 0000000000..001677a268 --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/ast/mock/MocksAstSpec.groovy @@ -0,0 +1,44 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.smoke.ast.mock + +import org.spockframework.EmbeddedSpecification +import org.spockframework.specs.extension.SpockSnapshotter +import spock.lang.Snapshot + +class MocksAstSpec extends EmbeddedSpecification { + + @Snapshot(extension = 'groovy') + SpockSnapshotter snapshotter + + def "simple interaction"() { + given: + snapshotter.featureBody() + + when: + def result = compiler.transpileFeatureBody(""" + given: + List list = Mock() + + when: + list.add(1) + + then: + 1 * list.add(1) +""") + then: + snapshotter.assertThat(result.source).matchesSnapshot() + } +} diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/DataProviders.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/DataProviders.groovy index 9563c41188..e27abaaf18 100644 --- a/spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/DataProviders.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/DataProviders.groovy @@ -18,6 +18,7 @@ package org.spockframework.smoke.parameterization import org.spockframework.EmbeddedSpecification import org.spockframework.runtime.SpockExecutionException +import org.spockframework.specs.extension.SpockSnapshotter import spock.lang.* import spock.util.Show @@ -103,7 +104,10 @@ where: x << [] } @Issue("https://github.com/spockframework/spock/issues/1287") - def "data provider with asserting closure produces error rethrower variable in data provider method"() { + def "data provider with asserting closure produces error rethrower variable in data provider method"(@Snapshot(extension = 'groovy') SpockSnapshotter snapshotter) { + given: + snapshotter.specBody() + when: def result = compiler.transpileFeatureBody(''' where: @@ -112,30 +116,7 @@ where: x << [] ''', EnumSet.of(Show.METHODS)) then: - result.source == '''\ -public void $spock_feature_0_0(java.lang.Object dataPipe, java.lang.Object dataVariable) { - this.getSpecificationContext().getMockController().leaveScope() -} - -public java.lang.Object $spock_feature_0_0prov0() { - org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE - return [{ -> - org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder() - try { - org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder1.reset(), 'true', 2, 29, null, $spock_valueRecorder1.record($spock_valueRecorder1.startRecordingValue(0), true)) - } - catch (java.lang.Throwable $spock_condition_throwable) { - org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder1, 'true', 2, 29, null, $spock_condition_throwable)} - finally { - } - }] -} - -public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { - java.lang.Object dataPipe = (( $spock_p0 ) as java.lang.Object) - java.lang.Object dataVariable = ((null) as java.lang.Object) - return new java.lang.Object[]{ dataPipe , dataVariable } -}''' + snapshotter.assertThat(result.source).matchesSnapshot() } @Issue("https://github.com/spockframework/spock/issues/1287") @@ -152,7 +133,10 @@ public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { } @Issue("https://github.com/spockframework/spock/issues/1287") - def "data variable with asserting closure produces error rethrower variable in data processor method"() { + def "data variable with asserting closure produces error rethrower variable in data processor method"(@Snapshot(extension = 'groovy') SpockSnapshotter snapshotter) { + given: + snapshotter.specBody() + when: def result = compiler.transpileFeatureBody(''' where: @@ -161,30 +145,7 @@ public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { ''', EnumSet.of(Show.METHODS)) then: - result.source == '''\ -public void $spock_feature_0_0(java.lang.Object dataPipe, java.lang.Object dataVariable) { - this.getSpecificationContext().getMockController().leaveScope() -} - -public java.lang.Object $spock_feature_0_0prov0() { - return [null] -} - -public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { - org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE - java.lang.Object dataPipe = (( $spock_p0 ) as java.lang.Object) - java.lang.Object dataVariable = (({ -> - org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder() - try { - org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder1.reset(), 'true', 3, 31, null, $spock_valueRecorder1.record($spock_valueRecorder1.startRecordingValue(0), true)) - } - catch (java.lang.Throwable $spock_condition_throwable) { - org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder1, 'true', 3, 31, null, $spock_condition_throwable)} - finally { - } - }) as java.lang.Object) - return new java.lang.Object[]{ dataPipe , dataVariable } -}''' + snapshotter.assertThat(result.source).matchesSnapshot() } @Issue("https://github.com/spockframework/spock/issues/1287") diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____3.txt b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____3.txt index 36f30e3f84..50a2e480b4 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____3.txt +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____3.txt @@ -40,6 +40,15 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov L10 ALOAD 3 POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_0 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP L0 LINENUMBER 4 L0 ALOAD 2 @@ -87,6 +96,24 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov ATHROW L13 FRAME SAME + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_0 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_1 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP ALOAD 0 INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; LDC Lorg/spockframework/runtime/SpecificationContext;.class @@ -129,8 +156,27 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov ALOAD 7 ATHROW L16 - LINENUMBER 8 L16 FRAME SAME + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_1 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_2 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + L17 + LINENUMBER 8 L17 ALOAD 1 LDC 2 AALOAD @@ -145,6 +191,15 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov LDC Lorg/spockframework/runtime/SpecificationContext;.class INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; CHECKCAST org/spockframework/runtime/SpecificationContext + ICONST_2 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + LDC Lorg/spockframework/runtime/SpecificationContext;.class + INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; + CHECKCAST org/spockframework/runtime/SpecificationContext INVOKEVIRTUAL org/spockframework/runtime/SpecificationContext.getMockController ()Lorg/spockframework/mock/IMockController; LDC Lorg/spockframework/mock/runtime/MockController;.class INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; @@ -152,11 +207,11 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov INVOKEVIRTUAL org/spockframework/mock/runtime/MockController.leaveScope ()V ACONST_NULL POP - L17 + L18 RETURN - LOCALVARIABLE this Lapackage/TestSpec; L8 L17 0 - LOCALVARIABLE $spock_errorCollector Lorg/spockframework/runtime/ErrorCollector; L9 L17 2 - LOCALVARIABLE $spock_valueRecorder Lorg/spockframework/runtime/ValueRecorder; L10 L17 3 + LOCALVARIABLE this Lapackage/TestSpec; L8 L18 0 + LOCALVARIABLE $spock_errorCollector Lorg/spockframework/runtime/ErrorCollector; L9 L18 2 + LOCALVARIABLE $spock_valueRecorder Lorg/spockframework/runtime/ValueRecorder; L10 L18 3 LOCALVARIABLE $spock_condition_throwable Ljava/lang/Throwable; L12 L3 4 LOCALVARIABLE $spock_ex Ljava/lang/Throwable; L15 L7 6 MAXSTACK = 9 diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____4.txt b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____4.txt index 14d0a2cdcd..0887d0dff6 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____4.txt +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation_Groovy____4.txt @@ -51,6 +51,19 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov L10 ALOAD 2 POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] + ICONST_0 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP L0 LINENUMBER 4 L0 ALOAD 1 @@ -98,6 +111,32 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov ATHROW L13 FRAME SAME + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] + ICONST_0 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] + ICONST_1 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP ALOAD 0 INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ @@ -145,8 +184,35 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov ALOAD 6 ATHROW L16 - LINENUMBER 8 L16 FRAME SAME + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] + ICONST_1 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] + ICONST_2 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockEntered (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + L17 + LINENUMBER 8 L17 ALOAD 0 ACONST_NULL ACONST_NULL @@ -168,6 +234,19 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov "()", 0 ] + ICONST_2 + INVOKESTATIC org/spockframework/runtime/SpockRuntime.callBlockExited (Lorg/spockframework/runtime/SpecificationContext;I)V + ACONST_NULL + POP + ALOAD 0 + INVOKEVIRTUAL org/spockframework/lang/SpecInternals.getSpecificationContext ()Lorg/spockframework/lang/ISpecificationContext; + INVOKEDYNAMIC cast(Lorg/spockframework/lang/ISpecificationContext;)Lorg/spockframework/runtime/SpecificationContext; [ + // handle kind 0x6 : INVOKESTATIC + org/codehaus/groovy/vmplugin/v8/IndyInterface.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite; + // arguments: + "()", + 0 + ] INVOKEVIRTUAL org/spockframework/runtime/SpecificationContext.getMockController ()Lorg/spockframework/mock/IMockController; INVOKEDYNAMIC cast(Lorg/spockframework/mock/IMockController;)Lorg/spockframework/mock/runtime/MockController; [ // handle kind 0x6 : INVOKESTATIC @@ -179,12 +258,12 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov INVOKEVIRTUAL org/spockframework/mock/runtime/MockController.leaveScope ()V ACONST_NULL POP - L17 - LINENUMBER 9 L17 + L18 + LINENUMBER 9 L18 RETURN - LOCALVARIABLE this Lapackage/TestSpec; L8 L17 0 - LOCALVARIABLE $spock_errorCollector Lorg/spockframework/runtime/ErrorCollector; L9 L17 1 - LOCALVARIABLE $spock_valueRecorder Lorg/spockframework/runtime/ValueRecorder; L10 L17 2 + LOCALVARIABLE this Lapackage/TestSpec; L8 L18 0 + LOCALVARIABLE $spock_errorCollector Lorg/spockframework/runtime/ErrorCollector; L9 L18 1 + LOCALVARIABLE $spock_valueRecorder Lorg/spockframework/runtime/ValueRecorder; L10 L18 2 LOCALVARIABLE $spock_condition_throwable Ljava/lang/Throwable; L12 L3 3 LOCALVARIABLE $spock_ex Ljava/lang/Throwable; L15 L7 5 MAXSTACK = 9 diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything.groovy index 863a604b80..ce864b0430 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything.groovy @@ -7,7 +7,9 @@ public class apackage.ASpec extends spock.lang.Specification { @org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = [])], parameterNames = []) public void $spock_feature_0_0() { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object nothing = null + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything__Groovy_4_0_2__.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything__Groovy_4_0_2__.groovy index 57dc76b278..11ddc426bf 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything__Groovy_4_0_2__.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything__Groovy_4_0_2__.groovy @@ -7,7 +7,9 @@ public class apackage.ASpec extends spock.lang.Specification implements groovy.l @org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = [])], parameterNames = []) public void $spock_feature_0_0() { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object nothing = null + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_renders_only_methods_and_its_annotation_by_default.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_renders_only_methods_and_its_annotation_by_default.groovy index 39cb8e739b..a690ad70c0 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_renders_only_methods_and_its_annotation_by_default.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_renders_only_methods_and_its_annotation_by_default.groovy @@ -6,7 +6,9 @@ class ASpec extends Specification { /*--------- tag::snapshot[] ---------*/ @org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = [])], parameterNames = []) public void $spock_feature_0_0() { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object nothing = null + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceSpecBody_renders_only_methods__fields__properties__object_initializers_and_their_annotation_by_default.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceSpecBody_renders_only_methods__fields__properties__object_initializers_and_their_annotation_by_default.groovy index 4a955d2daa..31d9795be5 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceSpecBody_renders_only_methods__fields__properties__object_initializers_and_their_annotation_by_default.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceSpecBody_renders_only_methods__fields__properties__object_initializers_and_their_annotation_by_default.groovy @@ -12,7 +12,9 @@ private java.lang.Object $spock_initializeFields() { @org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 3, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = [])], parameterNames = []) public void $spock_feature_0_0() { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object nothing = null + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_GString_labels.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_GString_labels.groovy new file mode 100644 index 0000000000..00650532dc --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_GString_labels.groovy @@ -0,0 +1,40 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { + def "aFeature"() { +/*--------- tag::snapshot[] ---------*/ +@org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.EXPECT, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHEN, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = [])], parameterNames = []) +public void $spock_feature_0_0() { + org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE + org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + java.lang.Integer idx = 0 + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) + "given ${( idx )++}" + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) + try { + org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), '\"expect \${idx++}\"', 3, 13, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), "${$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), ( idx )++)}expect ")) + } + catch (java.lang.Throwable $spock_condition_throwable) { + org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '\"expect \${idx++}\"', 3, 13, null, $spock_condition_throwable)} + finally { + } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) + "when ${( idx )++}" + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 3) + try { + org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), '\"then \${idx++}\"', 5, 11, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), "${$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), ( idx )++)}then ")) + } + catch (java.lang.Throwable $spock_condition_throwable) { + org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, '\"then \${idx++}\"', 5, 11, null, $spock_condition_throwable)} + finally { + } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 3) + this.getSpecificationContext().getMockController().leaveScope() +} +/*--------- end::snapshot[] ---------*/ + } +} diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_empty_labels.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_empty_labels.groovy new file mode 100644 index 0000000000..bdf57f9c28 --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_empty_labels.groovy @@ -0,0 +1,50 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { + def "aFeature"() { +/*--------- tag::snapshot[] ---------*/ +@org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = ['']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.EXPECT, texts = ['']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHEN, texts = ['']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = ['']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.CLEANUP, texts = ['']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHERE, texts = ['', '']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.FILTER, texts = [''])], parameterNames = []) +public void $spock_feature_0_0() { + java.lang.Throwable $spock_feature_throwable + try { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 3) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 3) + } + catch (java.lang.Throwable $spock_tmp_throwable) { + $spock_feature_throwable = $spock_tmp_throwable + throw $spock_tmp_throwable + } + finally { + org.spockframework.runtime.model.BlockInfo $spock_failedBlock = null + try { + if ( $spock_feature_throwable != null) { + $spock_failedBlock = this.getSpecificationContext().getCurrentBlock() + } + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 4) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 4) + } + catch (java.lang.Throwable $spock_tmp_throwable) { + if ( $spock_feature_throwable != null) { + $spock_feature_throwable.addSuppressed($spock_tmp_throwable) + } else { + throw $spock_tmp_throwable + } + } + finally { + if ( $spock_feature_throwable != null) { + ((org.spockframework.runtime.SpecificationContext) this.getSpecificationContext()).setCurrentBlock($spock_failedBlock) + } + } + } + this.getSpecificationContext().getMockController().leaveScope() +} +/*--------- end::snapshot[] ---------*/ + } +} diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_labels_and_blocks.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_labels_and_blocks.groovy new file mode 100644 index 0000000000..69434818ef --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/BlocksAst/all_observable_blocks_with_labels_and_blocks.groovy @@ -0,0 +1,52 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { + def "aFeature"() { +/*--------- tag::snapshot[] ---------*/ +@org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = ['given', 'and given']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.EXPECT, texts = ['expect', 'and expect']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHEN, texts = ['when', 'and when']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = ['then', 'and then']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = ['then2', 'and then2']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.CLEANUP, texts = ['cleanup', 'and cleanup']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHERE, texts = ['where', 'combine']), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.FILTER, texts = ['only one execution'])], parameterNames = []) +public void $spock_feature_0_0() { + java.lang.Throwable $spock_feature_throwable + try { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 3) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 3) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 4) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 4) + } + catch (java.lang.Throwable $spock_tmp_throwable) { + $spock_feature_throwable = $spock_tmp_throwable + throw $spock_tmp_throwable + } + finally { + org.spockframework.runtime.model.BlockInfo $spock_failedBlock = null + try { + if ( $spock_feature_throwable != null) { + $spock_failedBlock = this.getSpecificationContext().getCurrentBlock() + } + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 5) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 5) + } + catch (java.lang.Throwable $spock_tmp_throwable) { + if ( $spock_feature_throwable != null) { + $spock_feature_throwable.addSuppressed($spock_tmp_throwable) + } else { + throw $spock_tmp_throwable + } + } + finally { + if ( $spock_feature_throwable != null) { + ((org.spockframework.runtime.SpecificationContext) this.getSpecificationContext()).setCurrentBlock($spock_failedBlock) + } + } + } + this.getSpecificationContext().getMockController().leaveScope() +} +/*--------- end::snapshot[] ---------*/ + } +} diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference.groovy index c65f20fc26..12acd40e97 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference.groovy @@ -13,7 +13,10 @@ public void $spock_feature_0_0() { java.lang.Object foobar java.lang.Throwable $spock_feature_throwable try { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) foobar = this.foobar() + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'println(foobar)', 6, 3, null, this, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), 'println'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), foobar)}, $spock_valueRecorder.realizeNas(4, false), false, 3) } @@ -21,14 +24,21 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'println(foobar)', 6, 3, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) } catch (java.lang.Throwable $spock_tmp_throwable) { $spock_feature_throwable = $spock_tmp_throwable throw $spock_tmp_throwable } finally { + org.spockframework.runtime.model.BlockInfo $spock_failedBlock = null try { + if ( $spock_feature_throwable != null) { + $spock_failedBlock = this.getSpecificationContext().getCurrentBlock() + } + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) foobar.size() + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) } catch (java.lang.Throwable $spock_tmp_throwable) { if ( $spock_feature_throwable != null) { @@ -38,6 +48,9 @@ public void $spock_feature_0_0() { } } finally { + if ( $spock_feature_throwable != null) { + ((org.spockframework.runtime.SpecificationContext) this.getSpecificationContext()).setCurrentBlock($spock_failedBlock) + } } } this.getSpecificationContext().getMockController().leaveScope() diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy index c1d899d23d..9b4a45cfc3 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/CleanupBlocksAstSpec/cleanup_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy @@ -13,7 +13,10 @@ public void $spock_feature_0_0() { def (java.lang.Object foobar, java.lang.Object b) = [null, null] java.lang.Throwable $spock_feature_throwable try { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) (foobar, b) = this.foobar() + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'println(foobar)', 6, 3, null, this, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), 'println'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), foobar)}, $spock_valueRecorder.realizeNas(4, false), false, 3) } @@ -21,14 +24,21 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'println(foobar)', 6, 3, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) } catch (java.lang.Throwable $spock_tmp_throwable) { $spock_feature_throwable = $spock_tmp_throwable throw $spock_tmp_throwable } finally { + org.spockframework.runtime.model.BlockInfo $spock_failedBlock = null try { + if ( $spock_feature_throwable != null) { + $spock_failedBlock = this.getSpecificationContext().getCurrentBlock() + } + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) foobar.size() + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) } catch (java.lang.Throwable $spock_tmp_throwable) { if ( $spock_feature_throwable != null) { @@ -38,6 +48,9 @@ public void $spock_feature_0_0() { } } finally { + if ( $spock_feature_throwable != null) { + ((org.spockframework.runtime.SpecificationContext) this.getSpecificationContext()).setCurrentBlock($spock_failedBlock) + } } } this.getSpecificationContext().getMockController().leaveScope() diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/multi_parameterization.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/multi_parameterization.groovy index 20b6d371e4..201d1c5fd8 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/multi_parameterization.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/multi_parameterization.groovy @@ -8,6 +8,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'a == b', 1, 83, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), a) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), b))) } @@ -15,6 +16,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'a == b', 1, 83, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/nested_multi_parameterization.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/nested_multi_parameterization.groovy index 9fe8ae6883..215215d6ce 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/nested_multi_parameterization.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataAstSpec/nested_multi_parameterization.groovy @@ -8,6 +8,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'a == b', 1, 83, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), a) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), b))) } @@ -15,6 +16,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'a == b', 1, 83, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[0].groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[0].groovy index 140341467e..3ad733861b 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[0].groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[0].groovy @@ -7,6 +7,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang.Object c) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'true', 2, 7, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true)) } @@ -14,6 +15,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'true', 2, 7, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[1].groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[1].groovy index 140341467e..3ad733861b 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[1].groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[1].groovy @@ -7,6 +7,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang.Object c) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'true', 2, 7, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true)) } @@ -14,6 +15,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'true', 2, 7, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[2].groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[2].groovy index 140341467e..3ad733861b 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[2].groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/data_tables_with__separators_can_be_combined-[2].groovy @@ -7,6 +7,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang.Object c) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'true', 2, 7, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true)) } @@ -14,6 +15,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b, java.lang org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'true', 2, 7, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/filter_block_becomes_its_own_method.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/filter_block_becomes_its_own_method.groovy index a6dda8e53b..eb157c19b4 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/filter_block_becomes_its_own_method.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/filter_block_becomes_its_own_method.groovy @@ -7,6 +7,7 @@ class ASpec extends Specification { public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'true', 2, 7, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), true)) } @@ -14,6 +15,7 @@ public void $spock_feature_0_0(java.lang.Object a, java.lang.Object b) { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'true', 2, 7, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) this.getSpecificationContext().getMockController().leaveScope() } diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsAsSet_is_transformed_correctly.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsAsSet_is_transformed_correctly.groovy index 1e090f19a3..e11a1a15e8 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsAsSet_is_transformed_correctly.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsAsSet_is_transformed_correctly.groovy @@ -8,7 +8,10 @@ class ASpec extends Specification { public void $spock_feature_0_0() { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object x = [1] + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'x =~ [1]', 4, 9, null, org.spockframework.runtime.SpockRuntime, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), 'matchCollectionsAsSet'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), x), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(3), [$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), 1)])}, $spock_valueRecorder.realizeNas(6, false), false, 5) } @@ -16,6 +19,7 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'x =~ [1]', 4, 9, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsInAnyOrder_is_transformed_correctly.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsInAnyOrder_is_transformed_correctly.groovy index 7973626ae8..6e11bd81a6 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsInAnyOrder_is_transformed_correctly.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/collection_condition_matchCollectionsInAnyOrder_is_transformed_correctly.groovy @@ -8,7 +8,10 @@ class ASpec extends Specification { public void $spock_feature_0_0() { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object x = [1] + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyMethodCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'x ==~ [1]', 4, 9, null, org.spockframework.runtime.SpockRuntime, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), 'matchCollectionsInAnyOrder'), new java.lang.Object[]{$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), x), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(3), [$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), 1)])}, $spock_valueRecorder.realizeNas(6, false), false, 5) } @@ -16,6 +19,7 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'x ==~ [1]', 4, 9, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_find_conditions_are_transformed_correctly.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_find_conditions_are_transformed_correctly.groovy index 867b2cac29..00647881b6 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_find_conditions_are_transformed_correctly.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_find_conditions_are_transformed_correctly.groovy @@ -8,7 +8,10 @@ class ASpec extends Specification { public void $spock_feature_0_0() { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object x = '[1]' + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'x =~ /\\d/', 4, 9, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), org.spockframework.runtime.SpockRuntime.matchCollectionsAsSet($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), x), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), '\\d')))) } @@ -16,6 +19,7 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'x =~ /\\d/', 4, 9, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_match_conditions_are_transformed_correctly.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_match_conditions_are_transformed_correctly.groovy index 63bc98d5d2..4ce13e20d9 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_match_conditions_are_transformed_correctly.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/CollectionConditionAstSpec/regex_match_conditions_are_transformed_correctly.groovy @@ -8,7 +8,10 @@ class ASpec extends Specification { public void $spock_feature_0_0() { org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder() + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) java.lang.Object x = 'a1b' + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) try { org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'x ==~ /a\\db/', 4, 9, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), org.spockframework.runtime.SpockRuntime.matchCollectionsInAnyOrder($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), x), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), 'a\\db')))) } @@ -16,6 +19,7 @@ public void $spock_feature_0_0() { org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'x ==~ /a\\db/', 4, 9, null, $spock_condition_throwable)} finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference.groovy index c76b5be927..2a5315379b 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference.groovy @@ -9,6 +9,7 @@ public java.lang.Object foobar() { public void $spock_feature_0_0() { java.lang.Object foobar + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) this.getSpecificationContext().setThrownException(null) try { foobar = this.foobar() @@ -18,7 +19,10 @@ public void $spock_feature_0_0() { } finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) this.thrownImpl(null, null, java.lang.IllegalStateException) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy index 9f3f3590e9..9a592abdd8 100644 --- a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/ExceptionConditionsAstSpec/thrown_rewrite_keeps_correct_method_reference_for_multi_assignments.groovy @@ -9,6 +9,7 @@ public java.lang.Object foobar() { public void $spock_feature_0_0() { def (java.lang.Object foobar, java.lang.Object b) = [null, null] + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) this.getSpecificationContext().setThrownException(null) try { (foobar, b) = this.foobar() @@ -18,7 +19,10 @@ public void $spock_feature_0_0() { } finally { } + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) this.thrownImpl(null, null, java.lang.IllegalStateException) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) this.getSpecificationContext().getMockController().leaveScope() } /*--------- end::snapshot[] ---------*/ diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/mock/MocksAstSpec/simple_interaction.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/mock/MocksAstSpec/simple_interaction.groovy new file mode 100644 index 0000000000..4a79964dc0 --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/mock/MocksAstSpec/simple_interaction.groovy @@ -0,0 +1,24 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { + def "aFeature"() { +/*--------- tag::snapshot[] ---------*/ +@org.spockframework.runtime.model.FeatureMetadata(name = 'a feature', ordinal = 0, line = 1, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHEN, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = [])], parameterNames = []) +public void $spock_feature_0_0() { + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 0) + java.util.List list = this.MockImpl('list', java.util.List) + this.getSpecificationContext().getMockController().enterScope() + this.getSpecificationContext().getMockController().addInteraction(new org.spockframework.mock.runtime.InteractionBuilder(8, 5, '1 * list.add(1)').setFixedCount(1).addEqualTarget(list).addEqualMethodName('add').setArgListKind(true, false).addEqualArg(1).build()) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 0) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 1) + list.add(1) + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 1) + org.spockframework.runtime.SpockRuntime.callBlockEntered(this.getSpecificationContext(), 2) + this.getSpecificationContext().getMockController().leaveScope() + org.spockframework.runtime.SpockRuntime.callBlockExited(this.getSpecificationContext(), 2) + this.getSpecificationContext().getMockController().leaveScope() +} +/*--------- end::snapshot[] ---------*/ + } +} diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_provider_with_asserting_closure_produces_error_rethrower_variable_in_data_provider_method.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_provider_with_asserting_closure_produces_error_rethrower_variable_in_data_provider_method.groovy new file mode 100644 index 0000000000..f5929cc270 --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_provider_with_asserting_closure_produces_error_rethrower_variable_in_data_provider_method.groovy @@ -0,0 +1,30 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { +/*--------- tag::snapshot[] ---------*/ +public void $spock_feature_0_0(java.lang.Object dataPipe, java.lang.Object dataVariable) { + this.getSpecificationContext().getMockController().leaveScope() +} + +public java.lang.Object $spock_feature_0_0prov0() { + org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE + return [{ -> + org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder() + try { + org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder1.reset(), 'true', 2, 29, null, $spock_valueRecorder1.record($spock_valueRecorder1.startRecordingValue(0), true)) + } + catch (java.lang.Throwable $spock_condition_throwable) { + org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder1, 'true', 2, 29, null, $spock_condition_throwable)} + finally { + } + }] +} + +public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { + java.lang.Object dataPipe = (( $spock_p0 ) as java.lang.Object) + java.lang.Object dataVariable = ((null) as java.lang.Object) + return new java.lang.Object[]{ dataPipe , dataVariable } +} +/*--------- end::snapshot[] ---------*/ +} diff --git a/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_variable_with_asserting_closure_produces_error_rethrower_variable_in_data_processor_method.groovy b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_variable_with_asserting_closure_produces_error_rethrower_variable_in_data_processor_method.groovy new file mode 100644 index 0000000000..97a553191b --- /dev/null +++ b/spock-specs/src/test/resources/snapshots/org/spockframework/smoke/parameterization/DataProviders/data_variable_with_asserting_closure_produces_error_rethrower_variable_in_data_processor_method.groovy @@ -0,0 +1,30 @@ +package aPackage +import spock.lang.* + +class ASpec extends Specification { +/*--------- tag::snapshot[] ---------*/ +public void $spock_feature_0_0(java.lang.Object dataPipe, java.lang.Object dataVariable) { + this.getSpecificationContext().getMockController().leaveScope() +} + +public java.lang.Object $spock_feature_0_0prov0() { + return [null] +} + +public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0) { + org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE + java.lang.Object dataPipe = (( $spock_p0 ) as java.lang.Object) + java.lang.Object dataVariable = (({ -> + org.spockframework.runtime.ValueRecorder $spock_valueRecorder1 = new org.spockframework.runtime.ValueRecorder() + try { + org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder1.reset(), 'true', 3, 31, null, $spock_valueRecorder1.record($spock_valueRecorder1.startRecordingValue(0), true)) + } + catch (java.lang.Throwable $spock_condition_throwable) { + org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder1, 'true', 3, 31, null, $spock_condition_throwable)} + finally { + } + }) as java.lang.Object) + return new java.lang.Object[]{ dataPipe , dataVariable } +} +/*--------- end::snapshot[] ---------*/ +} From 0a4b1513d4c038e52bfe7adfbc9cdc44337eb231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Fri, 27 Dec 2024 16:54:28 +0100 Subject: [PATCH 4/5] Write GitHub Actions workflow files in Kotlin instead of YAML (#1630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now a 1:1 translation from previous Yaml. --------- Co-authored-by: Leonard Brünings --- .editorconfig | 7 + .github/actions/setup-jdks/action.yml | 2 +- .github/workflows/README.adoc | 98 ++++++++ .github/workflows/branches-and-prs.main.kts | 117 +++++++++ .github/workflows/branches-and-prs.yml | 155 ++++++++---- .github/workflows/codeql-analysis.main.kts | 132 ++++++++++ .github/workflows/codeql-analysis.yml | 96 ++++--- .github/workflows/common.main.kts | 172 +++++++++++++ .github/workflows/docs-pr.main.kts | 99 ++++++++ .github/workflows/docs-pr.yml | 77 +++--- .github/workflows/release.main.kts | 182 ++++++++++++++ .github/workflows/release.yml | 238 +++++++++++------- .../preprocess-workflows.gradle | 17 ++ .../gradle/PreprocessWorkflowsPlugin.groovy | 132 ++++++++++ build-logic/settings.gradle | 1 + build.gradle | 7 +- gradle.properties | 4 + 17 files changed, 1306 insertions(+), 230 deletions(-) create mode 100644 .github/workflows/README.adoc create mode 100755 .github/workflows/branches-and-prs.main.kts create mode 100755 .github/workflows/codeql-analysis.main.kts create mode 100755 .github/workflows/common.main.kts create mode 100755 .github/workflows/docs-pr.main.kts create mode 100755 .github/workflows/release.main.kts create mode 100644 build-logic/preprocess-workflows/preprocess-workflows.gradle create mode 100644 build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy diff --git a/.editorconfig b/.editorconfig index af6e9e0d27..9ed5bb081a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,10 @@ indent_size = 2 # The file contains important whitespace at the end of the line in a multi-line string. # and editorconfig doesn't seem to respect multi-line strings. trim_trailing_whitespace = false + +[*.{kt,kts}] +indent_size = 4 +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ktlint_code_style = intellij_idea +ktlint_standard_function-signature = disabled diff --git a/.github/actions/setup-jdks/action.yml b/.github/actions/setup-jdks/action.yml index 5afb6795da..eaf6567301 100644 --- a/.github/actions/setup-jdks/action.yml +++ b/.github/actions/setup-jdks/action.yml @@ -21,7 +21,7 @@ runs: uses: actions/setup-java@v4 with: # Temurin JDK 8 for macos on ARM is not available: https://github.com/adoptium/adoptium/issues/96 - distribution: ${{ runner.os == 'macOS' && 'zulu' || 'temurin' }} + distribution: ${{ ((runner.os == 'macOS') && (runner.arch == 'ARM64')) && 'zulu' || 'temurin' }} java-version: 8 - name: Prepare JDK8 env var shell: bash diff --git a/.github/workflows/README.adoc b/.github/workflows/README.adoc new file mode 100644 index 0000000000..379f4b1171 --- /dev/null +++ b/.github/workflows/README.adoc @@ -0,0 +1,98 @@ +== The YAML workflow files vs. the `*.main.kts` files + +The YAML workflow files are generated from the `*.main.kts` files. + +These use the https://github.com/typesafegithub/github-workflows-kt[github-workflows-kt] +Kotlin DSL library to conveniently and type-safely write GitHub Action workflow files. + +As there is no official built-in support in GitHub Actions yet until +https://github.com/orgs/community/discussions/15904 is considered, the YAML files +need to be generated manually. + +There is a safeguard check in all the generated files that this is not forgotten. +Running a workflow where the according `*.main.kts` produces a different output will +fail the execution. Additionally, the workflow that runs for pull requests checks +the consistency of all the YAML files as not all are run for pull requests. + + + +== Ways to generate the YAML workflow files + +There are multiple ways to generate the YAML files and all of them are fine, +but be aware of the last one of the caveats below if you are not using the Gradle method: + +* If you are in a `sh` derivate like e.g. `bash` and Kotlin is installed and + available in the `PATH`, you can just call the `*.main.kts` script like any + other shell script: ++ +[source,bash] +---- +$ ./release.main.kts +---- + +* If Kotlin is installed somewhere you can call it with the `*.main.kts` script + as argument: ++ +[source,bash] +---- +$ path/to/kotlin release.main.kts +---- + +* From the IDE you can create a run configuration that executes the `*.main.kts` script. + +* There is a Gradle task `preprocessWorkflows` that generates all YAML files from the + according `*.main.kts` files. Additionally, there is also one task per workflow to + only generate that one: ++ +[source,bash] +---- +$ ./gradlew preprocessReleaseWorkflow +$ ./gradlew preprocessWorkflows +---- + + + +== Caveats + +There are currently three known caveats with the approach we follow. + +* https://youtrack.jetbrains.com/issue/KTIJ-16532 ++ +If you navigate to a file in the dependencies, only a decompiled file is opened, +even though the source JAR would be available. Also the quick documentation is missing. ++ +This can easily by mitigated by attaching the library to the normal project +dependencies while having the need to navigate the source files or while editing them, +which makes them properly viewable and documentation displayable in the editor. + +* https://youtrack.jetbrains.com/issue/KTIJ-14580 ++ +We use `@file:Import` to reduce code duplication by having common code in a common file. +Unfortunately, this triggers a Kotlin IntelliJ plugin bug where the imported file cannot +be loaded properly and so the things supplied by it like dependencies or common functions +are not available. This makes most of the workflow `*.main.kts` files red as hell in the +IDE currently. ++ +To reduce risk for eye-cancer while reading the `*.main.kts` scripts or to be able to +sanely edit them, temporarily add the `@file:DependsOn` from the imported file to the +importing file and wait a second, then remove the line again once you are done. + +* https://youtrack.jetbrains.com/issue/KT-42101 ++ +We use `@file:Import` to reduce code duplication by having common code in a common file. +Unfortunately, this triggers a Kotlin bug where the compilation cache becomes confused +if the imported file is changed without the importing file being changed too. ++ +If only the imported file is changed, it could happen that an old version is used, +or it could also happen that classes added by a `@file:DependsOn` in the imported file +are not available to the importing file. So if there was a change in the imported file, +you either need to also change the importing file, or to properly execute the script, +you need to delete the stale entry from the compilation cache which can be found at for example +`~/.cache/main.kts.compiled.cache/` on Linux and `%LOCALAPPDATA%\main.kts.compiled.cache\` +on Windows. Alternatively, you can also delete the whole cache directory. ++ +Another option is to disable the compilation cache for the execution by setting the +environment variable `KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR` or the system property +`kotlin.main.kts.compiled.scripts.cache.dir` to an empty value, depending on the run +method you chose. The Gradle tasks already do that, so when using the Gradle tasks you +do not have this problem and it just works. diff --git a/.github/workflows/branches-and-prs.main.kts b/.github/workflows/branches-and-prs.main.kts new file mode 100755 index 0000000000..9025113acd --- /dev/null +++ b/.github/workflows/branches-and-prs.main.kts @@ -0,0 +1,117 @@ +#!/usr/bin/env kotlin + +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Import("common.main.kts") +@file:Repository("https://bindings.krzeminski.it/") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("codecov:codecov-action:v5") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.Checkout.FetchDepth +import io.github.typesafegithub.workflows.actions.codecov.CodecovAction +import io.github.typesafegithub.workflows.domain.Concurrency +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.triggers.MergeGroup +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.github +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Verify Branches and PRs", + on = listOf( + Push( + branchesIgnore = listOf( + "master", + "gh-pages" + ) + ), + PullRequest(), + MergeGroup() + ), + sourceFile = __FILE__, + targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", + // https://stackoverflow.com/a/72408109/16358266 + concurrency = Concurrency( + group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", + cancelInProgress = true + ) +) { + job( + id = "check_all_workflow_yaml_consistency", + name = "Check all Workflow YAML Consistency", + runsOn = UbuntuLatest + ) { + uses( + name = "Checkout Repository", + action = Checkout() + ) + run( + name = "Regenerate all Workflow YAMLs", + command = """find .github/workflows -mindepth 1 -maxdepth 1 -name '*.main.kts' -exec {} \;""" + ) + run( + name = "Check for Modifications", + command = """ + git add --intent-to-add . + git diff --exit-code + """.trimIndent() + ) + } + + job( + id = "build-and-verify", + name = "Build and Verify", + runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), + strategy = Strategy( + matrix = Matrix.full + ) + ) { + uses( + name = "Checkout Repository", + action = Checkout( + // Codecov needs fetch-depth > 1 + fetchDepth = FetchDepth.Value(2) + ) + ) + uses( + name = "Set up JDKs", + action = SetupBuildEnv( + additionalJavaVersion = expr(Matrix.javaVersion) + ) + ) + run( + name = "Build Spock", + command = listOf( + "./gradlew", + "--stacktrace", + "ghActionsBuild", + """"-Dvariant=${expr(Matrix.variant)}"""", + """"-DjavaVersion=${expr(Matrix.javaVersion)}"""" + ).joinToString(" "), + // secrets are not injected for pull requests + env = commonCredentials + ) + uses( + name = "Upload to Codecov.io", + action = CodecovAction() + ) + } +} diff --git a/.github/workflows/branches-and-prs.yml b/.github/workflows/branches-and-prs.yml index ef7bfd8d53..67a09b6c3c 100644 --- a/.github/workflows/branches-and-prs.yml +++ b/.github/workflows/branches-and-prs.yml @@ -1,69 +1,114 @@ -name: 'Verify Branches and PRs' +# This file was generated using Kotlin DSL (.github/workflows/branches-and-prs.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt +name: 'Verify Branches and PRs' on: push: branches-ignore: - - master - - gh-pages - pull_request: - merge_group: - -# https://stackoverflow.com/a/72408109/16358266 + - 'master' + - 'gh-pages' + pull_request: {} + merge_group: {} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: '${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}' cancel-in-progress: true - jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/branches-and-prs.yml'' && ''.github/workflows/branches-and-prs.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/branches-and-prs.yml''' + check_all_workflow_yaml_consistency: + name: 'Check all Workflow YAML Consistency' + runs-on: 'ubuntu-latest' + needs: + - 'check_yaml_consistency' + steps: + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Regenerate all Workflow YAMLs' + run: 'find .github/workflows -mindepth 1 -maxdepth 1 -name ''*.main.kts'' -exec {} \;' + - id: 'step-2' + name: 'Check for Modifications' + run: |- + git add --intent-to-add . + git diff --exit-code build-and-verify: - runs-on: ${{ matrix.os }} + name: 'Build and Verify' + runs-on: '${{ matrix.os }}' + needs: + - 'check_yaml_consistency' strategy: fail-fast: false matrix: - variant: ['2.5', '3.0', '4.0'] - java: ['8', '11', '17', '21', '23'] - os: ['ubuntu-latest'] + variant: + - '2.5' + - '3.0' + - '4.0' + java: + - '8' + - '11' + - '17' + - '21' + - '23' + os: + - 'ubuntu-latest' exclude: - - variant: '2.5' - java: '17' - os: 'ubuntu-latest' - - variant: '2.5' - java: '21' - os: 'ubuntu-latest' - - variant: '2.5' - java: '23' - os: 'ubuntu-latest' + - variant: '2.5' + java: '17' + os: 'ubuntu-latest' + - variant: '2.5' + java: '21' + os: 'ubuntu-latest' + - variant: '2.5' + java: '23' + os: 'ubuntu-latest' include: - - variant: '2.5' - java: '8' - os: 'windows-latest' - - variant: '3.0' - java: '8' - os: 'windows-latest' - - variant: '4.0' - java: '8' - os: 'windows-latest' - - variant: '2.5' - java: '8' - os: 'macos-latest' - - variant: '3.0' - java: '8' - os: 'macos-latest' - - variant: '4.0' - java: '8' - os: 'macos-latest' + - variant: '2.5' + java: '8' + os: 'windows-latest' + - variant: '3.0' + java: '8' + os: 'windows-latest' + - variant: '4.0' + java: '8' + os: 'windows-latest' + - variant: '2.5' + java: '8' + os: 'macos-latest' + - variant: '3.0' + java: '8' + os: 'macos-latest' + - variant: '4.0' + java: '8' + os: 'macos-latest' steps: - - uses: actions/checkout@v4 - with: - # Codecov needs fetch-depth > 1 - fetch-depth: 2 - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - with: - additional-java-version: ${{ matrix.java }} - - name: 'Build Spock' - # secrets are not injected for pull requests - env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: ./gradlew --stacktrace ghActionsBuild "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" - - name: 'Upload to Codecov.io' - uses: codecov/codecov-action@v5 + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + with: + fetch-depth: '2' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + with: + additional-java-version: '${{ matrix.java }}' + - id: 'step-2' + name: 'Build Spock' + env: + DEVELOCITY_ACCESS_KEY: '${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}' + run: './gradlew --stacktrace ghActionsBuild "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}"' + - id: 'step-3' + name: 'Upload to Codecov.io' + uses: 'codecov/codecov-action@v5' diff --git a/.github/workflows/codeql-analysis.main.kts b/.github/workflows/codeql-analysis.main.kts new file mode 100755 index 0000000000..1c9e1e5562 --- /dev/null +++ b/.github/workflows/codeql-analysis.main.kts @@ -0,0 +1,132 @@ +#!/usr/bin/env kotlin + +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Import("common.main.kts") +@file:Repository("https://bindings.krzeminski.it/") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("github:codeql-action__analyze:v3") +@file:DependsOn("github:codeql-action__init:v3") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.github.CodeqlActionAnalyze +import io.github.typesafegithub.workflows.actions.github.CodeqlActionInit +import io.github.typesafegithub.workflows.domain.Concurrency +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.triggers.Cron +import io.github.typesafegithub.workflows.domain.triggers.MergeGroup +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.domain.triggers.Schedule +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.github +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Code scanning - action", + on = listOf( + Push( + branches = listOf("!dependabot/**") + ), + PullRequest(), + MergeGroup(), + Schedule( + listOf( + Cron( + minute = "0", + hour = "15", + dayWeek = "TUE" + ) + ) + ) + ), + sourceFile = __FILE__, + targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", + // https://stackoverflow.com/a/72408109/16358266 + concurrency = Concurrency( + group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", + cancelInProgress = true + ) +) { + job( + id = "codeql-build", + name = "CodeQL-Build", + // CodeQL runs on UbuntuLatest, WindowsLatest, and MacOSLatest + runsOn = UbuntuLatest, + strategy = Strategy( + matrix = Matrix( + variants = Matrix.axes.variants + ) + ) + ) { + uses( + name = "Checkout Repository", + action = Checkout() + ) + // Manually added: Install and setup JDK + uses( + name = "Set up JDKs", + action = SetupBuildEnv() + ) + // Initializes the CodeQL tools for scanning + uses( + name = "Initialize CodeQL", + action = CodeqlActionInit( + // Override language selection by uncommenting this and choosing your languages + // languages = listOf("go", "javascript", "csharp", "python", "cpp", "java"), + ) + ) + // Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + // If this step fails, then you should remove it and run the build manually (see below). + // uses( + // name = "Autobuild", + // action = CodeqlActionAutobuild() + // ) + // + // ℹ️ Command-line programs to run using the OS shell. + // 📚 https://git.io/JvXDl + // + // ✏️ If the Autobuild fails above, remove it and uncomment the following + // three lines and modify them (or add more) to build your code if your + // project uses a compiled language + // + // run( + // command = """ + // make bootstrap + // make release + // """.trimIndent() + // ) + + // Manually added: build + // we have to disable build cache for now as it seems to be necessary for the compiler to run during the build + // https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/troubleshooting-the-codeql-workflow#no-code-found-during-the-build + run( + name = "Build Spock Classes", + command = listOf( + "./gradlew", + "--stacktrace", + "--no-build-cache", + "testClasses", + """"-Dvariant=${expr(Matrix.variant)}"""" + ).joinToString(" ") + ) + uses( + name = "Perform CodeQL Analysis", + action = CodeqlActionAnalyze() + ) + } +} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a9d632bb86..755b3faf1b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,62 +1,58 @@ -name: "Code scanning - action" +# This file was generated using Kotlin DSL (.github/workflows/codeql-analysis.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt +name: 'Code scanning - action' on: push: branches: - - '!dependabot/**' - pull_request: - merge_group: + - '!dependabot/**' + pull_request: {} + merge_group: {} schedule: - - cron: '0 15 * * 2' - -# https://stackoverflow.com/a/72408109/16358266 + - cron: '0 15 * * TUE' concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: '${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}' cancel-in-progress: true - jobs: - CodeQL-Build: - # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest - runs-on: ubuntu-latest + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/codeql-analysis.yml'' && ''.github/workflows/codeql-analysis.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/codeql-analysis.yml''' + codeql-build: + name: 'CodeQL-Build' + runs-on: 'ubuntu-latest' + needs: + - 'check_yaml_consistency' strategy: fail-fast: false matrix: - variant: [ '2.5', '3.0', '4.0' ] + variant: + - '2.5' + - '3.0' + - '4.0' steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Manually added: Install and setup JDK - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below). - #- name: Autobuild - # uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following - # three lines and modify them (or add more) to build your code if your - # project uses a compiled language - - #- run: | - # make bootstrap - # make release - - # Manually added: build - # we have to disable build cache for now as it seems to be necessary for the compiler to run during the build - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/troubleshooting-the-codeql-workflow#no-code-found-during-the-build - - name: 'Build Spock Classes' - run: ./gradlew --stacktrace --no-build-cache testClasses "-Dvariant=${{ matrix.variant }}" - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + - id: 'step-2' + name: 'Initialize CodeQL' + uses: 'github/codeql-action/init@v3' + - id: 'step-3' + name: 'Build Spock Classes' + run: './gradlew --stacktrace --no-build-cache testClasses "-Dvariant=${{ matrix.variant }}"' + - id: 'step-4' + name: 'Perform CodeQL Analysis' + uses: 'github/codeql-action/analyze@v3' diff --git a/.github/workflows/common.main.kts b/.github/workflows/common.main.kts new file mode 100755 index 0000000000..894d471bbb --- /dev/null +++ b/.github/workflows/common.main.kts @@ -0,0 +1,172 @@ +#!/usr/bin/env kotlin + +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Repository("https://repo.maven.apache.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.0.1") + +import io.github.typesafegithub.workflows.domain.Job +import io.github.typesafegithub.workflows.domain.JobOutputs.EMPTY +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.actions.Action.Outputs +import io.github.typesafegithub.workflows.domain.actions.LocalAction +import io.github.typesafegithub.workflows.dsl.JobBuilder +import io.github.typesafegithub.workflows.dsl.WorkflowBuilder +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.secrets +import io.github.typesafegithub.workflows.dsl.expressions.expr +import java.util.Properties + +val GRADLE_ENTERPRISE_ACCESS_KEY by secrets + +val commonCredentials = mapOf( + "DEVELOCITY_ACCESS_KEY" to expr(GRADLE_ENTERPRISE_ACCESS_KEY) +) + +data class Strategy( + val failFast: Boolean? = false, + val matrix: Matrix? = null +) { + fun toCustomArguments() = mapOf( + *listOfNotNull( + failFast?.let { "fail-fast" to failFast }, + matrix?.let { "matrix" to matrix.toCustomArguments() } + ).toTypedArray() + ) +} + +data class Matrix( + val operatingSystems: List? = null, + val variants: List? = null, + val javaVersions: List? = null, + val exclude: (Element.() -> Boolean)? = null, + val includes: List? = null +) { + private val originalElements by lazy { + (operatingSystems ?: listOf(null)) + .map { Element(operatingSystem = it) } + .flatMap { element -> (variants ?: listOf(null)).map { element.copy(variant = it) } } + .flatMap { element -> (javaVersions ?: listOf(null)).map { element.copy(javaVersion = it) } } + } + + fun toCustomArguments() = mapOf( + *listOfNotNull( + variants?.let { "variant" to variants }, + javaVersions?.let { "java" to javaVersions }, + operatingSystems?.let { "os" to operatingSystems }, + exclude?.let { + "exclude" to originalElements + .filter(exclude) + .map { it.toCustomArguments() } + }, + includes?.let { "include" to includes.map { it.toCustomArguments() } } + ).toTypedArray() + ) + + data class Axes( + val javaVersions: List, + val variants: List + ) + + data class Element( + val operatingSystem: String? = null, + val variant: String? = null, + val javaVersion: String? = null + ) { + fun toCustomArguments() = mapOf( + *listOfNotNull( + variant?.let { "variant" to variant }, + javaVersion?.let { "java" to javaVersion }, + operatingSystem?.let { "os" to operatingSystem } + ).toTypedArray() + ) + } + + companion object { + val operatingSystem = "matrix.os" + val variant = "matrix.variant" + val javaVersion = "matrix.java" + } +} + +fun WorkflowBuilder.job( + id: String, + name: String? = null, + runsOn: RunnerType, + needs: List> = emptyList(), + condition: String? = null, + strategy: Strategy? = null, + simpleStrategy: Map>? = null, + block: JobBuilder.() -> Unit +): Job = job( + id = id, + name = name, + runsOn = runsOn, + needs = needs, + condition = condition, + strategyMatrix = simpleStrategy, + _customArguments = mapOf( + *listOfNotNull( + strategy?.let { "strategy" to strategy.toCustomArguments() } + ).toTypedArray() + ), + block = block +) + +val Matrix.Companion.full + get() = Matrix( + operatingSystems = listOf("ubuntu-latest"), + variants = axes.variants, + javaVersions = axes.javaVersions + "23", + exclude = { (variant == "2.5") && (javaVersion!!.toInt() >= 17) }, + includes = listOf("windows-latest", "macos-latest") + .map { + Matrix.Element( + operatingSystem = it, + javaVersion = axes.javaVersions.first() + ) + } + .flatMap { element -> axes.variants.map { element.copy(variant = it) } } + ) + +val Matrix.Companion.axes by lazy { + Properties().let { properties -> + __FILE__ + .parentFile + .resolve("../../gradle.properties") + .inputStream() + .use { properties.load(it) } + + Matrix.Axes( + properties.getList("javaVersionsList"), + properties.getList("variantsList") + ) + } +} + +fun Properties.getList(key: String) = + getProperty(key).trim().split("""\s*+,\s*+""".toRegex()) + +data class SetupBuildEnv( + val additionalJavaVersion: String? = null +) : LocalAction("./.github/actions/setup-build-env") { + override fun toYamlArguments() = + additionalJavaVersion + ?.let { linkedMapOf("additional-java-version" to it) } + ?: linkedMapOf() + + override fun buildOutputObject(stepId: String): Outputs = Outputs(stepId) +} diff --git a/.github/workflows/docs-pr.main.kts b/.github/workflows/docs-pr.main.kts new file mode 100755 index 0000000000..1334f7566f --- /dev/null +++ b/.github/workflows/docs-pr.main.kts @@ -0,0 +1,99 @@ +#!/usr/bin/env kotlin + +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Import("common.main.kts") +@file:Repository("https://bindings.krzeminski.it/") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("actions:upload-artifact:v4") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.Checkout.FetchDepth +import io.github.typesafegithub.workflows.actions.actions.UploadArtifact +import io.github.typesafegithub.workflows.domain.Concurrency +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.triggers.MergeGroup +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.github +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Verify Docs", + on = listOf( + Push( + branchesIgnore = listOf( + "master", + "gh-pages" + ) + ), + PullRequest(), + MergeGroup() + ), + sourceFile = __FILE__, + targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", + // https://stackoverflow.com/a/72408109/16358266 + concurrency = Concurrency( + group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", + cancelInProgress = true + ) +) { + job( + id = "docs-and-javadoc", + name = "Docs and JavaDoc", + runsOn = UbuntuLatest, + ) { + uses( + name = "Checkout Repository", + action = Checkout( + fetchDepth = FetchDepth.Value(1) + ) + ) + uses( + name = "Set up JDKs", + action = SetupBuildEnv( + additionalJavaVersion = Matrix.axes.javaVersions.last() + ) + ) + run( + name = "Install GraphViz", + command = "sudo apt update && sudo apt install --yes graphviz" + ) + run( + name = "Build Docs", + command = listOf( + "./gradlew", + "--stacktrace", + "asciidoctor", + "javadoc", + """"-Dvariant=${Matrix.axes.variants.last()}"""", + """"-DjavaVersion=${Matrix.axes.javaVersions.last()}"""" + ).joinToString(" ") + ) + uses( + name = "Archive and upload docs", + action = UploadArtifact( + name = "docs", + path = listOf( + "build/docs/**", + "build/javadoc/**" + ) + ) + ) + } +} diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 518b745d9d..dc5e4cda86 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -1,38 +1,59 @@ -name: 'Verify Docs' +# This file was generated using Kotlin DSL (.github/workflows/docs-pr.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt +name: 'Verify Docs' on: push: branches-ignore: - - master - - gh-pages - pull_request: - merge_group: - -# https://stackoverflow.com/a/72408109/16358266 + - 'master' + - 'gh-pages' + pull_request: {} + merge_group: {} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: '${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}' cancel-in-progress: true - jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/docs-pr.yml'' && ''.github/workflows/docs-pr.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/docs-pr.yml''' docs-and-javadoc: + name: 'Docs and JavaDoc' runs-on: 'ubuntu-latest' + needs: + - 'check_yaml_consistency' steps: - - uses: actions/checkout@v4 - with: - # Codecov needs fetch-depth > 1 - fetch-depth: 2 - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - with: - additional-java-version: 21 - - name: 'Install GraphViz' - run: sudo apt update && sudo apt install --yes graphviz - - name: 'Build Docs' - run: ./gradlew --stacktrace asciidoctor javadoc "-Dvariant=4.0" "-DjavaVersion=21" - - name: 'Archive and upload docs' - uses: actions/upload-artifact@v4 - with: - name: docs - path: | - build/docs/** - build/javadoc/** + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + with: + fetch-depth: '1' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + with: + additional-java-version: '21' + - id: 'step-2' + name: 'Install GraphViz' + run: 'sudo apt update && sudo apt install --yes graphviz' + - id: 'step-3' + name: 'Build Docs' + run: './gradlew --stacktrace asciidoctor javadoc "-Dvariant=4.0" "-DjavaVersion=21"' + - id: 'step-4' + name: 'Archive and upload docs' + uses: 'actions/upload-artifact@v4' + with: + name: 'docs' + path: |- + build/docs/** + build/javadoc/** diff --git a/.github/workflows/release.main.kts b/.github/workflows/release.main.kts new file mode 100755 index 0000000000..d7119dd767 --- /dev/null +++ b/.github/workflows/release.main.kts @@ -0,0 +1,182 @@ +#!/usr/bin/env kotlin + +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Import("common.main.kts") +@file:Repository("https://bindings.krzeminski.it/") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("codecov:codecov-action:v5") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.Checkout.FetchDepth +import io.github.typesafegithub.workflows.actions.codecov.CodecovAction +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.github +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.secrets +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Build and Release Spock", + on = listOf( + Push( + branches = listOf("master"), + tags = listOf("spock-*") + ) + ), + sourceFile = __FILE__, + targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml" +) { + val GITHUB_TOKEN by secrets + val SONATYPE_OSS_USER by secrets + val SONATYPE_OSS_PASSWORD by secrets + val SIGNING_GPG_PASSWORD by secrets + + val buildAndVerify = job( + id = "build-and-verify", + name = "Build and Verify", + runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), + condition = "${github.repository} == 'spockframework/spock'", + strategy = Strategy( + matrix = Matrix.full + ) + ) { + uses( + name = "Checkout Repository", + action = Checkout( + // Codecov needs fetch-depth > 1 + fetchDepth = FetchDepth.Value(2) + ) + ) + uses( + name = "Set up JDKs", + action = SetupBuildEnv( + additionalJavaVersion = expr(Matrix.javaVersion) + ) + ) + run( + name = "Build Spock", + command = listOf( + "./gradlew", + "--stacktrace", + "ghActionsBuild", + """"-Dvariant=${expr(Matrix.variant)}"""", + """"-DjavaVersion=${expr(Matrix.javaVersion)}"""", + "-Dscan.tag.main-build" + ).joinToString(" "), + env = commonCredentials + ) + run( + name = "Stop Daemon", + command = "./gradlew --stop" + ) + uses( + name = "Upload to Codecov.io", + action = CodecovAction() + ) + } + val releaseSpock = job( + id = "release-spock", + name = "Release Spock", + runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), + needs = listOf(buildAndVerify), + strategyMatrix = mapOf( + // publish needs to be done for all versions + "variant" to Matrix.axes.variants, + // publish needs the min supported java version + "java" to Matrix.axes.javaVersions.take(1), + "os" to listOf("ubuntu-latest") + ) + ) { + uses( + name = "Checkout Repository", + action = Checkout() + ) + uses( + name = "Set up JDKs", + action = SetupBuildEnv( + additionalJavaVersion = expr(Matrix.javaVersion) + ) + ) + run( + name = "Publish Spock", + command = listOf( + "./gradlew", + "--no-parallel", + "--stacktrace", + "ghActionsPublish", + """"-Dvariant=${expr(Matrix.variant)}"""", + """"-DjavaVersion=${expr(Matrix.javaVersion)}"""", + "-Dscan.tag.main-publish" + ).joinToString(" "), + env = mutableMapOf( + "GITHUB_TOKEN" to expr(GITHUB_TOKEN), + "SONATYPE_OSS_USER" to expr(SONATYPE_OSS_USER), + "SONATYPE_OSS_PASSWORD" to expr(SONATYPE_OSS_PASSWORD), + "SIGNING_PASSWORD" to expr(SIGNING_GPG_PASSWORD) + ).apply { putAll(commonCredentials) } + ) + } + job( + id = "publish-release-docs", + name = "Publish Release Docs", + runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), + needs = listOf(releaseSpock), + strategyMatrix = mapOf( + // docs need the highest variant + "variant" to Matrix.axes.variants.takeLast(1), + // docs need the highest java version + "java" to Matrix.axes.javaVersions.takeLast(1), + "os" to listOf("ubuntu-latest") + ) + ) { + uses( + name = "Checkout Repository", + action = Checkout() + ) + uses( + name = "Set up JDKs", + action = SetupBuildEnv( + additionalJavaVersion = expr(Matrix.javaVersion) + ) + ) + run( + name = "Create Temporary Branch", + command = "git checkout -b \"docs-\$GITHUB_SHA\"" + ) + run( + name = "Install GraphViz", + command = "sudo apt update && sudo apt install --yes graphviz" + ) + run( + name = "Publish Docs", + command = listOf( + "./gradlew", + "--no-parallel", + "--stacktrace", + "ghActionsDocs", + """"-Dvariant=${expr(Matrix.variant)}"""", + """"-DjavaVersion=${expr(Matrix.javaVersion)}"""", + "-Dscan.tag.main-docs" + ).joinToString(" "), + env = mutableMapOf( + "GITHUB_TOKEN" to expr(GITHUB_TOKEN) + ).apply { putAll(commonCredentials) } + ) + } +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07114e9e57..d1ad9b36c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,114 +1,166 @@ -name: 'Build and Release Spock' +# This file was generated using Kotlin DSL (.github/workflows/release.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt +name: 'Build and Release Spock' on: push: branches: - - master + - 'master' tags: - - 'spock-*' - + - 'spock-*' jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/release.yml'' && ''.github/workflows/release.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/release.yml''' build-and-verify: - runs-on: ${{ matrix.os }} - if: github.repository == 'spockframework/spock' + name: 'Build and Verify' + runs-on: '${{ matrix.os }}' + needs: + - 'check_yaml_consistency' + if: 'github.repository == ''spockframework/spock''' strategy: fail-fast: false matrix: - variant: ['2.5', '3.0', '4.0'] - java: [ '8', '11', '17', '21', '23' ] - os: [ 'ubuntu-latest' ] + variant: + - '2.5' + - '3.0' + - '4.0' + java: + - '8' + - '11' + - '17' + - '21' + - '23' + os: + - 'ubuntu-latest' exclude: - - variant: '2.5' - java: '17' - os: 'ubuntu-latest' - - variant: '2.5' - java: '21' - os: 'ubuntu-latest' - - variant: '2.5' - java: '23' - os: 'ubuntu-latest' + - variant: '2.5' + java: '17' + os: 'ubuntu-latest' + - variant: '2.5' + java: '21' + os: 'ubuntu-latest' + - variant: '2.5' + java: '23' + os: 'ubuntu-latest' include: - - variant: '2.5' - java: '8' - os: 'windows-latest' - - variant: '3.0' - java: '8' - os: 'windows-latest' - - variant: '4.0' - java: '8' - os: 'windows-latest' - - variant: '2.5' - java: '8' - os: 'macos-latest' - - variant: '3.0' - java: '8' - os: 'macos-latest' - - variant: '4.0' - java: '8' - os: 'macos-latest' + - variant: '2.5' + java: '8' + os: 'windows-latest' + - variant: '3.0' + java: '8' + os: 'windows-latest' + - variant: '4.0' + java: '8' + os: 'windows-latest' + - variant: '2.5' + java: '8' + os: 'macos-latest' + - variant: '3.0' + java: '8' + os: 'macos-latest' + - variant: '4.0' + java: '8' + os: 'macos-latest' steps: - - uses: actions/checkout@v4 - with: - # Codecov needs fetch-depth > 1 - fetch-depth: 2 - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - with: - additional-java-version: ${{ matrix.java }} - - name: 'Build Spock' - env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: ./gradlew --stacktrace ghActionsBuild "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" "-Dscan.tag.main-build" - - name: 'Stop Daemon' - run: | - ./gradlew --stop - - name: 'Upload to Codecov.io' - uses: codecov/codecov-action@v5 - + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + with: + fetch-depth: '2' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + with: + additional-java-version: '${{ matrix.java }}' + - id: 'step-2' + name: 'Build Spock' + env: + DEVELOCITY_ACCESS_KEY: '${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}' + run: './gradlew --stacktrace ghActionsBuild "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" -Dscan.tag.main-build' + - id: 'step-3' + name: 'Stop Daemon' + run: './gradlew --stop' + - id: 'step-4' + name: 'Upload to Codecov.io' + uses: 'codecov/codecov-action@v5' release-spock: - runs-on: ${{ matrix.os }} - needs: [ 'build-and-verify' ] + name: 'Release Spock' + runs-on: '${{ matrix.os }}' + needs: + - 'build-and-verify' + - 'check_yaml_consistency' strategy: matrix: - os: [ 'ubuntu-latest' ] - variant: [ '2.5', '3.0', '4.0' ] # publish needs to be done for all versions - java: [ '8' ] # publish needs the min supported java version + variant: + - '2.5' + - '3.0' + - '4.0' + java: + - '8' + os: + - 'ubuntu-latest' steps: - - uses: actions/checkout@v4 - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - with: - additional-java-version: ${{ matrix.java }} - - name: 'Publish Spock' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONATYPE_OSS_USER: ${{ secrets.SONATYPE_OSS_USER }} - SONATYPE_OSS_PASSWORD: ${{ secrets.SONATYPE_OSS_PASSWORD }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_GPG_PASSWORD }} - DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: ./gradlew --no-parallel --stacktrace ghActionsPublish "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" "-Dscan.tag.main-publish" - + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + with: + additional-java-version: '${{ matrix.java }}' + - id: 'step-2' + name: 'Publish Spock' + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + SONATYPE_OSS_USER: '${{ secrets.SONATYPE_OSS_USER }}' + SONATYPE_OSS_PASSWORD: '${{ secrets.SONATYPE_OSS_PASSWORD }}' + SIGNING_PASSWORD: '${{ secrets.SIGNING_GPG_PASSWORD }}' + DEVELOCITY_ACCESS_KEY: '${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}' + run: './gradlew --no-parallel --stacktrace ghActionsPublish "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" -Dscan.tag.main-publish' publish-release-docs: - runs-on: ${{ matrix.os }} - needs: ['release-spock'] + name: 'Publish Release Docs' + runs-on: '${{ matrix.os }}' + needs: + - 'release-spock' + - 'check_yaml_consistency' strategy: matrix: - os: ['ubuntu-latest'] - variant: ['4.0'] # docs need the highest variant - java: ['21'] # docs need the highest java version + variant: + - '4.0' + java: + - '21' + os: + - 'ubuntu-latest' steps: - - uses: actions/checkout@v4 - - name: 'Set up JDKs' - uses: ./.github/actions/setup-build-env - with: - additional-java-version: ${{ matrix.java }} - - name: 'Create Temporary Branch' - run: | - git checkout -b "docs-$GITHUB_SHA" - - name: Install GraphViz - run: sudo apt update && sudo apt install --yes graphviz - - name: 'Publish Docs' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: ./gradlew --no-parallel --stacktrace ghActionsDocs "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" "-Dscan.tag.main-docs" + - id: 'step-0' + name: 'Checkout Repository' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Set up JDKs' + uses: './.github/actions/setup-build-env' + with: + additional-java-version: '${{ matrix.java }}' + - id: 'step-2' + name: 'Create Temporary Branch' + run: 'git checkout -b "docs-$GITHUB_SHA"' + - id: 'step-3' + name: 'Install GraphViz' + run: 'sudo apt update && sudo apt install --yes graphviz' + - id: 'step-4' + name: 'Publish Docs' + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + DEVELOCITY_ACCESS_KEY: '${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}' + run: './gradlew --no-parallel --stacktrace ghActionsDocs "-Dvariant=${{ matrix.variant }}" "-DjavaVersion=${{ matrix.java }}" -Dscan.tag.main-docs' diff --git a/build-logic/preprocess-workflows/preprocess-workflows.gradle b/build-logic/preprocess-workflows/preprocess-workflows.gradle new file mode 100644 index 0000000000..23aff703d6 --- /dev/null +++ b/build-logic/preprocess-workflows/preprocess-workflows.gradle @@ -0,0 +1,17 @@ +plugins { + id 'groovy-gradle-plugin' + id 'idea' +} + +dependencies { + implementation('org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.20') +} + +gradlePlugin { + plugins { + preprocessWorkflowsPlugin { + id = 'org.spockframework.preprocess-workflows' + implementationClass = 'org.spockframework.gradle.PreprocessWorkflowsPlugin' + } + } +} diff --git a/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy b/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy new file mode 100644 index 0000000000..d04c8161fc --- /dev/null +++ b/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy @@ -0,0 +1,132 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.gradle + +import groovy.transform.CompileStatic +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.tasks.JavaExec +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaToolchainService +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.com.intellij.openapi.vfs.local.CoreLocalFileSystem +import org.jetbrains.kotlin.com.intellij.openapi.vfs.local.CoreLocalVirtualFile +import org.jetbrains.kotlin.com.intellij.psi.PsiManager +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtStringTemplateExpression + +import static org.jetbrains.kotlin.cli.common.CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY +import static org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles.JVM_CONFIG_FILES + +@CompileStatic +class PreprocessWorkflowsPlugin implements Plugin { + void apply(Project project) { + def kotlinCompilerClasspath = project.configurations.detachedConfiguration( + project.dependencies.create('org.jetbrains.kotlin:kotlin-compiler:1.8.20'), + project.dependencies.create('org.jetbrains.kotlin:kotlin-scripting-compiler:1.8.20') + ) + def kotlinScriptClasspath = project.configurations.detachedConfiguration( + project.dependencies.create('org.jetbrains.kotlin:kotlin-main-kts:1.8.20') { ModuleDependency it -> + it.transitive = false + } + ) + + def preprocessWorkflows = project.tasks.register('preprocessWorkflows') { + it.group = 'github actions' + } + project.file('.github/workflows').eachFileMatch(~/.*\.main\.kts$/) { workflowScript -> + def workflowName = workflowScript.name - ~/\.main\.kts$/ + def pascalCasedWorkflowName = workflowName + .replaceAll(/-\w/) { String it -> it[1].toUpperCase() } + .replaceFirst(/^\w/) { String it -> it[0].toUpperCase() } + def preprocessWorkflow = project.tasks.register("preprocess${pascalCasedWorkflowName}Workflow", JavaExec) { + it.group = 'github actions' + + it.inputs + .file(workflowScript) + .withPropertyName('workflowScript') + it.inputs + .files(getImportedFiles(project.file(workflowScript))) + .withPropertyName("importedFiles") + it.outputs + .file(new File(workflowScript.parent, "${workflowName}.yml")) + .withPropertyName('workflowFile') + + it.javaLauncher.set project.extensions.getByType(JavaToolchainService).launcherFor { + it.languageVersion.set(JavaLanguageVersion.of(17)) + } + it.classpath(kotlinCompilerClasspath) + it.mainClass.set 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler' + it.args('-no-stdlib', '-no-reflect') + it.args('-classpath', kotlinScriptClasspath.asPath) + it.args('-script', workflowScript.absolutePath) + + // work-around for https://youtrack.jetbrains.com/issue/KT-42101 + it.systemProperty('kotlin.main.kts.compiled.scripts.cache.dir', '') + } + preprocessWorkflows.configure { + it.dependsOn(preprocessWorkflow) + } + } + } + + private List getImportedFiles(File workflowScript) { + if (!workflowScript.file) { + return [] + } + + return PsiManager + .getInstance( + KotlinCoreEnvironment + .createForProduction( + Disposer.newDisposable(), + new CompilerConfiguration().tap { + it.put(MESSAGE_COLLECTOR_KEY, MessageCollector.@Companion.NONE) + }, + JVM_CONFIG_FILES + ) + .project + ) + .findFile( + new CoreLocalVirtualFile( + new CoreLocalFileSystem(), + workflowScript + ) + ) + .with { it as KtFile } + .fileAnnotationList + ?.annotationEntries + ?.findAll { it.shortName?.asString() == "Import" } + *.valueArgumentList + ?.collectMany { it?.arguments ?: [] } + *.argumentExpression + ?.findAll { it instanceof KtStringTemplateExpression } + ?.collect { it as KtStringTemplateExpression } + *.entries + *.first() + ?.findAll { it instanceof KtLiteralStringTemplateEntry } + ?.collect { it as KtLiteralStringTemplateEntry } + ?.collect { new File(workflowScript.parentFile, it.text) } + ?.collectMany { getImportedFiles(it) + it } + ?: [] + } +} diff --git a/build-logic/settings.gradle b/build-logic/settings.gradle index 8303208fd4..ae8ad333c1 100644 --- a/build-logic/settings.gradle +++ b/build-logic/settings.gradle @@ -20,6 +20,7 @@ dependencyResolutionManagement { } include("base") +include("preprocess-workflows") include("asciidoc-extensions") nameBuildScriptsAfterProjectNames(rootProject.children) diff --git a/build.gradle b/build.gradle index dcb4219406..8dffe596d4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,14 @@ import static org.spockframework.gradle.AsciiDocLinkVerifier.verifyLinksAndAncho plugins { id "org.spockframework.base" apply false - id "base" + id "java-base" id "org.asciidoctor.jvm.convert" id "jacoco" id "net.nemerosa.versioning" id "io.github.gradle-nexus.publish-plugin" id "com.github.ben-manes.versions" id "io.spring.nohttp" + id "org.spockframework.preprocess-workflows" } description = "Spock Framework" @@ -20,9 +21,9 @@ ext { baseVersion = "2.4" snapshotVersion = true milestone = 0 - javaVersions = [8, 11, 17, 21] // ensure that latest version is actually build on GH actions and added to gradle.properties, otherwise no docs get published + javaVersions = javaVersionsList.trim().split(/\s*+,\s*+/).collect { it as int } javaVersion = (System.getProperty("javaVersion") ?: 8) as int - variants = [2.5, 3.0, 4.0] + variants = variantsList.trim().split(/\s*+,\s*+/).collect { it as BigDecimal } variant = System.getProperty("variant") as BigDecimal ?: variants.first() develocity.buildScan.tag "groovy-$variant" if (variant == 2.5) { diff --git a/gradle.properties b/gradle.properties index 21f1e47c99..d84a801050 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,3 +19,7 @@ org.gradle.java.installations.fromEnv=JDK8,JDK11,JDK17,JDK21,JDK23 org.gradle.parallel=true org.gradle.caching=true + +javaVersionsList=8, 11, 17, 21 +variantsList=2.5, 3.0, 4.0 +kotlin.code.style=official From 230ad607a75401ff96c9e2547ef0703cb3ceb580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Sat, 28 Dec 2024 03:19:48 +0100 Subject: [PATCH 5/5] Improve Codecov settings (#1629) - Fail the CI build if the Codecov uploading failed. - Show more detailed information in the Codecov comment. - Do not update the Codecov comment 13 times, but post it after all 14 reports were uploaded. - Ignore dates in coverage reports to not get "upload expired" because the report was taken from remote build cache. --- .github/workflows/branches-and-prs.main.kts | 13 ++++++++++--- ...branches-and-prs.yml => branches-and-prs.yaml} | 6 ++++-- .github/workflows/codeql-analysis.main.kts | 1 - .../{codeql-analysis.yml => codeql-analysis.yaml} | 4 ++-- .github/workflows/common.main.kts | 7 +++++++ .github/workflows/docs-pr.main.kts | 1 - .github/workflows/{docs-pr.yml => docs-pr.yaml} | 4 ++-- .github/workflows/release.main.kts | 15 +++++++++++---- .github/workflows/{release.yml => release.yaml} | 6 ++++-- .../gradle/PreprocessWorkflowsPlugin.groovy | 2 +- codecov.yml | 6 ++++++ 11 files changed, 47 insertions(+), 18 deletions(-) rename .github/workflows/{branches-and-prs.yml => branches-and-prs.yaml} (94%) rename .github/workflows/{codeql-analysis.yml => codeql-analysis.yaml} (93%) rename .github/workflows/{docs-pr.yml => docs-pr.yaml} (90%) rename .github/workflows/{release.yml => release.yaml} (95%) create mode 100644 codecov.yml diff --git a/.github/workflows/branches-and-prs.main.kts b/.github/workflows/branches-and-prs.main.kts index 9025113acd..78a38b9024 100755 --- a/.github/workflows/branches-and-prs.main.kts +++ b/.github/workflows/branches-and-prs.main.kts @@ -47,7 +47,6 @@ workflow( MergeGroup() ), sourceFile = __FILE__, - targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", // https://stackoverflow.com/a/72408109/16358266 concurrency = Concurrency( group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", @@ -76,12 +75,18 @@ workflow( ) } + val matrix = Matrix.full + with(__FILE__.parentFile.resolve("../../codecov.yml")) { + readText() + .replace("after_n_builds:.*+$".toRegex(), "after_n_builds: ${matrix.size}") + .let(::writeText) + } job( id = "build-and-verify", name = "Build and Verify", runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), strategy = Strategy( - matrix = Matrix.full + matrix = matrix ) ) { uses( @@ -111,7 +116,9 @@ workflow( ) uses( name = "Upload to Codecov.io", - action = CodecovAction() + action = CodecovAction( + failCiIfError = true + ) ) } } diff --git a/.github/workflows/branches-and-prs.yml b/.github/workflows/branches-and-prs.yaml similarity index 94% rename from .github/workflows/branches-and-prs.yml rename to .github/workflows/branches-and-prs.yaml index 67a09b6c3c..49e0587a7e 100644 --- a/.github/workflows/branches-and-prs.yml +++ b/.github/workflows/branches-and-prs.yaml @@ -23,10 +23,10 @@ jobs: uses: 'actions/checkout@v4' - id: 'step-1' name: 'Execute script' - run: 'rm ''.github/workflows/branches-and-prs.yml'' && ''.github/workflows/branches-and-prs.main.kts''' + run: 'rm ''.github/workflows/branches-and-prs.yaml'' && ''.github/workflows/branches-and-prs.main.kts''' - id: 'step-2' name: 'Consistency check' - run: 'git diff --exit-code ''.github/workflows/branches-and-prs.yml''' + run: 'git diff --exit-code ''.github/workflows/branches-and-prs.yaml''' check_all_workflow_yaml_consistency: name: 'Check all Workflow YAML Consistency' runs-on: 'ubuntu-latest' @@ -112,3 +112,5 @@ jobs: - id: 'step-3' name: 'Upload to Codecov.io' uses: 'codecov/codecov-action@v5' + with: + fail_ci_if_error: 'true' diff --git a/.github/workflows/codeql-analysis.main.kts b/.github/workflows/codeql-analysis.main.kts index 1c9e1e5562..5b914c4e7e 100755 --- a/.github/workflows/codeql-analysis.main.kts +++ b/.github/workflows/codeql-analysis.main.kts @@ -55,7 +55,6 @@ workflow( ) ), sourceFile = __FILE__, - targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", // https://stackoverflow.com/a/72408109/16358266 concurrency = Concurrency( group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yaml similarity index 93% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/codeql-analysis.yaml index 755b3faf1b..8757e31019 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yaml @@ -24,10 +24,10 @@ jobs: uses: 'actions/checkout@v4' - id: 'step-1' name: 'Execute script' - run: 'rm ''.github/workflows/codeql-analysis.yml'' && ''.github/workflows/codeql-analysis.main.kts''' + run: 'rm ''.github/workflows/codeql-analysis.yaml'' && ''.github/workflows/codeql-analysis.main.kts''' - id: 'step-2' name: 'Consistency check' - run: 'git diff --exit-code ''.github/workflows/codeql-analysis.yml''' + run: 'git diff --exit-code ''.github/workflows/codeql-analysis.yaml''' codeql-build: name: 'CodeQL-Build' runs-on: 'ubuntu-latest' diff --git a/.github/workflows/common.main.kts b/.github/workflows/common.main.kts index 894d471bbb..33aefb3cf5 100755 --- a/.github/workflows/common.main.kts +++ b/.github/workflows/common.main.kts @@ -55,6 +55,13 @@ data class Matrix( val exclude: (Element.() -> Boolean)? = null, val includes: List? = null ) { + val size by lazy { + originalElements + .filterNot(exclude ?: { true }) + .size + + (includes?.size ?: 0) + } + private val originalElements by lazy { (operatingSystems ?: listOf(null)) .map { Element(operatingSystem = it) } diff --git a/.github/workflows/docs-pr.main.kts b/.github/workflows/docs-pr.main.kts index 1334f7566f..e3c9c04448 100755 --- a/.github/workflows/docs-pr.main.kts +++ b/.github/workflows/docs-pr.main.kts @@ -46,7 +46,6 @@ workflow( MergeGroup() ), sourceFile = __FILE__, - targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml", // https://stackoverflow.com/a/72408109/16358266 concurrency = Concurrency( group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}", diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yaml similarity index 90% rename from .github/workflows/docs-pr.yml rename to .github/workflows/docs-pr.yaml index dc5e4cda86..35cbf4244a 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yaml @@ -23,10 +23,10 @@ jobs: uses: 'actions/checkout@v4' - id: 'step-1' name: 'Execute script' - run: 'rm ''.github/workflows/docs-pr.yml'' && ''.github/workflows/docs-pr.main.kts''' + run: 'rm ''.github/workflows/docs-pr.yaml'' && ''.github/workflows/docs-pr.main.kts''' - id: 'step-2' name: 'Consistency check' - run: 'git diff --exit-code ''.github/workflows/docs-pr.yml''' + run: 'git diff --exit-code ''.github/workflows/docs-pr.yaml''' docs-and-javadoc: name: 'Docs and JavaDoc' runs-on: 'ubuntu-latest' diff --git a/.github/workflows/release.main.kts b/.github/workflows/release.main.kts index d7119dd767..c8c0fd2cb0 100755 --- a/.github/workflows/release.main.kts +++ b/.github/workflows/release.main.kts @@ -39,21 +39,26 @@ workflow( tags = listOf("spock-*") ) ), - sourceFile = __FILE__, - targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml" + sourceFile = __FILE__ ) { val GITHUB_TOKEN by secrets val SONATYPE_OSS_USER by secrets val SONATYPE_OSS_PASSWORD by secrets val SIGNING_GPG_PASSWORD by secrets + val matrix = Matrix.full + with(__FILE__.parentFile.resolve("../../codecov.yml")) { + readText() + .replace("after_n_builds:.*+$".toRegex(), "after_n_builds: ${matrix.size}") + .let(::writeText) + } val buildAndVerify = job( id = "build-and-verify", name = "Build and Verify", runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)), condition = "${github.repository} == 'spockframework/spock'", strategy = Strategy( - matrix = Matrix.full + matrix = matrix ) ) { uses( @@ -87,7 +92,9 @@ workflow( ) uses( name = "Upload to Codecov.io", - action = CodecovAction() + action = CodecovAction( + failCiIfError = true + ) ) } val releaseSpock = job( diff --git a/.github/workflows/release.yml b/.github/workflows/release.yaml similarity index 95% rename from .github/workflows/release.yml rename to .github/workflows/release.yaml index d1ad9b36c1..ec302d72f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yaml @@ -19,10 +19,10 @@ jobs: uses: 'actions/checkout@v4' - id: 'step-1' name: 'Execute script' - run: 'rm ''.github/workflows/release.yml'' && ''.github/workflows/release.main.kts''' + run: 'rm ''.github/workflows/release.yaml'' && ''.github/workflows/release.main.kts''' - id: 'step-2' name: 'Consistency check' - run: 'git diff --exit-code ''.github/workflows/release.yml''' + run: 'git diff --exit-code ''.github/workflows/release.yaml''' build-and-verify: name: 'Build and Verify' runs-on: '${{ matrix.os }}' @@ -95,6 +95,8 @@ jobs: - id: 'step-4' name: 'Upload to Codecov.io' uses: 'codecov/codecov-action@v5' + with: + fail_ci_if_error: 'true' release-spock: name: 'Release Spock' runs-on: '${{ matrix.os }}' diff --git a/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy b/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy index d04c8161fc..d9136c9074 100644 --- a/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy +++ b/build-logic/preprocess-workflows/src/main/groovy/org/spockframework/gradle/PreprocessWorkflowsPlugin.groovy @@ -68,7 +68,7 @@ class PreprocessWorkflowsPlugin implements Plugin { .files(getImportedFiles(project.file(workflowScript))) .withPropertyName("importedFiles") it.outputs - .file(new File(workflowScript.parent, "${workflowName}.yml")) + .file(new File(workflowScript.parent, "${workflowName}.yaml")) .withPropertyName('workflowFile') it.javaLauncher.set project.extensions.getByType(JavaToolchainService).launcherFor { diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..599046d6e8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,6 @@ +codecov: + max_report_age: off + +comment: + layout: "reach, diff, flags, files" + after_n_builds: 18