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

io.qameta.allure.history.HistoryPlugin Maven / Gradle / Ivy

There is a newer version: 2.30.0
Show newest version
/*
 *  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.history;

import com.fasterxml.jackson.core.type.TypeReference;
import io.qameta.allure.CommonJsonAggregator2;
import io.qameta.allure.Reader;
import io.qameta.allure.context.JacksonContext;
import io.qameta.allure.core.Configuration;
import io.qameta.allure.core.LaunchResults;
import io.qameta.allure.core.ResultsVisitor;
import io.qameta.allure.entity.ExecutorInfo;
import io.qameta.allure.entity.Statistic;
import io.qameta.allure.entity.Status;
import io.qameta.allure.entity.TestResult;
import io.qameta.allure.executor.ExecutorPlugin;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Plugin that adds history to the report.
 *
 * @since 2.0
 */
public class HistoryPlugin extends CommonJsonAggregator2 implements Reader {

    private static final Set MARK_STATUSES = new HashSet<>(Arrays.asList(
            Status.FAILED, Status.BROKEN, Status.PASSED
    ));

    private static final String HISTORY_BLOCK_NAME = "history";
    private static final String HISTORY_FILE_NAME = "history.json";

    //@formatter:off
    private static final TypeReference> HISTORY_TYPE =
            new TypeReference>() {
            };
    //@formatter:on


    public HistoryPlugin() {
        super(HISTORY_BLOCK_NAME, HISTORY_FILE_NAME);
    }

    @Override
    public void readResults(final Configuration configuration,
                            final ResultsVisitor visitor,
                            final Path directory) {
        final JacksonContext context = configuration.requireContext(JacksonContext.class);
        final Path historyFile = directory.resolve(HISTORY_BLOCK_NAME).resolve(HISTORY_FILE_NAME);
        if (Files.exists(historyFile)) {
            try (InputStream is = Files.newInputStream(historyFile)) {
                final Map history = context.getValue().readValue(is, HISTORY_TYPE);
                visitor.visitExtra(HISTORY_BLOCK_NAME, history);
            } catch (IOException e) {
                visitor.error("Could not read history file " + historyFile, e);
            }
        }
    }

    private boolean isNewFailed(final HistoryItem current,
                                final List prev) {
        return statusChangeTo(Status.FAILED, current, prev);
    }

    private boolean isNewBroken(final HistoryItem current,
                                final List prev) {
        return statusChangeTo(Status.BROKEN, current, prev);
    }

    private boolean isNewPassed(final HistoryItem current,
                                final List prev) {
        return statusChangeTo(Status.PASSED, current, prev);
    }

    private boolean statusChangeTo(final Status target,
                                   final HistoryItem current,
                                   final List prev) {
        final Optional prevItem = prev.stream()
                .filter(hi -> MARK_STATUSES.contains(hi.getStatus()))
                .findFirst();

        return prevItem.isPresent()
               && target.equals(current.getStatus())
               && !target.equals(prevItem.get().getStatus());
    }

    private boolean isFlaky(final HistoryItem current,
                            final List prev) {
        if (prev.isEmpty()) {
            return false;
        }
        if (current.getStatus() != Status.FAILED && current.getStatus() != Status.BROKEN) {
            return false;
        }
        final List statuses = prev
                .stream()
                .map(HistoryItem::getStatus)
                .limit(5)
                .collect(Collectors.toList());

        return statuses.contains(Status.PASSED)
               && statuses.indexOf(Status.PASSED) < statuses.lastIndexOf(Status.FAILED);
    }

    @Override
    protected Map getData(final List launches) {
        final Map history = launches.stream()
                .map(launch -> launch.getExtra(HISTORY_BLOCK_NAME, (Supplier>) HashMap::new))
                .reduce(new HashMap<>(), (a, b) -> {
                    a.putAll(b);
                    return a;
                });
        launches.forEach(launch -> {
            final ExecutorInfo executorInfo = launch.getExtra(
                    ExecutorPlugin.EXECUTORS_BLOCK_NAME,
                    ExecutorInfo::new
            );
            launch.getResults().stream()
                    .filter(result -> Objects.nonNull(result.getHistoryId()))
                    .forEach(result -> updateHistory(history, result, executorInfo));
        });
        return history;
    }

    private void updateHistory(final Map history,
                               final TestResult result,
                               final ExecutorInfo info) {
        //@formatter:off
        final HistoryData data = history.computeIfAbsent(
                result.getHistoryId(),
                id -> new HistoryData().setStatistic(new Statistic())
        );
        //@formatter:on

        data.getStatistic().update(result);
        if (!data.getItems().isEmpty()) {
            result.addExtraBlock(HISTORY_BLOCK_NAME, copy(data));
        }
        final HistoryItem current = new HistoryItem()
                .setUid(result.getUid())
                .setStatus(result.getStatus())
                .setStatusDetails(result.getStatusMessage())
                .setTime(result.getTime());

        if (Objects.nonNull(info.getReportUrl())) {
            current.setReportUrl(createReportUrl(info.getReportUrl(), result.getUid()));
        }

        final List prevItems = data.getItems();
        result.setFlaky(result.isFlaky() || isFlaky(current, prevItems));
        result.setNewFailed(isNewFailed(current, prevItems));
        result.setNewBroken(isNewBroken(current, prevItems));
        result.setNewPassed(isNewPassed(current, prevItems));

        final List newItems = Stream.concat(Stream.of(current), prevItems.stream())
                .limit(20)
                .collect(Collectors.toList());
        data.setItems(newItems);
    }

    private static HistoryData copy(final HistoryData other) {
        final Statistic statistic = new Statistic();
        statistic.merge(other.getStatistic());
        final List items = new ArrayList<>(other.getItems());
        return new HistoryData()
                .setStatistic(statistic)
                .setItems(items);
    }

    private static String createReportUrl(final String reportUrl, final String uuid) {
        final String pattern = reportUrl.endsWith("index.html") ? "%s#testresult/%s" : "%s/#testresult/%s";
        return String.format(pattern, reportUrl, uuid);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy