sanitycheck: fix --failed-only handling

- Report build errors as errors, not test failures
- Do not try and build/run tests with build failures
- Fix issue with empty reports when running --only-failed
- Report build errors in the detailed and target reports

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
diff --git a/scripts/sanity_chk/sanitylib.py b/scripts/sanity_chk/sanitylib.py
index ebfc22a..52244f9 100644
--- a/scripts/sanity_chk/sanitylib.py
+++ b/scripts/sanity_chk/sanitylib.py
@@ -1725,7 +1725,7 @@
                     self.instance.status = "skipped"
                     self.instance.reason = "{} overflow".format(res[0])
                 else:
-                    self.instance.status = "failed"
+                    self.instance.status = "error"
                     self.instance.reason = "Build failure"
 
             results = {
@@ -1783,7 +1783,7 @@
             results = {'msg': msg, 'filter': filter_results}
 
         else:
-            self.instance.status = "failed"
+            self.instance.status = "error"
             self.instance.reason = "Cmake build failure"
             logger.error("Cmake build failure: %s for %s" % (self.source_dir, self.platform.name))
             results = {"returncode": p.returncode}
@@ -1973,7 +1973,7 @@
         # The build process, call cmake and build with configured generator
         if op == "cmake":
             results = self.cmake()
-            if self.instance.status == "failed":
+            if self.instance.status in ["failed", "error"]:
                 pipeline.put({"op": "report", "test": self.instance})
             elif self.cmake_only:
                 pipeline.put({"op": "report", "test": self.instance})
@@ -1993,7 +1993,7 @@
             results = self.build()
 
             if not results:
-                self.instance.status = "failed"
+                self.instance.status = "error"
                 self.instance.reason = "Build Failure"
                 pipeline.put({"op": "report", "test": self.instance})
             else:
@@ -2060,7 +2060,7 @@
         self.suite.total_done += 1
         instance = self.instance
 
-        if instance.status in ["failed", "timeout"]:
+        if instance.status in ["error", "failed", "timeout"]:
             self.suite.total_failed += 1
             if self.verbose:
                 status = Fore.RED + "FAILED " + Fore.RESET + instance.reason
@@ -2099,7 +2099,7 @@
                 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
                 instance.testcase.name, status, more_info))
 
-            if instance.status in ["failed", "timeout"]:
+            if instance.status in ["error", "failed", "timeout"]:
                 self.log_info_file(self.inline_logs)
         else:
             sys.stdout.write("\rINFO    - Total complete: %s%4d/%4d%s  %2d%%  skipped: %s%4d%s, failed: %s%4d%s" % (
@@ -2658,6 +2658,8 @@
                     self.device_testing,
                     self.fixtures
                 )
+                for t in tc.cases:
+                    instance.results[t] = None
 
                 if device_testing_filter:
                     for h in self.connected_hardware:
@@ -2815,7 +2817,7 @@
 
     def execute(self):
         def calc_one_elf_size(instance):
-            if instance.status not in ["failed", "skipped"]:
+            if instance.status not in ["error", "failed", "skipped"]:
                 if instance.platform.type != "native":
                     size_calc = instance.calculate_sizes()
                     instance.metrics["ram_size"] = size_calc.get_ram_size()
@@ -2943,7 +2945,6 @@
 
 
     def xunit_report(self, filename, platform=None, full_report=False, append=False):
-
         total = 0
         if platform:
             selected = [platform]
@@ -2978,7 +2979,7 @@
                         else:
                             fails += 1
                 else:
-                    if instance.status in ["failed", "timeout"]:
+                    if instance.status in ["error", "failed", "timeout"]:
                         if instance.reason in ['build_error', 'handler_crash']:
                             errors += 1
                         else:
@@ -2999,10 +3000,20 @@
             # When we re-run the tests, we re-use the results and update only with
             # the newly run tests.
             if os.path.exists(filename) and append:
-                eleTestsuite = eleTestsuites.findall(f'testsuite/[@name="{p}"]')[0]
-                eleTestsuite.attrib['failures'] = "%d" % fails
-                eleTestsuite.attrib['errors'] = "%d" % errors
-                eleTestsuite.attrib['skip'] = "%d" % skips
+                ts = eleTestsuites.findall(f'testsuite/[@name="{p}"]')
+                if ts:
+                    eleTestsuite = ts[0]
+                    eleTestsuite.attrib['failures'] = "%d" % fails
+                    eleTestsuite.attrib['errors'] = "%d" % errors
+                    eleTestsuite.attrib['skip'] = "%d" % skips
+                else:
+                    logger.info(f"Did not find any existing results for {p}")
+                    eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
+                                name=run, time="%f" % duration,
+                                tests="%d" % (total),
+                                failures="%d" % fails,
+                                errors="%d" % (errors), skip="%s" % (skips))
+
             else:
                 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
                                              name=run, time="%f" % duration,
@@ -3039,6 +3050,7 @@
                                     type="failure",
                                     message="failed")
                             else:
+
                                 el = ET.SubElement(
                                     eleTestcase,
                                     'error',
@@ -3048,28 +3060,37 @@
                             log_file = os.path.join(p, "handler.log")
                             el.text = self.process_log(log_file)
 
+                        elif instance.results[k] == 'PASS':
+                            pass
                         elif instance.results[k] == 'SKIP':
+                            el = ET.SubElement(eleTestcase, 'skipped', type="skipped", message="Skipped")
+                        else:
                             el = ET.SubElement(
                                 eleTestcase,
-                                'skipped',
-                                type="skipped",
-                                message="Skipped")
+                                'error',
+                                type="error",
+                                message=f"{instance.reason}")
                 else:
                     if platform:
                         classname = ".".join(instance.testcase.name.split(".")[:2])
                     else:
                         classname = p + ":" + ".".join(instance.testcase.name.split(".")[:2])
 
+                    # remove testcases that are being re-run from exiting reports
+                    for tc in eleTestsuite.findall(f'testcase/[@classname="{classname}"]'):
+                        eleTestsuite.remove(tc)
+
                     eleTestcase = ET.SubElement(eleTestsuite, 'testcase',
                         classname=classname,
                         name="%s" % (instance.testcase.name),
                         time="%f" % handler_time)
-                    if instance.status in ["failed", "timeout"]:
+                    if instance.status in ["error", "failed", "timeout"]:
                         failure = ET.SubElement(
                             eleTestcase,
                             'failure',
                             type="failure",
                             message=instance.reason)
+
                         p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
                         bl = os.path.join(p, "build.log")
                         hl = os.path.join(p, "handler.log")
@@ -3105,7 +3126,7 @@
                            "handler": instance.platform.simulation}
 
                 rowdict["status"] = instance.status
-                if instance.status not in ["failed", "timeout"]:
+                if instance.status not in ["error", "failed", "timeout"]:
                     if instance.handler:
                         rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
                     ram_size = instance.metrics.get("ram_size", 0)