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 2019/08/15 17:57:51 UTC

[ignite-teamcity-bot] branch master updated: Board: Displaying each test details added - Fixes #153.

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 47c4121  Board: Displaying each test details added - Fixes #153.
47c4121 is described below

commit 47c4121ebaeda1bd9fa7168cc767dea2b2653106
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Thu Aug 15 20:53:35 2019 +0300

    Board: Displaying each test details added - Fixes #153.
    
    Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
 .../org/apache/ignite/ci/web/model/Version.java    |   2 +-
 .../src/main/webapp/board/index.html               |  35 +++-
 .../ignite/tcbot/engine/board/BoardService.java    | 180 ++++++++++++---------
 .../tcbot/engine/board/IssueResolveStatus.java     |  21 +++
 .../ignite/tcbot/engine/ui/BoardDefectIssueUi.java |  70 ++++++++
 .../tcbot/engine/ui/BoardDefectSummaryUi.java      |  39 ++++-
 .../ignite/tcbot/engine/ui/ShortTestFailureUi.java |  27 ++--
 7 files changed, 282 insertions(+), 92 deletions(-)

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 2f2bb49..34e494a 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
@@ -28,7 +28,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 = "20190814";
+    public static final String VERSION = "20190815";
 
     /** Java version, where Web App is running. */
     public String javaVer;
diff --git a/ignite-tc-helper-web/src/main/webapp/board/index.html b/ignite-tc-helper-web/src/main/webapp/board/index.html
index ff74043..92ebc68 100644
--- a/ignite-tc-helper-web/src/main/webapp/board/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/board/index.html
@@ -54,7 +54,8 @@
                     {text: "Commits", value: 'blameCandidateSummary'},
                     {text: "Issues", value: 'cntissues'},
                     {text: "Fixed", value: 'fixedissues'},
-                    {text: "Not Fixed", value: 'notfixedissues'},
+                    {text: "Ignored", value: 'cntignoredissues'},
+                    {text: "Not Fixed", value: 'cntfailingissues'},
                     {text: "Unclear", value: 'unclearissues'}
                 ]
             },
@@ -183,11 +184,24 @@
                     </div>
 
                     Tests affected:
-                    <div v-for="(test) in item.testOrSuitesAffected">
-                        {{ test }}
-                    </div>
-
-                    <button v-on:click="onResolve(item.id)">Resolve</button>
+                    <table>
+                        <tr v-for="(issue) in item.allIssues">
+                            <td v-if="issue.status === 'FIXED'">
+                                Fixed
+                            </td>
+                            <td v-else-if="issue.status === 'FAILING'">
+                                Still Failing
+                            </td>
+                            <td v-else>
+                                {{ issue.status }}
+                            </td>
+
+                            <td>{{ issue.issueType }}</td>
+                            <td>{{ issue.name }}</td>
+                        </tr>
+                    </table>
+
+                    <button v-if="item.cntFailingIssues === 0" v-on:click="onResolve(item.id)">Resolve</button>
                 </td>
             </template>
 
@@ -209,8 +223,13 @@
             <template v-slot:item.fixedIssues="{ item }">
                 <span class='visaStage' style="background: #12AD5E" v-if="item.fixedIssues!=null" >  {{ item.fixedIssues }} </span>
             </template>
-            <template v-slot:item.notFixedIssues="{ item }" >
-                <span class='visaStage' style="background: red"  v-if="item.notFixedIssues!=null" >  {{ item.notFixedIssues }} </span>
+            <template v-slot:item.cntIgnoredIssues="{ item }">
+                <span class='visaStage' style="background: darkorange"
+                      v-if="item.cntIgnoredIssues!=0"
+                      :title="item.summaryIgnoredIssues"> {{ item.cntIgnoredIssues }} </span>
+            </template>
+            <template v-slot:item.cntFailingIssues="{ item }" >
+                <span class='visaStage' style="background: red" v-if="item.cntFailingIssues!=0" :title="item.summaryFailingIssues">  {{ item.cntFailingIssues }} </span>
             </template>
 
             <template v-slot:item.unclearIssues="{ item }" >
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 be09320..0405eef 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
@@ -23,7 +23,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -31,6 +30,8 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
+
+import com.google.common.base.Preconditions;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
@@ -49,6 +50,7 @@ import org.apache.ignite.tcbot.engine.defect.DefectIssue;
 import org.apache.ignite.tcbot.engine.defect.DefectsStorage;
 import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
 import org.apache.ignite.tcbot.engine.issue.IssueType;
