Repository: dmwm/wmcore
Issue: Don't duplicate error report in the FJR

Apparently multicore workflows can add the same exception/error multiple times into the FJR,
e.g.:
amaltaro_ReReco_badBlocks_HG1904_Validation_190320_153739_3035

before persisting it in the FJR, we might compare whether the same exit code + exception type has already occurred in that step, if so, then we just skip it.

Changed files:
src/python/WMCore/FwkJobReport/Report.py, test/python/WMCore_t/FwkJobReport_t/Report_t.py

diff --git a/src/python/WMCore/FwkJobReport/Report.py b/src/python/WMCore/FwkJobReport/Report.py
index 09ca0e704..855fe32a3 100644
--- a/src/python/WMCore/FwkJobReport/Report.py
+++ b/src/python/WMCore/FwkJobReport/Report.py
@@ -314,6 +314,9 @@ class Report(object):
             reportError = getattr(reportStep.errors, "error%i" % i)
             if getattr(reportError, 'exitCode', None):
                 returnCodes.add(int(reportError.exitCode))
+            else:
+                # exitCode is likely set to None(?!?)
+                returnCodes.add(99999)
 
         return returnCodes
 
@@ -579,6 +582,14 @@ class Report(object):
             # Assumption: Adding an error fails a step
             self.addStep(stepName, status=1)
 
+        if exitCode is not None:
+            exitCode = int(exitCode)
+
+        setExitCodes = self.getStepExitCodes(stepName)
+        if exitCode in setExitCodes:
+            logging.warning("Exit code: %s has been already added to the job report", exitCode)
+            return
+
         stepSection = self.retrieveStep(stepName)
         errorCount = getattr(stepSection.errors, "errorCount", 0)
         errEntry = "error%s" % errorCount
@@ -1125,7 +1136,7 @@ class Report(object):
         fileInfo = FileInfo()
 
         if not stepReport:
-            return None
+            return
 
         listOfModules = getattr(stepReport, 'outputModules', None)
 
@@ -1136,7 +1147,7 @@ class Report(object):
                 if not aFile:
                     msg = "Could not find file%i in module" % n
                     logging.error(msg)
-                    return None
+                    return
                 fileInfo(fileReport=aFile, step=step, outputModule=module)
 
         return
diff --git a/test/python/WMCore_t/FwkJobReport_t/Report_t.py b/test/python/WMCore_t/FwkJobReport_t/Report_t.py
index b6ac8731f..9137e9c85 100644
--- a/test/python/WMCore_t/FwkJobReport_t/Report_t.py
+++ b/test/python/WMCore_t/FwkJobReport_t/Report_t.py
@@ -294,7 +294,7 @@ class ReportTest(unittest.TestCase):
             "Error: Error0 section is missing."
         assert myReport.data.cmsRun1.errors.error0.type == "CMSException", \\
             "Error: Wrong error type."
-        assert myReport.data.cmsRun1.errors.error0.exitCode == "8001", \\
+        assert myReport.data.cmsRun1.errors.error0.exitCode == 8001, \\
             "Error: Wrong exit code."
         assert myReport.data.cmsRun1.errors.error0.details == cmsException, \\
             "Error: Error details are wrong:\\n|%s|\\n|%s|" % (myReport.data.cmsRun1.errors.error0.details,
@@ -541,16 +541,49 @@ class ReportTest(unittest.TestCase):
         Test and see if we can get an exit code out of a report
 
         Note: Errors without a return code return 99999
+        getStepExitCode: returns the first valid and non-zero exit code
+        getExitCode: uses the method above to get an exit code
+        getStepExitCodes: returns a set of all exit codes within the step
         """
 
         report = Report("cmsRun1")
         self.assertEqual(report.getExitCode(), 0)
+        self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 0)
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {})
+        self.assertItemsEqual(report.getStepErrors(stepName="cmsRun1"), {})
+
         report.addError(stepName="cmsRun1", exitCode=None, errorType="test", errorDetails="test")
+        # None is not a valid exitCode, but it will get mapped to 99999
         self.assertEqual(report.getExitCode(), 99999)
         self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 99999)
-        report.addError(stepName="cmsRun1", exitCode='12345', errorType="test", errorDetails="test")
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {99999})
+        self.assertEqual(report.getStepErrors(stepName="cmsRun1")['errorCount'], 1)
+
+        report.addError(stepName="cmsRun1", exitCode=12345, errorType="test", errorDetails="test")
+        self.assertEqual(report.getExitCode(), 12345)
+        self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 12345)
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {99999, 12345})
+        self.assertEqual(report.getStepErrors(stepName="cmsRun1")['errorCount'], 2)
+
+        report.addError(stepName="cmsRun1", exitCode=123, errorType="test", errorDetails="test")
+        self.assertEqual(report.getExitCode(), 12345)
+        self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 12345)
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {99999, 12345, 123})
+        self.assertEqual(report.getStepErrors(stepName="cmsRun1")['errorCount'], 3)
+
+        # now try to record the same exit code once again
+        report.addError(stepName="cmsRun1", exitCode=12345, errorType="test", errorDetails="test")
+        self.assertEqual(report.getExitCode(), 12345)
+        self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 12345)
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {99999, 12345, 123})
+        self.assertEqual(report.getStepErrors(stepName="cmsRun1")['errorCount'], 3)
+
+        # and once again, but different type and details (which does not matter)
+        report.addError(stepName="cmsRun1", exitCode=12345, errorType="testAA", errorDetails="testAA")
         self.assertEqual(report.getExitCode(), 12345)
         self.assertEqual(report.getStepExitCode(stepName="cmsRun1"), 12345)
+        self.assertItemsEqual(report.getStepExitCodes(stepName="cmsRun1"), {99999, 12345, 123})
+        self.assertEqual(report.getStepErrors(stepName="cmsRun1")['errorCount'], 3)
 
     def testProperties(self):
         """