Show tests on TC board that were stable and have become flaky (#159)

Signed-off-by: Ivan Rakov <ivan.glukos@gmail.com>
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
index b80d67b..6be47bc 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
@@ -114,6 +114,20 @@
         return Strings.isNullOrEmpty(srvCode) ? ITcBotConfig.DEFAULT_SERVER_CODE : srvCode;
     }
 
+    /** {@inheritDoc} */
+    @Override public Integer flakyRate() {
+        Integer flakyRate = getConfig().flakyRate();
+
+        return flakyRate == null || flakyRate < 0 || flakyRate > 100 ? ITcBotConfig.DEFAULT_FLAKY_RATE : flakyRate;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Double confidence() {
+        Double confidence = getConfig().confidence();
+
+        return confidence == null || confidence < 0 || confidence > 1 ? ITcBotConfig.DEFAULT_CONFIDENCE : confidence;
+    }
+
     @Override
     public ITrackedBranchesConfig getTrackedBranches() {
         return getConfig();
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
index e96a0be..fe1ef2b 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
@@ -30,11 +30,13 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
+import org.apache.ignite.ci.teamcity.ignited.runhist.Invocation;
 import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
 import org.apache.ignite.tcbot.engine.issue.IssueType;
 import org.apache.ignite.ci.jobs.CheckQueueJob;
@@ -64,11 +66,14 @@
 import org.apache.ignite.tcignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.tcignited.SyncMode;
 import org.apache.ignite.tcignited.history.IRunHistory;
+import org.apache.ignite.tcignited.history.InvocationData;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.ignite.tcignited.buildref.BranchEquivalence.normalizeBranch;
+import static org.apache.ignite.tcignited.history.RunStatus.RES_FAILURE;
+import static org.apache.ignite.tcignited.history.RunStatus.RES_OK;
 
 /**
  *
@@ -471,15 +476,49 @@
                 type = IssueType.newFailure;
                 final String flakyComments = runStat.getFlakyComments();
 
-                if (!Strings.isNullOrEmpty(flakyComments)) {
-                    if (runStat.detectTemplate(EventTemplates.newFailureForFlakyTest) == null) {
-                        logger.info("Skipping registering new issue for test fail:" +
-                            " Test seems to be flaky " + name + ": " + flakyComments);
-
-                        firstFailedBuildId = null;
-                    }
-                    else
+                if (!Strings.isNullOrEmpty(flakyComments) &&
+                    runStat.detectTemplate(EventTemplates.newFailureForFlakyTest) != null)
                         type = IssueType.newFailureForFlakyTest;
+            }
+        }
+
+        double flakyRate = 0;
+
+        if (firstFailedBuildId == null || type == null) {
+            List<Invocation> invocations = runStat.getInvocations().
+                filter(invocation -> invocation != null && invocation.status() != InvocationData.MISSING)
+                .collect(Collectors.toList());
+
+            int confidenceOkTestsRow = Math.max(1,
+                (int) Math.ceil(Math.log(1 - cfg.confidence()) / Math.log(1 - cfg.flakyRate() / 100.0)));
+
+            if (invocations.size() >= confidenceOkTestsRow * 2) {
+                List<Invocation> lastInvocations =
+                    invocations.subList(invocations.size() - confidenceOkTestsRow * 2, invocations.size());
+
+                int stableTestRuns = 0;
+
+                for (int i = 0; i < confidenceOkTestsRow; i++) {
+                    if (lastInvocations.get(i).status() == RES_OK.getCode())
+                        stableTestRuns++;
+                    else
+                        break;
+                }
+
+                if (stableTestRuns == confidenceOkTestsRow) {
+                    long failedTestRuns = 0;
+
+                    for (int i = confidenceOkTestsRow; i < confidenceOkTestsRow * 2; i++) {
+                        if (lastInvocations.get(i).status() == RES_FAILURE.getCode())
+                            failedTestRuns++;
+                    }
+
+                    flakyRate = (double) failedTestRuns / confidenceOkTestsRow * 100;
+
+                    if (flakyRate > cfg.flakyRate()) {
+                        type = IssueType.newTestWithHighFlakyRate;
+                        firstFailedBuildId = lastInvocations.get(confidenceOkTestsRow).buildId();
+                    }
                 }
             }
         }
@@ -501,6 +540,7 @@
         issue.trackedBranchName = trackedBranch;
         issue.displayName = testFailure.testName;
         issue.webUrl = testFailure.webUrl;
+        issue.flakyRate = flakyRate;
 
         issue.buildTags.addAll(suiteTags);
 
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
index a8ae186..f6c1f7b 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
@@ -98,6 +98,14 @@
                 return ITcBotConfig.DEFAULT_SERVER_CODE;
             }
 
+            @Override public Integer flakyRate() {
+                return DEFAULT_FLAKY_RATE;
+            }
+
+            @Override public Double confidence() {
+                return DEFAULT_CONFIDENCE;
+            }
+
             @Override  public ITrackedBranchesConfig getTrackedBranches() {
                 return tracked;
             }
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
index bc5e4c9..aeb07fc 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
@@ -106,8 +106,8 @@
 
         Map<String, String> buildWoChanges = new TreeMap<String, String>() {
             {
-                put("testFailedShoudlBeConsideredAsFlaky", "0000011111");
-                put("testFlakyStableFailure", "0000011111111111");
+                put("testFailedShouldBeConsideredAsFlaky", "0000011111");
+                put("testFlakyStableFailure", "0000010101100101");
             }
         };
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
index 6923122..d765130 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
@@ -47,6 +47,8 @@
     /** Display type. for issue. Kept for backward compatibilty with older records without type code. */
     private String displayType;
 
+    public double flakyRate;
+
     @Nullable
     public String trackedBranchName;
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
index 4bfbba6..9042e1e 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
@@ -19,6 +19,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -43,6 +44,7 @@
 import org.apache.ignite.tcbot.common.util.FutureUtil;
 import org.apache.ignite.tcbot.engine.chain.BuildChainProcessor;
 import org.apache.ignite.tcbot.engine.chain.SingleBuildRunCtx;
+import org.apache.ignite.tcbot.engine.conf.ITcBotConfig;
 import org.apache.ignite.tcbot.engine.defect.BlameCandidate;
 import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
 import org.apache.ignite.tcbot.engine.defect.DefectFirstBuild;
@@ -63,6 +65,10 @@
 import org.apache.ignite.tcignited.build.FatBuildDao;
 import org.apache.ignite.tcignited.build.ITest;
 import org.apache.ignite.tcignited.creds.ICredentialsProv;
+import org.apache.ignite.tcignited.history.IRunHistory;
+
+import static org.apache.ignite.tcignited.history.RunStatus.RES_MISSING;
+import static org.apache.ignite.tcignited.history.RunStatus.RES_OK;
 
 public class BoardService {
     @Inject IIssuesStorage issuesStorage;
@@ -74,6 +80,7 @@
     @Inject IStringCompactor compactor;
     @Inject BuildChainProcessor buildChainProcessor;
     @Inject IUserStorage userStorage;
+    @Inject ITcBotConfig cfg;
 
     /**
      * @param creds Credentials.
@@ -118,7 +125,7 @@
                 rebuild = !freshRebuild.isEmpty() ? freshRebuild.stream().findFirst() : Optional.empty();
 
                 for (DefectIssue issue : cause.issues()) {
-                    BoardDefectIssueUi issueUi = processIssue(tcIgn, rebuild, issue);
+                    BoardDefectIssueUi issueUi = processIssue(tcIgn, rebuild, issue, firstBuild.buildTypeId());
 
                     defectUi.addIssue(issueUi);
                 }
@@ -134,7 +141,7 @@
 
     public BoardDefectIssueUi processIssue(ITeamcityIgnited tcIgn,
                                            Optional<FatBuildCompacted> rebuild,
-                                           DefectIssue issue) {
+                                           DefectIssue issue, int projectId) {
         Optional<ITest> testResult;
 
         String issueType = compactor.getStringFromId(issue.issueTypeCode());
@@ -176,15 +183,47 @@
 
                 if (test.isIgnoredTest() || test.isMutedTest())
                     status = IssueResolveStatus.IGNORED;
+                else if (IssueType.newTestWithHighFlakyRate.code().equals(issueType)) {
+                    int fullSuiteNameAndFullTestName = issue.testNameCid();
+
+                    int branchName = rebuild.get().branchName();
+
+                    IRunHistory runStat = tcIgn.getTestRunHist(fullSuiteNameAndFullTestName, projectId, branchName);
+
+                    if (runStat == null)
+                        status = IssueResolveStatus.UNKNOWN;
+                    else {
+                        List<Integer> runResults = runStat.getLatestRunResults();
+                        if (runResults == null)
+                            status = IssueResolveStatus.UNKNOWN;
+                        else {
+                            int confidenceOkTestsRow = Math.max(1,
+                                (int) Math.ceil(Math.log(1 - cfg.confidence()) / Math.log(1 - issue.getFlakyRate() / 100.0)));
+                            Collections.reverse(runResults);
+                            int okTestRow = 0;
+
+                            for (Integer run : runResults) {
+                                if (run == null || run == RES_MISSING.getCode())
+                                    continue;
+                                if (run == RES_OK.getCode() && (okTestRow < confidenceOkTestsRow))
+                                    okTestRow++;
+                                else
+                                    break;
+                            }
+
+                            status = okTestRow >= confidenceOkTestsRow ? IssueResolveStatus.FIXED : IssueResolveStatus.FAILING;
+                        }
+                    }
+                }
                 else
                     status = test.isFailedTest(compactor) ? IssueResolveStatus.FAILING : IssueResolveStatus.FIXED;
 
                 FatBuildCompacted fatBuildCompacted = rebuild.get();
                 Long testNameId = test.getTestId();
-                String projectId = fatBuildCompacted.projectId(compactor);
+                String RebuildProjectId = fatBuildCompacted.projectId(compactor);
                 String branchName = fatBuildCompacted.branchName(compactor);
 
-                webUrl = DsTestFailureUi.buildWebLink(tcIgn, testNameId, projectId, branchName);
+                webUrl = DsTestFailureUi.buildWebLink(tcIgn, testNameId, RebuildProjectId, branchName);
             }
             else {
                 //exception for new test. removal of test means test is fixed
@@ -244,6 +283,7 @@
                 int issueTypeCid = compactor.getStringId(issue.type);
                 Integer testNameCid = compactor.getStringIdIfPresent(testName);
                 int trackedBranchCid = compactor.getStringId(issue.trackedBranchName);
+                double flakyRate = issue.flakyRate;
 
                 int tcSrvCodeCid = compactor.getStringId(srvCode);
                 defectStorage.merge(tcSrvCodeCid, srvId, fatBuild,
@@ -252,7 +292,7 @@
 
                         defect.trackedBranchCidSetIfEmpty(trackedBranchCid);
 
-                        defect.computeIfAbsent(fatBuild).addIssue(issueTypeCid, testNameCid);
+                        defect.computeIfAbsent(fatBuild).addIssue(issueTypeCid, testNameCid, flakyRate);
 
                         defect.removeOldVerBlameCandidates();
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/ITcBotConfig.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/ITcBotConfig.java
index 3c9af9a..1d38a21 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/ITcBotConfig.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/ITcBotConfig.java
@@ -30,9 +30,21 @@
     /** Default server code. */
     public String DEFAULT_SERVER_CODE = "apache";
 
+    /** Default flaky rate. */
+    public Integer DEFAULT_FLAKY_RATE = 20;
+
+    /** Default confidence. */
+    public Double DEFAULT_CONFIDENCE = 0.95;
+
     /** */
     public String primaryServerCode();
 
+    /** */
+    public Integer flakyRate();
+
+    /** */
+    public Double confidence();
+
     /**
      * @return Tracked branches configuration for TC Bot.
      */
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/TcBotJsonConfig.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/TcBotJsonConfig.java
index 818ba5a..0fd0f24 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/TcBotJsonConfig.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/TcBotJsonConfig.java
@@ -38,6 +38,12 @@
     /** Primary server ID. */
     @Nullable private String primaryServerCode;
 
+    /** Flaky rate to consider test as a flaky test. */
+    @Nullable private Integer flakyRate;
+
+    /** Сonfidence (used with flaky tests). */
+    @Nullable private Double confidence;
+
     /** Additional list Servers to be used for validation of PRs, but not for tracking any branches. */
     private List<TcServerConfig> tcServers = new ArrayList<>();
 
@@ -83,6 +89,20 @@
         return primaryServerCode;
     }
 
+    /**
+     * @return Flaky rate to consider test as a flaky test.
+     */
+    @Nullable public Integer flakyRate() {
+        return flakyRate;
+    }
+
+    /**
+     * @return Сonfidence.
+     */
+    @Nullable public Double confidence() {
+        return confidence;
+    }
+
     public Optional<TcServerConfig> getTcConfig(String code) {
         return tcServers.stream().filter(s -> Objects.equals(code, s.getCode())).findAny();
     }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
index fd271ce..568296f 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
@@ -33,8 +33,8 @@
         this.build = build;
     }
 
-    public DefectFirstBuild addIssue(int typeCid, Integer testNameCid) {
-        issues.add(new DefectIssue(typeCid, testNameCid));
+    public DefectFirstBuild addIssue(int typeCid, Integer testNameCid, double flakyRate) {
+        issues.add(new DefectIssue(typeCid, testNameCid, flakyRate));
 
         return this;
     }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
index 5ee60c7..726332f 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
@@ -24,9 +24,12 @@
     private int issueTypeCode;
     private int testOrSuiteName;
 
-    public DefectIssue(int issueTypeCode, Integer testNameCid) {
+    private double flakyRate;
+
+    public DefectIssue(int issueTypeCode, Integer testNameCid, double flakyRate) {
         this.issueTypeCode = issueTypeCode;
         testOrSuiteName = testNameCid;
+        this.flakyRate = flakyRate;
     }
 
     /** {@inheritDoc} */
@@ -52,4 +55,8 @@
     public int issueTypeCode() {
         return issueTypeCode;
     }
+
+    public double getFlakyRate() {
+        return flakyRate;
+    }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/EventTemplates.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/EventTemplates.java
index b979475..415f481 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/EventTemplates.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/EventTemplates.java
@@ -27,9 +27,11 @@
 import static org.apache.ignite.tcignited.history.RunStatus.RES_MISSING;
 
 public class EventTemplates {
-    private static final int OK = RES_OK.getCode();
-    private static final int FAIL = RES_FAILURE.getCode();
-    private static final int MISSING = RES_MISSING.getCode();
+    public static final int OK = RES_OK.getCode();
+    public static final int FAIL = RES_FAILURE.getCode();
+    public static final int MISSING = RES_MISSING.getCode();
+    public static final int OK_OR_FAILURE = RES_OK_OR_FAILURE.getCode();
+    public static final int CRITICAL_FAILURE = RES_CRITICAL_FAILURE.getCode();
 
     public static final EventTemplate newFailure = new EventTemplate(
             new int[]{OK, OK, OK, OK, OK},
@@ -37,8 +39,8 @@
     );
 
     public static final EventTemplate newCriticalFailure = new EventTemplate(
-            new int[]{RES_OK_OR_FAILURE.getCode(), RES_OK_OR_FAILURE.getCode(), RES_OK_OR_FAILURE.getCode(), RES_OK_OR_FAILURE.getCode(), RES_OK_OR_FAILURE.getCode()},
-            new int[]{RES_CRITICAL_FAILURE.getCode(), RES_CRITICAL_FAILURE.getCode(), RES_CRITICAL_FAILURE.getCode(), RES_CRITICAL_FAILURE.getCode()}
+            new int[]{OK_OR_FAILURE, OK_OR_FAILURE, OK_OR_FAILURE, OK_OR_FAILURE, OK_OR_FAILURE},
+            new int[]{CRITICAL_FAILURE, CRITICAL_FAILURE, CRITICAL_FAILURE, CRITICAL_FAILURE}
     );
 
     public static final EventTemplate newContributedTestFailure = new EventTemplate(
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
index b7b6cce..25171c4 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
@@ -34,7 +34,10 @@
     newCriticalFailure("newCriticalFailure", "New Critical Failure"),
 
     /** New trusted suite failure. */
-    newTrustedSuiteFailure("newTrustedSuiteFailure", "New Trusted Suite failure");
+    newTrustedSuiteFailure("newTrustedSuiteFailure", "New Trusted Suite failure"),
+
+    /** New failure for flaky test. */
+    newTestWithHighFlakyRate("newTestWithHighFlakyRate", "Test with high flaky rate");
 
     /** Code. */
     private final String code;
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
index ac11fb0..92088e6 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
@@ -544,6 +544,10 @@
         return compactor.getStringFromId(projectId);
     }
 
+    public int projectId() {
+        return projectId;
+    }
+
     public List<ProblemOccurrence> problems(IStringCompactor compactor) {
         if (this.problems == null)
              return Collections.emptyList();
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/IRunHistory.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/IRunHistory.java
index b54bc53..ff60b0a 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/IRunHistory.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/history/IRunHistory.java
@@ -16,18 +16,22 @@
  */
 package org.apache.ignite.tcignited.history;
 
+import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import java.util.List;
+import org.apache.ignite.ci.teamcity.ignited.runhist.Invocation;
 
 /**
  * Test or Build run statistics.
  */
 public interface IRunHistory {
-    /**
-     *
-     */
+
     public boolean isFlaky();
 
+    public Iterable<Invocation> invocations();
+
+    public Stream<Invocation> getInvocations();
+
     @Nullable
     List<Integer> getLatestRunResults();