+import org.apache.ignite.tcbot.engine.ui.BoardDefectIssueUi;
 import org.apache.ignite.tcbot.engine.ui.BoardDefectSummaryUi;
 import org.apache.ignite.tcbot.engine.ui.BoardSummaryUi;
 import org.apache.ignite.tcbot.engine.user.IUserStorage;
@@ -87,7 +89,7 @@ public class BoardService {
 
             String srvCode = next.tcSrvCode(compactor);
 
-            if(!creds.hasAccess(srvCode))
+            if (!creds.hasAccess(srvCode))
                 continue;
 
             ITeamcityIgnited tcIgn = tcProv.server(srvCode, creds);
@@ -96,7 +98,6 @@ public class BoardService {
 
             List<BlameCandidate> candidates = next.blameCandidates();
 
-
             Map<Integer, DefectFirstBuild> build = next.buildsInvolved();
             for (DefectFirstBuild cause : build.values()) {
                 FatBuildCompacted firstBuild = cause.build();
@@ -107,40 +108,19 @@ public class BoardService {
 
                 Stream<FatBuildCompacted> results = FutureUtil.getResults(futures);
                 List<FatBuildCompacted> freshRebuild = results.collect(Collectors.toList());
-                if(!freshRebuild.isEmpty()) {
-                    FatBuildCompacted buildCompacted = freshRebuild.get(0);
-
-                    for (DefectIssue issue : cause.issues()) {
-                        Optional<ITest> any = buildCompacted.getAllTests()
-                            .filter(t -> t.testName() == issue.testNameCid())
-                            .findAny();
-
-                        if(any.isPresent()) {
-                            boolean failed = any.get().isFailedTest(compactor);
-                            if(!failed)
-                                defectUi.addFixedIssue();
-                            else
-                                defectUi.addNotFixedIssue();
-                        } else {
-                            //exception for new test. removal of test means test is fixed
-                            if(IssueType.newContributedTestFailure.code().equals(compactor.getStringFromId(issue.issueTypeCode())))
-                                defectUi.addFixedIssue();
-                            else
-                                defectUi.addUnclearIssue();
-                        }
-
-                        defectUi.addIssue(compactor.getStringFromId(issue.testNameCid()));
-                    }
-                } else {
-                    for (DefectIssue issue : cause.issues()) {
-                        defectUi.addUnclearIssue();
-
-                        defectUi.addIssue(compactor.getStringFromId(issue.testNameCid()));
-                    }
+
+                Optional<FatBuildCompacted> rebuild;
+
+                rebuild = !freshRebuild.isEmpty() ? freshRebuild.stream().findFirst() : Optional.empty();
+
+                for (DefectIssue issue : cause.issues()) {
+                    BoardDefectIssueUi issueUi = processIssue(defectUi, rebuild, issue);
+
+                    defectUi.addIssue(compactor.getStringFromId(issue.testNameCid()), issueUi);
                 }
             }
 
-            defectUi.branch =  next.tcBranch(compactor);
+            defectUi.branch = next.tcBranch(compactor);
 
             res.addDefect(defectUi);
         }
@@ -148,6 +128,57 @@ public class BoardService {
         return res;
     }
 
+    public BoardDefectIssueUi processIssue(BoardDefectSummaryUi defectUi, Optional<FatBuildCompacted> rebuild,
+        DefectIssue issue) {
+        Optional<ITest> testResult;
+
+        if (rebuild.isPresent()) {
+            testResult = rebuild.get().getAllTests()
+                .filter(t -> t.testName() == issue.testNameCid())
+                .findAny();
+        }
+        else
+            testResult = Optional.empty();
+
+        IssueResolveStatus status;
+        if (testResult.isPresent()) {
+            ITest test = testResult.get();
+
+            if (test.isIgnoredTest() || test.isMutedTest())
+                status = IssueResolveStatus.IGNORED;
+            else {
+                boolean failed = test.isFailedTest(compactor);
+                if (!failed) {
+                    defectUi.addFixedIssue();
+
+                    status = IssueResolveStatus.FIXED;
+                }
+                else {
+                    defectUi.addNotFixedIssue();
+
+                    status = IssueResolveStatus.FAILING;
+
+                }
+            }
+        } else{
+            //exception for new test. removal of test means test is fixed
+            if (IssueType.newContributedTestFailure.code().equals(compactor.getStringFromId(issue.issueTypeCode()))) {
+                defectUi.addFixedIssue();
+
+                status = IssueResolveStatus.FIXED;
+            }
+            else {
+                defectUi.addUnclearIssue();
+                status = IssueResolveStatus.UNKNOWN;
+            }
+        }
+
+        return new BoardDefectIssueUi(status,
+            compactor, issue,
+            issue.testNameCid(),
+            issue.issueTypeCode());
+    }
+
     public void issuesToDefectsLater() {
         scheduler.sheduleNamed("issuesToDefects", this::issuesToDefects, 15, TimeUnit.MINUTES);
     }
@@ -207,53 +238,56 @@ public class BoardService {
 
                         defect.removeOldVerBlameCandidates();
 
-                        if(defect.blameCandidates().isEmpty()) {
-                            //save changes because it can be missed in older DB versions
-                            defect.changeMap(changeDao.getAll(srvId, fatBuild.changes()));
-
-                            Map<Integer, ChangeCompacted> map = defect.changeMap();
+                        if(defect.blameCandidates().isEmpty())
+                            fillBlameCandidates(srvId, fatBuild, defect);
 
-                            Collection<ChangeCompacted> values = map.values();
-                            for (ChangeCompacted change : values) {
-                                BlameCandidate candidate = new BlameCandidate();
-                                int vcsUsernameCid = change.vcsUsername();
-                                candidate.vcsUsername(vcsUsernameCid);
-
-                                int tcUserUsername = change.tcUserUsername();
-                                @Nullable TcHelperUser tcHelperUser = null;
-                                if (tcUserUsername != -1)
-                                    tcHelperUser = userStorage.getUser(compactor.getStringFromId(tcUserUsername));
-                                else {
-                                    String strVcsUsername = compactor.getStringFromId(vcsUsernameCid);
-
-                                    if(!Strings.isNullOrEmpty(strVcsUsername) &&
-                                        strVcsUsername.contains("<") && strVcsUsername.contains(">")) {
-                                        int emailStartIdx = strVcsUsername.indexOf('<');
-                                        int emailEndIdx = strVcsUsername.indexOf('>');
-                                        String email = strVcsUsername.substring(emailStartIdx + 1, emailEndIdx);
-                                        tcHelperUser = userStorage.findUserByEmail(email);
-                                    }
-                                }
+                        return defect;
+                    });
 
+            });
 
-                                if (tcHelperUser != null) {
-                                    String username = tcHelperUser.username();
+        return cntDefects.get() + " defects processed for " + cntIssues.get() + " issues checked";
+    }
 
-                                    String fullName = tcHelperUser.fullName();
-                                    candidate.fullDisplayName(compactor.getStringId(fullName));
-                                    candidate.tcHelperUserUsername(compactor.getStringId(username));
-                                }
+    private void fillBlameCandidates(int srvId, FatBuildCompacted fatBuild, DefectCompacted defect) {
+        //save changes because it can be missed in older DB versions
+        defect.changeMap(changeDao.getAll(srvId, fatBuild.changes()));
+
+        Map<Integer, ChangeCompacted> map = defect.changeMap();
+
+        Collection<ChangeCompacted> values = map.values();
+        for (ChangeCompacted change : values) {
+            BlameCandidate candidate = new BlameCandidate();
+            int vcsUsernameCid = change.vcsUsername();
+            candidate.vcsUsername(vcsUsernameCid);
+
+            int tcUserUsername = change.tcUserUsername();
+            @Nullable TcHelperUser tcHelperUser = null;
+            if (tcUserUsername != -1)
+                tcHelperUser = userStorage.getUser(compactor.getStringFromId(tcUserUsername));
+            else {
+                String strVcsUsername = compactor.getStringFromId(vcsUsernameCid);
+
+                if(!Strings.isNullOrEmpty(strVcsUsername) &&
+                    strVcsUsername.contains("<") && strVcsUsername.contains(">")) {
+                    int emailStartIdx = strVcsUsername.indexOf('<');
+                    int emailEndIdx = strVcsUsername.indexOf('>');
+                    String email = strVcsUsername.substring(emailStartIdx + 1, emailEndIdx);
+                    tcHelperUser = userStorage.findUserByEmail(email);
+                }
+            }
 
-                                defect.addBlameCandidate(candidate);
-                            }
-                        }
 
-                        return defect;
-                    });
+            if (tcHelperUser != null) {
+                String username = tcHelperUser.username();
 
-            });
+                String fullName = tcHelperUser.fullName();
+                candidate.fullDisplayName(compactor.getStringId(fullName));
+                candidate.tcHelperUserUsername(compactor.getStringId(username));
+            }
 
-        return cntDefects.get() + " defects processed for " + cntIssues.get() + " issues checked";
+            defect.addBlameCandidate(candidate);
+        }
     }
 
     public void resolveDefect(Integer defectId, ICredentialsProv creds) {
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java
new file mode 100644
index 0000000..d76fac9
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.tcbot.engine.board;
+
+public enum IssueResolveStatus {
+    FIXED, FAILING, IGNORED, UNKNOWN
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
new file mode 100644
index 0000000..76927ad
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.tcbot.engine.ui;
+
+import com.google.common.base.Strings;
+import org.apache.ignite.tcbot.engine.board.IssueResolveStatus;
+import org.apache.ignite.tcbot.engine.defect.DefectIssue;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+
+/**
+ * UI version for displaying org.apache.ignite.tcbot.engine.defect.DefectIssue and its current status
+ */
+public class BoardDefectIssueUi {
+    private transient IStringCompactor compactor;
+    private transient DefectIssue issue;
+    private IssueResolveStatus status;
+
+    public BoardDefectIssueUi(IssueResolveStatus status, IStringCompactor compactor,
+        DefectIssue issue, int testNameCid, int code) {
+        this.status = status;
+        this.compactor = compactor;
+        this.issue = issue;
+    }
+
+    public String getName() {
+        String name = compactor.getStringFromId(issue.testNameCid());
+
+        //todo check if it is a suite name
+
+        String suiteName = null, testName = null;
+
+        String[] split = Strings.nullToEmpty(name).split("\\:");
+        if (split.length >= 2) {
+            suiteName = ShortTestFailureUi.extractSuite(split[0]);
+            testName = ShortTestFailureUi.extractTest(split[1]);
+        }
+
+        if (testName != null && suiteName != null)
+            return suiteName + ":" + testName;
+
+        return name;
+
+    }
+
+    public String getIssueType() {
+        return compactor.getStringFromId(issue.issueTypeCode());
+    }
+
+    public String getStatus() {
+        return status.toString();
+    }
+
+    public IssueResolveStatus status() {
+        return status;
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
index 1495a64..d9a61cb 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
@@ -17,11 +17,13 @@
 package org.apache.ignite.tcbot.engine.ui;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.ignite.tcbot.engine.board.IssueResolveStatus;
 import org.apache.ignite.tcbot.engine.defect.BlameCandidate;
 import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
@@ -37,9 +39,12 @@ public class BoardDefectSummaryUi {
     public Integer notFixedIssues;
     public Integer unclearIssues;
 
+    @Deprecated
     public List<String> testOrSuitesAffected = new ArrayList<>();
     private Set<String> tags = new HashSet<>();
 
+    private List<BoardDefectIssueUi> issuesList = new ArrayList<>();
+
     public BoardDefectSummaryUi(DefectCompacted defect, IStringCompactor compactor) {
         this.defect = defect;
         this.compactor = compactor;
@@ -110,15 +115,46 @@ public class BoardDefectSummaryUi {
         return defect.id();
     }
 
-    public void addIssue(String testOrBuildName) {
+    public void addIssue(String testOrBuildName, BoardDefectIssueUi issue) {
         if (cntIssues == null)
             cntIssues = 0;
 
         cntIssues++;
 
         testOrSuitesAffected.add(testOrBuildName);
+        issuesList.add(issue);
+    }
+
+    public List<BoardDefectIssueUi> getAllIssues() {
+        return Collections.unmodifiableList(issuesList);
+    }
+
+    public List<BoardDefectIssueUi> getIgnoredIssues() {
+        return issuesList.stream().filter(iss -> iss.status() == IssueResolveStatus.IGNORED).collect(Collectors.toList());
+    }
+
+    public String getSummaryIgnoredIssues() {
+        return limitedListPrint(getIgnoredIssues(), BoardDefectIssueUi::getName);
+    }
+
+    public Integer getCntIgnoredIssues() {
+        return getIgnoredIssues().size();
     }
 
+
+    public List<BoardDefectIssueUi> getFailingIssues() {
+        return issuesList.stream().filter(iss -> iss.status() == IssueResolveStatus.FAILING).collect(Collectors.toList());
+    }
+
+    public String getSummaryFailingIssues() {
+        return limitedListPrint(getFailingIssues(), BoardDefectIssueUi::getName);
+    }
+
+    public Integer getCntFailingIssues() {
+        return getFailingIssues().size();
+    }
+
+
     public void addFixedIssue() {
         if (fixedIssues == null)
             fixedIssues = 0;
@@ -137,6 +173,7 @@ public class BoardDefectSummaryUi {
         this.tags.addAll(parameters);
     }
 
+    @Deprecated
     public void addUnclearIssue() {
         if (unclearIssues == null)
             unclearIssues = 0;
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/ShortTestFailureUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/ShortTestFailureUi.java
index b702274..92a2319 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/ShortTestFailureUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/ShortTestFailureUi.java
@@ -50,15 +50,8 @@ public class ShortTestFailureUi {
 
         String[] split = Strings.nullToEmpty(name).split("\\:");
         if (split.length >= 2) {
-            String suiteShort = split[0].trim();
-            String[] suiteComps = suiteShort.split("\\.");
-            if (suiteComps.length > 1)
-                suiteName = suiteComps[suiteComps.length - 1];
-
-            String testShort = split[1].trim();
-            String[] testComps = testShort.split("\\.");
-            if (testComps.length > 2)
-                testName = testComps[testComps.length - 2] + "." + testComps[testComps.length - 1];
+            this.suiteName = extractSuite(split[0]);
+            this.testName = extractTest(split[1]);
         }
 
         final IRunHistory stat = failure.history(tcIgn, baseBranchId);
@@ -66,4 +59,20 @@ public class ShortTestFailureUi {
 
         return this;
     }
+
+    public static String extractTest(String s) {
+        String testShort = s.trim();
+        String[] testComps = testShort.split("\\.");
+        if (testComps.length > 2)
+            return testComps[testComps.length - 2] + "." + testComps[testComps.length - 1];
+        return null;
+    }
+
+    public static   String extractSuite(String s) {
+        String suiteShort = s.trim();
+        String[] suiteComps = suiteShort.split("\\.");
+        if (suiteComps.length > 1)
+            return suiteComps[suiteComps.length - 1];
+        return null;
+    }
 }