blob: 5d413e91f614d7af3f50d556a8d764a0c1c62af4 [file] [log] [blame]
# Copyright 2014 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test the failures_lib module."""
import json
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import failure_message_lib
from chromite.lib import failures_lib
class StepFailureTests(cros_test_lib.TestCase):
"""Tests for StepFailure."""
def testConvertToStageFailureMessage(self):
"""Test ConvertToStageFailureMessage."""
failure = failures_lib.StepFailure("step failure message")
stage_failure_msg = failure.ConvertToStageFailureMessage(
1, "HWTest [sanity]"
)
self.assertEqual(stage_failure_msg.stage_name, "HWTest [sanity]")
self.assertEqual(stage_failure_msg.stage_prefix_name, "HWTest")
self.assertEqual(stage_failure_msg.exception_type, "StepFailure")
self.assertEqual(stage_failure_msg.exception_category, "unknown")
class CompoundFailureTest(cros_test_lib.TestCase):
"""Test the CompoundFailure class."""
def _CreateExceptInfos(self, cls, message="", traceback="", num=1):
"""A helper function to create a list of ExceptInfo objects."""
exc_infos = []
for _ in range(num):
exc_infos.extend(
failures_lib.CreateExceptInfo(cls(message), traceback)
)
return exc_infos
def testHasEmptyList(self):
"""Tests the HasEmptyList method."""
self.assertTrue(failures_lib.CompoundFailure().HasEmptyList())
exc_infos = self._CreateExceptInfos(KeyError)
self.assertFalse(
failures_lib.CompoundFailure(exc_infos=exc_infos).HasEmptyList()
)
def testHasAndMatchesFailureType(self):
"""Tests the HasFailureType and the MatchesFailureType methods."""
# Create a CompoundFailure instance with mixed types of exceptions.
exc_infos = self._CreateExceptInfos(KeyError)
exc_infos.extend(self._CreateExceptInfos(ValueError))
exc = failures_lib.CompoundFailure(exc_infos=exc_infos)
self.assertTrue(exc.HasFailureType(KeyError))
self.assertTrue(exc.HasFailureType(ValueError))
self.assertFalse(exc.MatchesFailureType(KeyError))
self.assertFalse(exc.MatchesFailureType(ValueError))
# Create a CompoundFailure instance with a single type of exceptions.
exc_infos = self._CreateExceptInfos(KeyError, num=5)
exc = failures_lib.CompoundFailure(exc_infos=exc_infos)
self.assertTrue(exc.HasFailureType(KeyError))
self.assertFalse(exc.HasFailureType(ValueError))
self.assertTrue(exc.MatchesFailureType(KeyError))
self.assertFalse(exc.MatchesFailureType(ValueError))
def testHasFatalFailure(self):
"""Tests the HasFatalFailure method."""
exc_infos = self._CreateExceptInfos(KeyError)
exc_infos.extend(self._CreateExceptInfos(ValueError))
exc = failures_lib.CompoundFailure(exc_infos=exc_infos)
self.assertTrue(exc.HasFatalFailure())
self.assertTrue(exc.HasFatalFailure(exempt_exception_list=[KeyError]))
self.assertFalse(
exc.HasFatalFailure(exempt_exception_list=[KeyError, ValueError])
)
exc = failures_lib.CompoundFailure()
self.assertFalse(exc.HasFatalFailure())
def testMessageContainsAllInfo(self):
"""Tests that by default, all information is included in the message."""
exc_infos = self._CreateExceptInfos(
KeyError, message="bar1", traceback="foo1"
)
exc_infos.extend(
self._CreateExceptInfos(
ValueError, message="bar2", traceback="foo2"
)
)
exc = failures_lib.CompoundFailure(exc_infos=exc_infos)
self.assertIn("bar1", str(exc))
self.assertIn("bar2", str(exc))
self.assertIn("KeyError", str(exc))
self.assertIn("ValueError", str(exc))
self.assertIn("foo1", str(exc))
self.assertIn("foo2", str(exc))
def testConvertToStageFailureMessage(self):
"""Test ConvertToStageFailureMessage."""
exc_infos = self._CreateExceptInfos(
KeyError, message="bar1", traceback="foo1"
)
exc_infos.extend(
self._CreateExceptInfos(
failures_lib.StepFailure, message="bar2", traceback="foo2"
)
)
exc = failures_lib.CompoundFailure(
message="compound failure", exc_infos=exc_infos
)
stage_failure_msg = exc.ConvertToStageFailureMessage(
1, "HWTest [sanity]"
)
self.assertEqual(len(stage_failure_msg.inner_failures), 2)
self.assertEqual(stage_failure_msg.stage_name, "HWTest [sanity]")
self.assertEqual(stage_failure_msg.stage_prefix_name, "HWTest")
self.assertEqual(stage_failure_msg.exception_type, "CompoundFailure")
self.assertEqual(stage_failure_msg.exception_category, "unknown")
class SetFailureTypeTest(cros_test_lib.TestCase):
"""Test that the SetFailureType decorator works."""
ERROR_MESSAGE = "You failed!"
class TacoNotTasty(failures_lib.CompoundFailure):
"""Raised when the taco is not tasty."""
class NoGuacamole(TacoNotTasty):
"""Raised when no guacamole in the taco."""
class SubparLunch(failures_lib.CompoundFailure):
"""Raised when the lunch is subpar."""
class FooException(Exception):
"""A foo exception."""
def _GetFunction(self, set_type, raise_type, *args, **kwargs):
"""Returns a function to test.
Args:
set_type: The exception type that the function is decorated with.
raise_type: The exception type that the function raises.
*args: args to pass to the instance of |raise_type|.
Returns:
The function to test.
"""
@failures_lib.SetFailureType(set_type)
def f():
raise raise_type(*args, **kwargs)
return f
def testAssertionFailOnIllegalExceptionType(self):
"""Assertion should fail if the pre-set type is not allowed ."""
self.assertRaises(
AssertionError, self._GetFunction, ValueError, self.FooException
)
def testReraiseAsNewException(self):
"""Tests that the pre-set exception type is raised correctly."""
try:
self._GetFunction(
self.TacoNotTasty, self.FooException, self.ERROR_MESSAGE
)()
except Exception as e:
self.assertIsInstance(e, self.TacoNotTasty)
self.assertTrue(e.msg, self.ERROR_MESSAGE)
self.assertEqual(len(e.exc_infos), 1)
self.assertEqual(e.exc_infos[0].str, self.ERROR_MESSAGE)
self.assertEqual(e.exc_infos[0].type, self.FooException)
self.assertIsInstance(e.exc_infos[0].traceback, str)
def testReraiseACompoundFailure(self):
"""Tests that the list of ExceptInfo objects are copied over."""
tb1 = "Stub traceback1"
tb2 = "Stub traceback2"
org_infos = failures_lib.CreateExceptInfo(
ValueError("No taco."), tb1
) + failures_lib.CreateExceptInfo(OSError("No salsa"), tb2)
try:
self._GetFunction(
self.SubparLunch, self.TacoNotTasty, exc_infos=org_infos
)()
except Exception as e:
self.assertIsInstance(e, self.SubparLunch)
# The orignal exceptions stored in exc_infos are preserved.
self.assertEqual(e.exc_infos, org_infos)
# All essential inforamtion should be included in the message of
# the new excpetion.
self.assertIn(tb1, str(e))
self.assertIn(tb2, str(e))
self.assertIn(str(ValueError), str(e))
self.assertIn(str(OSError), str(e))
self.assertIn(str("No taco"), str(e))
self.assertIn(str("No salsa"), str(e))
# Assert that summary does not contain the textual tracebacks.
self.assertFalse(tb1 in e.ToSummaryString())
self.assertFalse(tb2 in e.ToSummaryString())
def testReraiseACompoundFailureWithEmptyList(self):
"""Tests that a CompoundFailure with empty list is handled correctly."""
try:
self._GetFunction(
self.SubparLunch, self.TacoNotTasty, message="empty list"
)()
except Exception as e:
self.assertIsInstance(e, self.SubparLunch)
self.assertEqual(e.exc_infos[0].type, self.TacoNotTasty)
def testReraiseOriginalException(self):
"""Tests that the original exception is re-raised."""
# NoGuacamole is a subclass of TacoNotTasty, so the wrapper has no
# effect on it.
f = self._GetFunction(self.TacoNotTasty, self.NoGuacamole)
self.assertRaises(self.NoGuacamole, f)
def testPassArgsToWrappedFunctor(self):
"""Tests that we can pass arguments to the functor."""
@failures_lib.SetFailureType(self.TacoNotTasty)
def f(arg):
return arg
@failures_lib.SetFailureType(self.TacoNotTasty)
def g(kwarg=""):
return kwarg
# Test passing arguments.
self.assertEqual(f("foo"), "foo")
# Test passing keyword arguments.
self.assertEqual(g(kwarg="bar"), "bar")
class ExceptInfoTest(cros_test_lib.TestCase):
"""Tests the namedtuple class ExceptInfo."""
def testConvertToExceptInfo(self):
"""Tests converting an exception to an ExceptInfo object."""
traceback = "Stub traceback"
message = "Taco is not a valid option!"
except_infos = failures_lib.CreateExceptInfo(
ValueError(message), traceback
)
self.assertEqual(except_infos[0].type, ValueError)
self.assertEqual(except_infos[0].str, message)
self.assertEqual(except_infos[0].traceback, traceback)
class FailureTypeListTests(cros_test_lib.TestCase):
"""Tests for failure type lists."""
def testFailureTypeList(self):
"""Verify current failure names are already added to the type lists."""
self.assertTrue(
failures_lib.BuildScriptFailure.__name__
in failure_message_lib.BUILD_SCRIPT_FAILURE_TYPES
)
self.assertTrue(
failures_lib.PackageBuildFailure.__name__
in failure_message_lib.PACKAGE_BUILD_FAILURE_TYPES
)
class GetStageFailureMessageFromExceptionTests(cros_test_lib.TestCase):
"""Tests for GetStageFailureMessageFromException"""
def testGetStageFailureMessageFromExceptionOnStepFailure(self):
"""Test GetStageFailureMessageFromException on StepFailure."""
exc = failures_lib.StepFailure("step failure message")
msg = failures_lib.GetStageFailureMessageFromException(
"CommitQueueSync", 1, exc
)
self.assertEqual(msg.build_stage_id, 1)
self.assertEqual(msg.stage_name, "CommitQueueSync")
self.assertEqual(msg.stage_prefix_name, "CommitQueueSync")
self.assertEqual(msg.exception_type, "StepFailure")
self.assertEqual(msg.exception_category, "unknown")
def testGetStageFailureMessageFromExceptionOnException(self):
"""Test GetStageFailureMessageFromException on regular exception."""
exc = ValueError("Invalid valure.")
msg = failures_lib.GetStageFailureMessageFromException(
"CommitQueueSync", 1, exc
)
self.assertEqual(msg.build_stage_id, 1)
self.assertEqual(msg.stage_name, "CommitQueueSync")
self.assertEqual(msg.stage_prefix_name, "CommitQueueSync")
self.assertEqual(msg.exception_type, "ValueError")
self.assertEqual(msg.exception_category, "unknown")
class BuildFailuresForFindit(cros_test_lib.TestCase):
"""Test cases for exporting build failures for Findit integration."""
def testBuildFailuresJson(self):
error = cros_build_lib.RunCommandError("run cmd error")
failed_packages = ["sys-apps/mosys", "chromeos-base/cryptohome"]
build_failure = failures_lib.PackageBuildFailure(
error, "build_packages", failed_packages
)
self.assertSetEqual(set(failed_packages), build_failure.failed_packages)
failure_json = build_failure.BuildCompileFailureOutputJson()
values = json.loads(failure_json)
failures = values["failures"]
self.assertEqual(len(failures), 2)
# Verify both output targets are not equal, this makes sure the loop
# below is correct.
self.assertNotEqual(
failures[0]["output_targets"], failures[1]["output_targets"]
)
for value in failures:
self.assertEqual(value["rule"], "emerge")
self.assertIn(value["output_targets"], failed_packages)