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

org.arquillian.smart.testing.strategies.affected.ClassDependenciesGraph Maven / Gradle / Ivy

/*
 * Derived from Infinitest, a Continuous Test Runner.
 *
 * Copyright (C) 2010-2013
 * "Ben Rady" ,
 * "Rod Coffin" ,
 * "Ryan Breidenbach" 
 * "David Gageot" , et al.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.arquillian.smart.testing.strategies.affected;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.arquillian.smart.testing.api.TestVerifier;
import org.arquillian.smart.testing.configuration.Configuration;
import org.arquillian.smart.testing.logger.Log;
import org.arquillian.smart.testing.logger.Logger;
import org.arquillian.smart.testing.scm.Change;
import org.arquillian.smart.testing.strategies.affected.ast.JavaClass;
import org.arquillian.smart.testing.strategies.affected.ast.JavaClassBuilder;
import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

import static org.arquillian.smart.testing.strategies.affected.AffectedTestsDetector.AFFECTED;
import static org.jgrapht.Graphs.predecessorListOf;

public class ClassDependenciesGraph {

    private static final Filter coreJava = new Filter(Collections.singletonList(""), Collections.singletonList("java.*"));

    private final JavaClassBuilder builder;
    private final DirectedGraph graph;
    private final Filter filter;
    private final TestVerifier testVerifier;
    private final boolean enableTransitivity;
    private final Path projectDir;
    private ElementAdapter elementAdapter;
    private final WatchFilesResolver watchFilesResolver;
    private final ComponentUnderTestResolver componentUnderTestResolver;

    ClassDependenciesGraph(TestVerifier testVerifier, Configuration configuration, File projectDir) {
        this.builder = new JavaClassBuilder();
        this.graph = new DefaultDirectedGraph<>(DefaultEdge.class);
        final AffectedConfiguration affectedConfiguration =
            (AffectedConfiguration) configuration.getStrategyConfiguration(AFFECTED);
        this.filter = new Filter(affectedConfiguration.getInclusions(), affectedConfiguration.getExclusions());
        this.testVerifier = testVerifier;
        this.enableTransitivity = affectedConfiguration.isTransitivity();
        this.projectDir = projectDir.toPath();
        this.watchFilesResolver = new WatchFilesResolver(this.projectDir);
        this.componentUnderTestResolver = new ComponentUnderTestResolver();
        this.elementAdapter = new ElementAdapter(this.testVerifier, this.builder);
    }

    void buildTestDependencyGraph(Collection modifiedJavaFiles) {
        // First update class index
        List testClassesNames = new ArrayList<>();
        for (File testJavaFile : modifiedJavaFiles) {
            String changedTestClassClassname =
                builder.getClassName(JavaToClassLocation.transform(testJavaFile, testVerifier));
            if (changedTestClassClassname != null) {
                testClassesNames.add(changedTestClassClassname);
            }
        }

        // Then find dependencies
        for (String changedTestClassNames : testClassesNames) {
            JavaClass testJavaClass = builder.getClassDescription(changedTestClassNames);
            if (testJavaClass != null) {
                final String[] imports = testJavaClass.getImports();

                final List manualProductionClasses = this.componentUnderTestResolver.resolve(testJavaClass);
                manualProductionClasses.addAll(Arrays.asList(imports));

                final List files = this.watchFilesResolver.resolve(testJavaClass);

                addToIndex(new JavaElement(testJavaClass), manualProductionClasses, files);
            }
        }
    }

    private void addToIndex(JavaElement javaElement, List imports, List files) {
        addToGraph(javaElement);
        createOrUpdateJavaElementWithImportDependencies(javaElement, imports);
        createOrUpdateJavaElementWithDependencies(javaElement, files);
    }

    private void createOrUpdateJavaElementWithDependencies(JavaElement javaElement, List files) {
        for (Path file : files) {
            final FileElement fileElement = new FileElement(file);

            if (!graph.containsVertex(fileElement)) {
                graph.addVertex(fileElement);
            }

            if (!graph.containsEdge(javaElement, fileElement)) {
                graph.addEdge(javaElement, fileElement);
            }
        }
    }

    private void addToGraph(JavaElement newClass) {
        if (!graph.addVertex(newClass)) {
            replaceVertex(newClass);
        }
    }

    private void replaceVertex(JavaElement newClass) {
        List incomingEdges = getParents(newClass);

        graph.removeVertex(newClass);
        graph.addVertex(newClass);
        for (Element each : incomingEdges) {
            graph.addEdge(each, newClass);
        }
    }

    private void createOrUpdateJavaElementWithImportDependencies(JavaElement javaElementParentClass, List imports) {

        for (String importz : imports) {

            if (addImport(javaElementParentClass, importz)
                && filter.shouldBeIncluded(importz)
                && this.enableTransitivity) {
                JavaClass javaClass = builder.getClassDescription(importz);
                if (javaClass != null) {
                    createOrUpdateJavaElementWithImportDependencies(javaElementParentClass, Arrays.asList(javaClass.getImports()));
                }
            }
        }
    }

    private boolean addImport(JavaElement javaElementParentClass, String importz) {

        if (coreJava.shouldBeIncluded(importz)) {

            JavaElement importClass = new JavaElement(importz);
            if (!importClass.equals(javaElementParentClass)) {
                if (!graph.containsVertex(importClass)) {
                    graph.addVertex(importClass);
                }

                // This condition is required because we are flattening imports in graph
                if (!graph.containsEdge(javaElementParentClass, importClass)) {
                    graph.addEdge(javaElementParentClass, importClass);
                    return true;
                }
            }
        }

        return false;
    }

    Set findTestsDependingOn(Collection files) {

        return files.stream()
            .map(change -> this.elementAdapter.tranform(change))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .filter(graph::containsVertex)
            .map(this::getParents)
            .flatMap(Collection::stream)
            .map(e -> {
                JavaElement javaElement = (JavaElement) e;
                return javaElement.getClassName();
            })
            .collect(Collectors.toSet());
    }

    private List getParents(Element childClass) {
        return predecessorListOf(graph, childClass);

    }

    @Override
    public String toString() {
        final Set defaultEdges = graph.edgeSet();
        StringBuilder s = new StringBuilder(System.lineSeparator());

        defaultEdges.forEach(de -> s.append(de.toString()).append(System.lineSeparator()));

        return s.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy