You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dp...@apache.org on 2018/11/21 16:51:51 UTC

[ignite-teamcity-bot] branch master updated: IGNITE-10336 Fix of chain collection to avoid missing builds - Fixes #77.

This is an automated email from the ASF dual-hosted git repository.

dpavlov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git


The following commit(s) were added to refs/heads/master by this push:
     new f63df1d  IGNITE-10336 Fix of chain collection to avoid missing builds - Fixes #77.
f63df1d is described below

commit f63df1df3baf71cf18fe16fd86f9e10f39d9644a
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Wed Nov 21 19:51:46 2018 +0300

    IGNITE-10336 Fix of chain collection to avoid missing builds - Fixes #77.
    
    Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
 .../apache/ignite/ci/analysis/FullChainRunCtx.java |   2 +-
 .../ignite/ci/analysis/mode/LatestRebuildMode.java |   2 +-
 .../ci/tcbot/builds/CompareBuildsService.java      |   2 +-
 .../ignite/ci/tcbot/chain/BuildChainProcessor.java | 240 ++++++++++++---------
 .../ci/teamcity/ignited/BuildRefCompacted.java     |   7 +-
 .../ignited/fatbuild/FatBuildCompacted.java        |   3 +
 .../org/apache/ignite/ci/web/model/Version.java    |   2 +-
 .../ci/tcbot/chain/BuildChainProcessorTest.java    |  43 +++-
 8 files changed, 185 insertions(+), 116 deletions(-)

diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/FullChainRunCtx.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/FullChainRunCtx.java
index 36602c6..c9cf274 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/FullChainRunCtx.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/FullChainRunCtx.java
@@ -90,7 +90,7 @@ public class FullChainRunCtx {
             + (hasFullDurationInfo() ? "" : "+");
     }
 
-    public void addAllSuites(ArrayList<MultBuildRunCtx> suites) {
+    public void addAllSuites(List<MultBuildRunCtx> suites) {
         this.buildCfgsResults.addAll(suites);
     }
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/mode/LatestRebuildMode.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/mode/LatestRebuildMode.java
index 5083bb7..9583f5a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/mode/LatestRebuildMode.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/mode/LatestRebuildMode.java
@@ -22,6 +22,6 @@ public enum LatestRebuildMode {
     NONE,
     /** replace builds with Latest rebuild. */
     LATEST,
-    /** Collect history of builds. */
+    /** Collect history of builds. Rebuilds are applied, but have higher priority. */
     ALL
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/builds/CompareBuildsService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/builds/CompareBuildsService.java
index 71715c8..e420ca4 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/builds/CompareBuildsService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/builds/CompareBuildsService.java
@@ -80,7 +80,7 @@ public class CompareBuildsService {
             MultBuildRunCtx buildCtx = new MultBuildRunCtx(build, compactor);
 
             final FatBuildCompacted fatBuild = tcIgnited.getFatBuild(build.getId());
-            buildCtx.addBuild(bcp.loadTestsAndProblems(fatBuild, tcIgnited));
+            buildCtx.addBuild(bcp.loadChanges(fatBuild, tcIgnited));
 
             for (String testName : buildCtx.tests())
                 tests.add(extractTestName(testName));
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessor.java
index 14d6d2e..5142fe6 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessor.java
@@ -17,16 +17,19 @@
 
 package org.apache.ignite.ci.tcbot.chain;
 
+import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+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.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -92,22 +95,9 @@ public class BuildChainProcessor {
         if (entryPoints.isEmpty())
             return res;
 
-        Map<Integer, FatBuildCompacted> builds = new ConcurrentHashMap<>();
+        Map<Integer, Future<FatBuildCompacted>> builds = loadAllBuildsInChains(entryPoints, mode, teamcityIgnited);
 
-        final Stream<FatBuildCompacted> entryPointsFatBuilds = entryPoints.stream()
-            .filter(Objects::nonNull)
-            .filter(id -> !builds.containsKey(id)) //load and propagate only new entry points
-            .map(id -> builds.computeIfAbsent(id, teamcityIgnited::getFatBuild));
-
-        final ExecutorService svc = tcUpdatePool.getService();
-
-        final Stream<FatBuildCompacted> depsFirstLevel = entryPointsFatBuilds
-            .map(ref -> svc.submit(() -> dependencies(teamcityIgnited, builds, mode, ref)))
-            .collect(Collectors.toList())
-            .stream()
-            .flatMap(fut -> FutureUtil.getResult(fut));
-
-        depsFirstLevel
+        builds.values().stream().map(FutureUtil::getResult)
             .filter(b -> !b.isComposite() && b.getTestsCount() > 0)
             .forEach(b ->
             {
@@ -121,7 +111,7 @@ public class BuildChainProcessor {
                     );
 
                 if (!lrTests.isEmpty()) {
-                    Collections.sort(lrTests, (test0, test1) -> {
+                    lrTests.sort((test0, test1) -> {
                         long t0 = test0.time;
                         long t1 = test1.time;
 
@@ -156,18 +146,18 @@ public class BuildChainProcessor {
 
     /**
      * @param teamcity Teamcity.
-     * @param teamcityIgnited
+     * @param tcIgn Teamcity Ignited.
      * @param entryPoints Entry point(s): Build(s) to start scan from.
      * @param includeLatestRebuild Include latest rebuild.
      * @param procLog Process logger.
      * @param includeScheduledInfo Include scheduled info.
      * @param failRateBranch Fail rate branch.
-     * @param mode
+     * @param mode background data update mode.
      */
     @AutoProfiling
     public FullChainRunCtx loadFullChainContext(
         IAnalyticsEnabledTeamcity teamcity,
-        ITeamcityIgnited teamcityIgnited,
+        ITeamcityIgnited tcIgn,
         Collection<Integer> entryPoints,
         LatestRebuildMode includeLatestRebuild,
         ProcessLogsMode procLog,
@@ -178,39 +168,45 @@ public class BuildChainProcessor {
         if (entryPoints.isEmpty())
             return new FullChainRunCtx(Build.createFakeStub());
 
-        Map<Integer, FatBuildCompacted> builds = new ConcurrentHashMap<>();
+        Map<Integer, Future<FatBuildCompacted>> builds = loadAllBuildsInChains(entryPoints, mode, tcIgn);
 
-        final Stream<FatBuildCompacted> entryPointsFatBuilds = entryPoints.stream()
-                .filter(Objects::nonNull)
-                .filter(id -> !builds.containsKey(id)) //load and propagate only new entry points
-                .map(id -> builds.computeIfAbsent(id, id0 -> teamcityIgnited.getFatBuild(id0, mode)));
+        Map<String, List<Future<FatBuildCompacted>>> freshRebuilds = new ConcurrentHashMap<>();
 
-        final ExecutorService svc = tcUpdatePool.getService();
+        groupByBuildType(builds).forEach(
+            (k, buildsForBt) -> {
+                List<Future<FatBuildCompacted>> futures = replaceWithRecent(buildsForBt,
+                    entryPoints.size(),
+                    includeLatestRebuild,
+                    builds,
+                    mode,
+                    tcIgn);
 
-        final Stream<FatBuildCompacted> depsFirstLevel = entryPointsFatBuilds
-            .flatMap(ref -> dependencies(teamcityIgnited, builds, mode, ref));
+                freshRebuilds.put(k, futures);
+            }
+        );
 
-        Stream<FatBuildCompacted> secondLevelDeps = depsFirstLevel
-            .flatMap(ref -> dependencies(teamcityIgnited, builds, mode, ref));
+        List<MultBuildRunCtx> contexts = new ArrayList<>(freshRebuilds.size());
 
-        // builds may became non unique because of race in filtering and acquiring deps
-        final List<Future<Stream<FatBuildCompacted>>> phase3Submitted = secondLevelDeps
-                .map((fatBuild) -> svc.submit(
-                        () -> replaceWithRecent(teamcityIgnited, includeLatestRebuild, mode, builds, fatBuild, entryPoints.size())))
+        freshRebuilds.forEach((bt, listBuilds) -> {
+            List<FatBuildCompacted> buildsForSuite = listBuilds.stream()
+                .map(FutureUtil::getResult)
+                .filter(buildCompacted -> !buildCompacted.isFakeStub())
                 .collect(Collectors.toList());
 
-        Map<String, MultBuildRunCtx> buildsCtxMap = new ConcurrentHashMap<>();
+            if (buildsForSuite.isEmpty())
+                return;
+
+            BuildRef ref = buildsForSuite.iterator().next().toBuildRef(compactor);
 
-        phase3Submitted.stream()
-                .flatMap(fut -> FutureUtil.getResult(fut))
-                .forEach((fatBuild) -> createCxt(teamcityIgnited, buildsCtxMap, fatBuild));
+            final MultBuildRunCtx ctx = new MultBuildRunCtx(ref, compactor);
 
-        ArrayList<MultBuildRunCtx> contexts = new ArrayList<>(buildsCtxMap.values());
+            buildsForSuite.forEach(buildCompacted -> ctx.addBuild(loadChanges(buildCompacted, tcIgn)));
 
-        contexts.forEach(multiCtx -> {
-            analyzeTests(multiCtx, teamcity, procLog);
+            analyzeTests(ctx, teamcity, procLog);
 
-            fillBuildCounts(multiCtx, teamcityIgnited, includeScheduledInfo);
+            fillBuildCounts(ctx, tcIgn, includeScheduledInfo);
+
+            contexts.add(ctx);
         });
 
         Function<MultBuildRunCtx, Float> function = ctx -> {
@@ -227,8 +223,8 @@ public class BuildChainProcessor {
         };
 
         Integer someEntryPnt = entryPoints.iterator().next();
-        FatBuildCompacted build = builds.computeIfAbsent(someEntryPnt, id -> teamcityIgnited.getFatBuild(id, mode));
-        FullChainRunCtx fullChainRunCtx = new FullChainRunCtx(build.toBuild(compactor));
+        Future<FatBuildCompacted> build = getOrLoadBuild(someEntryPnt, mode, builds, tcIgn);
+        FullChainRunCtx fullChainRunCtx = new FullChainRunCtx(FutureUtil.getResult(build).toBuild(compactor));
 
         contexts.sort(Comparator.comparing(function).reversed());
 
@@ -237,20 +233,55 @@ public class BuildChainProcessor {
         return fullChainRunCtx;
     }
 
-    @SuppressWarnings("WeakerAccess")
-    @AutoProfiling
-    protected void createCxt(ITeamcityIgnited teamcityIgnited,
-                             Map<String, MultBuildRunCtx> buildsCtxMap,
-                             FatBuildCompacted buildCompacted) {
-        final BuildRef ref = buildCompacted.toBuildRef(compactor);
+    @NotNull
+    public Map<Integer, Future<FatBuildCompacted>> loadAllBuildsInChains(Collection<Integer> entryPoints,
+        SyncMode mode,
+        ITeamcityIgnited tcIgn) {
+        Map<Integer, Future<FatBuildCompacted>> builds = new ConcurrentHashMap<>();
+
+        Stream<Future<FatBuildCompacted>> entryPointsFatBuilds = entryPoints.stream()
+            .filter(Objects::nonNull)
+            .map(id -> getOrLoadBuild(id, mode, builds, tcIgn));
 
-        if (buildCompacted.isFakeStub() || ref.isFakeStub())
-            return;
+        Set<Integer> remainedUnloadedDeps = entryPointsFatBuilds
+            .flatMap(ref -> dependencies(ref, mode, builds, tcIgn).stream()).collect(Collectors.toSet());
 
-        final MultBuildRunCtx ctx = buildsCtxMap.computeIfAbsent(ref.buildTypeId,
-                k -> new MultBuildRunCtx(ref, compactor));
+        for (int level = 1; level < 5; level++) {
+            if (remainedUnloadedDeps.isEmpty())
+                break;
+
+            Set<Integer> depsNextLevel = remainedUnloadedDeps
+                .stream()
+                .map(builds::get)
+                .peek(val -> Preconditions.checkNotNull(val, "Build future should be in context"))
+                .flatMap(ref -> dependencies(ref, mode, builds, tcIgn).stream()).collect(Collectors.toSet());
+
+            logger.info("Level [" + level + "] dependencies:" + depsNextLevel);
+
+            remainedUnloadedDeps = depsNextLevel;
+        }
 
-        ctx.addBuild(loadTestsAndProblems(buildCompacted, teamcityIgnited));
+        return builds;
+    }
+
+    @NotNull
+    public Map<String, List<FatBuildCompacted>> groupByBuildType(Map<Integer, Future<FatBuildCompacted>> builds) {
+        Map<String, List<FatBuildCompacted>> buildsByBt = new ConcurrentHashMap<>();
+        builds.values().forEach(bFut -> {
+            FatBuildCompacted b = FutureUtil.getResult(bFut);
+
+            String buildTypeId = b.buildTypeId(compactor);
+            if (buildTypeId == null)
+                logger.error("Invalid build type ID for build " + b.getId());
+            else
+                buildsByBt.computeIfAbsent(buildTypeId, k -> new ArrayList<>()).add(b);
+        });
+        return buildsByBt;
+    }
+
+    public Future<FatBuildCompacted> getOrLoadBuild(Integer id, SyncMode mode,
+        Map<Integer, Future<FatBuildCompacted>> builds, ITeamcityIgnited tcIgn) {
+        return builds.computeIfAbsent(id, id0 -> loadBuildAsync(id0, mode, tcIgn));
     }
 
     /**
@@ -260,51 +291,55 @@ public class BuildChainProcessor {
      * @param tcIgnited
      * @return Full context.
      */
-    public SingleBuildRunCtx loadTestsAndProblems(@Nonnull FatBuildCompacted buildCompacted,
+    public SingleBuildRunCtx loadChanges(@Nonnull FatBuildCompacted buildCompacted,
                                                   ITeamcityIgnited tcIgnited) {
         SingleBuildRunCtx ctx = new SingleBuildRunCtx(buildCompacted, compactor);
 
         ctx.setChanges(tcIgnited.getAllChanges(buildCompacted.changes()));
 
-        //todo support storing build.lastChanges.changes) ?
-
         return ctx;
     }
 
     @SuppressWarnings("WeakerAccess")
     @NotNull
     @AutoProfiling
-    protected Stream<FatBuildCompacted> replaceWithRecent(ITeamcityIgnited teamcityIgnited,
+    protected List<Future<FatBuildCompacted>> replaceWithRecent(List<FatBuildCompacted> builds,
+        int cntLimit,
         LatestRebuildMode includeLatestRebuild,
+        Map<Integer, Future<FatBuildCompacted>> allBuildsMap,
         SyncMode syncMode,
-        Map<Integer, FatBuildCompacted> builds,
-        FatBuildCompacted buildCompacted,
-        int cntLimit) {
-        if (includeLatestRebuild == LatestRebuildMode.NONE)
-            return Stream.of(buildCompacted);
+        ITeamcityIgnited tcIgn) {
+        if (includeLatestRebuild == LatestRebuildMode.NONE || builds.isEmpty())
+            return completed(builds);
+
+        Optional<FatBuildCompacted> maxIdBuildOpt = builds.stream().max(Comparator.comparing(BuildRefCompacted::id));
+        if (!maxIdBuildOpt.isPresent())
+            return completed(builds);
+
+        FatBuildCompacted freshBuild = maxIdBuildOpt.get();
 
-        final String branch = getBranchOrDefault(buildCompacted.branchName(compactor));
+        final String branch = getBranchOrDefault(freshBuild.branchName(compactor));
 
-        final String buildTypeId = buildCompacted.buildTypeId(compactor);
-        Stream<BuildRefCompacted> hist = teamcityIgnited.getAllBuildsCompacted(buildTypeId, branch)
+        final String buildTypeId = freshBuild.buildTypeId(compactor);
+        Stream<BuildRefCompacted> hist = tcIgn.getAllBuildsCompacted(buildTypeId, branch)
             .stream()
-            .filter(t -> !t.isCancelled(compactor))
-            .filter(t -> t.isFinished(compactor));
+            .filter(bref -> !bref.isCancelled(compactor))
+            .filter(bref -> bref.isFinished(compactor));
 
         if (includeLatestRebuild == LatestRebuildMode.LATEST) {
             BuildRefCompacted recentRef = hist.max(Comparator.comparing(BuildRefCompacted::id))
-                    .orElse(buildCompacted);
+                .orElse(freshBuild);
 
-            return Stream.of(recentRef)
-                .map(b -> builds.computeIfAbsent(b.id(), id -> teamcityIgnited.getFatBuild(id, syncMode)));
+            return Collections.singletonList(
+                getOrLoadBuild(recentRef.id(), syncMode, allBuildsMap, tcIgn));
         }
 
         if (includeLatestRebuild == LatestRebuildMode.ALL) {
             return hist
                 .sorted(Comparator.comparing(BuildRefCompacted::id).reversed())
                 .limit(cntLimit)
-               // .filter(b -> !builds.containsKey(b.id())) // todo removing this causes incorrect count of failures (duplicated builds)
-                .map(b -> builds.computeIfAbsent(b.id(), id -> teamcityIgnited.getFatBuild(id, syncMode)));
+                .map(bref -> getOrLoadBuild(bref.id(), syncMode, allBuildsMap, tcIgn))
+                .collect(Collectors.toList());
         }
 
         throw new UnsupportedOperationException("invalid mode " + includeLatestRebuild);
@@ -365,47 +400,40 @@ public class BuildChainProcessor {
         return branch;
     }
 
+    /**
+     * @param buildFut Chain build future.
+     * @param mode Mode.
+     * @param builds Builds.
+     * @param teamcityIgnited Teamcity ignited.
+     * @return Set of new builds found during this dependencies check round.
+     */
     @NotNull
-    private Stream<FatBuildCompacted> dependencies(
-        ITeamcityIgnited teamcityIgnited,
-        Map<Integer, FatBuildCompacted> builds,
+    private Set<Integer> dependencies(
+        Future<FatBuildCompacted> buildFut,
         SyncMode mode,
-        FatBuildCompacted build) {
+        Map<Integer, Future<FatBuildCompacted>> builds,
+        ITeamcityIgnited teamcityIgnited) {
+        Set<Integer> newBuilds = new HashSet<>();
 
-        Stream<FatBuildCompacted> stream = IntStream.of(build.snapshotDependencies())
-            .mapToObj(id -> {
-                if (builds.containsKey(id))
-                    return Futures.<FatBuildCompacted>immediateFuture(null); //load and propagate only new dependencies
+        IntStream.of(FutureUtil.getResult(buildFut).snapshotDependencies())
+            .forEach(id -> builds.computeIfAbsent(id, id0 -> {
+                newBuilds.add(id0);
 
-                if (mode == SyncMode.NONE)
-                    return Futures.immediateFuture(loadBuild(teamcityIgnited, builds, mode, id));
+                return loadBuildAsync(id0, mode, teamcityIgnited);
+            }));
 
-                return tcUpdatePool.getService().submit(() -> {
-                    if (builds.containsKey(id))
-                        return null;
+        return newBuilds;
+    }
 
-                    return loadBuild(teamcityIgnited, builds, mode, id);
-                });
-            })
-            .collect(Collectors.toList())
-            .stream()
-            .map(future -> FutureUtil.getResult(future))
-            .filter(Objects::nonNull);
+    public Future<FatBuildCompacted> loadBuildAsync(Integer id, SyncMode mode, ITeamcityIgnited teamcityIgnited) {
+        if (mode == SyncMode.NONE)
+            return Futures.immediateFuture(teamcityIgnited.getFatBuild(id, SyncMode.NONE));
 
-        return Stream.concat(
-            Stream.of(build),
-            stream);
+        return tcUpdatePool.getService().submit(() -> teamcityIgnited.getFatBuild(id, mode));
     }
 
-    @Nullable
-    private FatBuildCompacted loadBuild(ITeamcityIgnited teamcityIgnited,
-        Map<Integer, FatBuildCompacted> builds,
-        SyncMode mode,
-        int id) {
-        FatBuildCompacted buildLoaded = teamcityIgnited.getFatBuild(id, mode);
-
-        FatBuildCompacted prevVal = builds.putIfAbsent(id, buildLoaded);
 
-        return prevVal == null ? buildLoaded : null;
+    private List<Future<FatBuildCompacted>> completed(List<FatBuildCompacted> builds) {
+        return builds.stream().map(Futures::immediateFuture).collect(Collectors.toList());
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
index efda317..f6ccf03 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/BuildRefCompacted.java
@@ -22,6 +22,7 @@ import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.ci.db.Persisted;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.ci.tcmodel.hist.BuildRef.*;
 
@@ -93,7 +94,7 @@ public class BuildRefCompacted {
     }
 
     protected void fillBuildRefFields(IStringCompactor compactor, BuildRef res) {
-        res.setId(id < 0 ? null : id);
+        res.setId(getId());
         res.buildTypeId = buildTypeId(compactor);
         res.branchName = branchName(compactor);
         res.status = compactor.getStringFromId(status);
@@ -101,6 +102,10 @@ public class BuildRefCompacted {
         res.href = getHrefForId(id());
     }
 
+    @Nullable public Integer getId() {
+        return id < 0 ? null : id;
+    }
+
     public String buildTypeId(IStringCompactor compactor) {
         return compactor.getStringFromId(buildTypeId);
     }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
index 95e88ad..419de92 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
@@ -330,6 +330,9 @@ public class FatBuildCompacted extends BuildRefCompacted implements IVersionedEn
      *
      */
     public boolean isFakeStub() {
+        if (getId() == null)
+            return true;
+
         Boolean flag = getFlag(FAKE_BUILD_F);
 
         return flag != null && flag;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
index 51ce92e..c406e7f 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
@@ -23,7 +23,7 @@ package org.apache.ignite.ci.web.model;
     public static final String GITHUB_REF = "https://github.com/apache/ignite-teamcity-bot";
 
     /** TC Bot Version. */
-    public static final String VERSION = "20181120";
+    public static final String VERSION = "20181121";
 
     /** TC Bot Version. */
     public String version = VERSION;
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessorTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessorTest.java
index 2e2ec97..c09ea1c 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessorTest.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/BuildChainProcessorTest.java
@@ -45,7 +45,6 @@ import org.apache.ignite.ci.teamcity.ignited.InMemoryStringCompactor;
 import org.apache.ignite.ci.teamcity.ignited.SyncMode;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.jetbrains.annotations.NotNull;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -60,8 +59,15 @@ import static org.mockito.Mockito.when;
  * Test for chain processor
  */
 public class BuildChainProcessorTest {
+    /** Unique failed test, prefix for test name. This name will be unique each time. */
     public static final String UNIQUE_FAILED_TEST = "uniqueFailedTest";
+
+    /** Test failing every time. */
     public static final String TEST_FAILING_EVERY_TIME = "testFailingEveryTime";
+
+    /** Pds 1 build type ID. */
+    public static final String PDS_1_BT_ID = "Pds1";
+
     /** Injector. */
     private Injector injector = Guice.createInjector(new AbstractModule() {
         @Override protected void configure() {
@@ -73,7 +79,6 @@ public class BuildChainProcessorTest {
     /**
      *
      */
-    @Ignore
     @Test
     public void testAllBuildsArePresentInMergedBuilds() {
         IStringCompactor c = injector.getInstance(IStringCompactor.class);
@@ -102,14 +107,42 @@ public class BuildChainProcessorTest {
             else
                 assertTrue(suite.failedTests() >= 1);
 
-            List<ITestFailures> tests = suite.getFailedTests();
-            for (ITestFailures test : tests) {
+            for (ITestFailures test : suite.getFailedTests()) {
                 if (test.getName().startsWith(UNIQUE_FAILED_TEST))
                     assertEquals(1, test.failuresCount());
                 else if (test.getName().equals(TEST_FAILING_EVERY_TIME))
                     assertEquals(10, test.failuresCount());
             }
         }
+
+        //Adding successfull re-runs
+        for (int j = 0; j < 10; j++) {
+            FatBuildCompacted pds1 = testFatBuild(c, 130 + j, PDS_1_BT_ID);
+            pds1.buildTypeName(UNIQUE_FAILED_TEST, c);
+
+            TestOccurrenceFull t1 = new TestOccurrenceFull();
+            t1.name = UNIQUE_FAILED_TEST + j;
+            t1.status = TestOccurrence.STATUS_SUCCESS;
+            pds1.addTests(c, Lists.newArrayList(t1));
+
+            builds.put(pds1.id(), pds1);
+        }
+
+        FullChainRunCtx ctx2 = bcp.loadFullChainContext(tcOldMock(), tcIgnited,
+            entry,
+            LatestRebuildMode.ALL, ProcessLogsMode.SUITE_NOT_COMPLETE, false, ITeamcity.DEFAULT, SyncMode.NONE);
+        List<MultBuildRunCtx> suites2 = ctx2.failedChildSuites().collect(Collectors.toList());
+
+        assertTrue(!suites2.isEmpty());
+
+        for (MultBuildRunCtx suite : suites2) {
+            System.out.println(suite.getFailedTestsNames().collect(Collectors.toList()));
+
+            if (suite.suiteName() != null && suite.suiteName().startsWith(UNIQUE_FAILED_TEST)) {
+                for (ITestFailures test : suite.getFailedTests())
+                    assertTrue("Failure found but should be hidden by re-run " + test.getName(), false);
+            }
+        }
     }
 
     /**
@@ -144,7 +177,7 @@ public class BuildChainProcessorTest {
 
         builds.put(root.id(), root);
 
-        FatBuildCompacted pds1 = testFatBuild(c, 100 + i, "Pds1");
+        FatBuildCompacted pds1 = testFatBuild(c, 100 + i, PDS_1_BT_ID);
         pds1.buildTypeName(UNIQUE_FAILED_TEST, c);
 
         TestOccurrenceFull t1 = new TestOccurrenceFull();