All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.qameta.allure.spock2.compiler.AllureStatementsLabelsToStepsTransformation Maven / Gradle / Ivy

/*
 *  Copyright 2016-2024 Qameta Software Inc
 *
 *  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
 *
 *      http://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 io.qameta.allure.spock2.compiler;

import io.qameta.allure.Allure;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GroovyClassVisitor;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.spockframework.compiler.AstNodeCache;
import org.spockframework.compiler.AstUtil;
import org.spockframework.compiler.ErrorReporter;
import org.spockframework.compiler.SourceLookup;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author charlie (Dmitry Baev).
 */
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
public class AllureStatementsLabelsToStepsTransformation implements ASTTransformation {

    @Override
    public void visit(final ASTNode[] nodes, final SourceUnit sourceUnit) {
        new Impl().visit(sourceUnit);
    }

    /**
     * Use of nested class defers linking until after groovy version check.
     */
    private static final class Impl {
        private static final AstNodeCache NODE_CACHE = new AstNodeCache();

        private void visit(final SourceUnit sourceUnit) {
            final ErrorReporter errorReporter = new ErrorReporter(sourceUnit);
            final SourceLookup sourceLookup = new SourceLookup(sourceUnit);

            try {
                sourceUnit.getAST().getClasses().stream()
                        .filter(this::isSpec)
                        .forEach(clazz -> processSpec(sourceUnit, clazz, errorReporter));
            } finally {
                sourceLookup.close();
            }
        }

        private boolean isSpec(final ClassNode clazz) {
            return clazz.isDerivedFrom(NODE_CACHE.Specification);
        }

        private void processSpec(final SourceUnit sourceUnit,
                                 final ClassNode clazz,
                                 final ErrorReporter errorReporter) {
            try {
                clazz.visitContents(new MethodVisitor());
                if (!sourceUnit.getErrorCollector().hasErrors()) {
                    new VariableScopeVisitor(sourceUnit).visitClass(clazz);
                }
            } catch (Exception e) {
                errorReporter.error(
                        "Unexpected error during compilation of spec '%s'."
                        + " Please file a bug report at https://github.com/allure-framework/allure-java.",
                        e, clazz.getName()
                );
            }
        }
    }

    /**
     * Adds Allure steps for statements with labels.
     */
    private static final class MethodVisitor implements GroovyClassVisitor {

        private static final ClassNode ALLURE = ClassHelper.makeWithoutCaching(Allure.class);

        @Override
        public void visitClass(final ClassNode node) {
            // do nothing
        }

        @Override
        public void visitConstructor(final ConstructorNode node) {
            // do nothing
        }

        @Override
        public void visitMethod(final MethodNode node) {
            final List newStatements = AstUtil.getStatements(node).stream()
                    .flatMap(statement -> Stream.concat(
                            getAllureStepStatements(statement),
                            Stream.of(statement)
                    ))
                    .collect(Collectors.toList());

            node.setCode(new BlockStatement(
                    newStatements,
                    ((BlockStatement) node.getCode()).getVariableScope())
            );
        }

        @Override
        public void visitField(final FieldNode node) {
            // do nothing
        }

        @Override
        public void visitProperty(final PropertyNode node) {
            // do nothing
        }

        @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts")
        private static Stream getAllureStepStatements(final Statement statement) {
            if (Objects.nonNull(statement.getStatementLabels())
                && !statement.getStatementLabels().isEmpty()) {
                final String labels = String.join(" ", statement.getStatementLabels());
                final StringBuilder builder = new StringBuilder(labels);

                if (statement instanceof ExpressionStatement) {
                    final ExpressionStatement es = (ExpressionStatement) statement;
                    if (es.getExpression() instanceof ConstantExpression) {
                        final Object value = ((ConstantExpression) es.getExpression()).getValue();
                        if (value instanceof String) {
                            builder.append(": ").append(value);
                        }
                    }
                }

                final String stepName = builder.toString();

                return Stream.of(new ExpressionStatement(
                        new StaticMethodCallExpression(
                                ALLURE,
                                "step",
                                new ArgumentListExpression(
                                        new ConstantExpression(stepName)
                                )
                        )
                ));
            }
            return Stream.empty();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy