Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a0f91e2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Cert: Use acts.test_decorators.test_info to log metadata" am: 201d60a2

Change-Id: I050253f17faafc0504b7cec07f568b0c9523a0fd
parents 15fe9240 201d60a2
Loading
Loading
Loading
Loading
+68 −81
Original line number Diff line number Diff line
@@ -20,13 +20,14 @@ import time

from mobly import asserts

from acts import signals
from acts.base_test import BaseTestClass

from bluetooth_packets_python3 import hci_packets
from bluetooth_packets_python3 import l2cap_packets
from cert.event_stream import EventStream, FilteringEventStream
from cert.truth import assertThat
from cert.metadata import metadata, MetadataKey
from cert.metadata import metadata


class BogusProto:
@@ -397,103 +398,89 @@ class CertSelfTest(BaseTestClass):
                .then(lambda data: data.value_ == 3)

    def test_metadata_empty(self):
        my_content = [{}]

        class TestClass:

            def record_data(self, content):
                my_content[0] = content

        @metadata()
            def sample_pass_test(self):
        def simple_pass_test(arg):
            pass

            @metadata()
            def sample_skipped_test(self):
                asserts.skip("SKIP")
        try:
            simple_pass_test(1)
        except signals.TestFailure:
            pass
        except Exception as e:
            asserts.fail("@metadata() should only raise signals.TestFailure, "
                         "but raised %s with msg %s instead" %
                         (e.__class__.__name__, str(e)))
        else:
            asserts.fail("@metadata() should not work")

            @metadata()
            def sample_failed_test(self):
                asserts.fail("FAIL")
    def test_metadata_empty_no_function_call(self):

        test_class = TestClass()
        @metadata
        def simple_pass_test(arg):
            pass

        try:
            test_class.sample_pass_test()
        except Exception:
            asserts.fail("Should not raise exception")
        finally:
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_NAME)],
                                 "sample_pass_test")
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_CLASS)],
                                 "TestClass")
            simple_pass_test(1)
        except signals.TestFailure:
            pass
        except Exception as e:
            asserts.fail("@metadata should only raise signals.TestFailure, "
                         "but raised %s with msg %s instead" %
                         (e.__class__.__name__, str(e)))
        else:
            asserts.fail("@metadata should not work")

        try:
            test_class.sample_skipped_test()
        except Exception:
    def test_metadata_pts_missing_id(self):

        @metadata(pts_test_name="Hello world")
        def simple_pass_test(arg):
            pass
        finally:
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_NAME)],
                                 "sample_skipped_test")
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_CLASS)],
                                 "TestClass")

        try:
            test_class.sample_failed_test()
        except Exception:
            simple_pass_test(1)
        except signals.TestFailure:
            pass
        finally:
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_NAME)],
                                 "sample_failed_test")
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_CLASS)],
                                 "TestClass")

    def test_metadata_empty_no_function_call(self):
        my_content = [{}]

        class TestClass:
        except Exception as e:
            asserts.fail("should only raise signals.TestFailure, "
                         "but raised %s with msg %s instead" %
                         (e.__class__.__name__, str(e)))
        else:
            asserts.fail("missing pts_test_id should not work")

            def record_data(self, content):
                my_content[0] = content
    def test_metadata_pts_missing_name(self):

            @metadata()
            def sample_pass_test(self):
        @metadata(pts_test_id="A/B/C")
        def simple_pass_test(arg):
            pass

        test_class = TestClass()

        try:
            test_class.sample_pass_test()
        except Exception:
            asserts.fail("Should not raise exception")
        finally:
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_NAME)],
                                 "sample_pass_test")
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_CLASS)],
                                 "TestClass")

    def test_metadata_pts_test_id(self):
        my_content = [{}]

        class TestClass:
            simple_pass_test(1)
        except signals.TestFailure:
            pass
        except Exception as e:
            asserts.fail("should only raise signals.TestFailure, "
                         "but raised %s with msg %s instead" %
                         (e.__class__.__name__, str(e)))
        else:
            asserts.fail("missing pts_test_name should not work")

            def record_data(self, content):
                my_content[0] = content
    def test_metadata_pts_test_id_and_description(self):

            @metadata(pts_test_id="Hello World")
            def sample_pass_test(self):
        @metadata(pts_test_id="A/B/C", pts_test_name="Hello world")
        def simple_pass_test(arg):
            pass

        test_class = TestClass()

        try:
            test_class.sample_pass_test()
        except Exception:
            asserts.fail("Should not raise exception")
        finally:
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_NAME)],
                                 "sample_pass_test")
            asserts.assert_equal(my_content[0][str(MetadataKey.TEST_CLASS)],
                                 "TestClass")
            asserts.assert_equal(my_content[0][str(MetadataKey.PTS_TEST_ID)],
                                 "Hello World")
            simple_pass_test(1)
        except signals.TestPass as e:
            asserts.assert_true(
                "pts_test_id" in e.extras,
                msg=("pts_test_id not in extra: %s" % str(e.extras)))
            asserts.assert_equal(e.extras["pts_test_id"], "A/B/C")
            asserts.assert_true(
                "pts_test_name" in e.extras,
                msg=("pts_test_name not in extra: %s" % str(e.extras)))
            asserts.assert_equal(e.extras["pts_test_name"], "Hello world")
        else:
            asserts.fail("Must throw an exception using @metadata decorator")
+41 −56
Original line number Diff line number Diff line
@@ -13,83 +13,68 @@
#   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.
import enum
import functools
import inspect

from mobly import asserts

from acts.base_test import BaseTestClass
from acts.test_decorators import test_info


@enum.unique
class MetadataKey(enum.Enum):
    """Enums used for recording UserData section for Gd Cert Tests"""
    # TEST_CLASS + TEST_NAME should uniquely define a test
    TEST_NAME = 'Test Name'
    TEST_CLASS = 'Test Class'
    # A fully qualified PTS test ID such as L2CAP/COS/IEX/BV-01-C
    PTS_TEST_ID = 'PTS Test ID'
def _fail_decorator(msg):

    def __str__(self):
        """
        :return: str representation of |value| instead of "Class.Enum"
        """
        return repr(self)
    def fail_decorator(func):

    def __repr__(self):
        """
        :return: the same str representation since |value| is unique
        """
        return str(self.value)
        @functools.wraps(func)
        def fail(*args, **kwargs):
            asserts.fail(msg)

        return fail

    return fail_decorator

def metadata(_do_not_use=None, pts_test_id=None):

def metadata(_do_not_use=None, pts_test_id=None, pts_test_name=None):
    """
    Record a piece of test metadata in the UserData section of the test summary
    file. The metadata will come with a timestamp, but there is no guarantee
    on the order of when the metadata will be written
    Record a piece of test metadata in the Extra section of the test Record in
    the test summary file. The metadata will come with a timestamp, but there
    is no guarantee on the order of when the metadata will be written

    Note:
    - Metadata is recorded per test case as key-value pairs.
    - TEST_CLASS and TEST_NAME combination can be used to correlate a piece of
      metadata with the ACTS or Mobly generated test run record in the same
      YAML file
    - Metadata is only guaranteed to be written when the test result is PASS,
      FAIL or SKIPPED. When there are test infrastructural errors, metadata
      might not be written successfully
    :param _do_not_use: a positional argument with default value. This argument
                        is only used when @metadata is used instead of
                        @metadata()
                        is to ensure that @metadata(key=value) is used in a
                        functional form instead of @metadata or @metadata(a)
    :param pts_test_id: A fully qualified PTS test ID such as
                        L2CAP/COS/IEX/BV-01-C, see MetadataKey.PTS_TEST_ID
    :return: decorated test case function
                        L2CAP/COS/IEX/BV-01-C
    :param pts_test_name: A human readable test name such as
                          "Request Connection" for the above example
    :return: decorated test case function object
    """
    if _do_not_use is not None:

    def real_decorator(test_case_function):
        def fail(*args, **kwargs):
            asserts.fail("@metadata must be used in functional form such "
                         "as @metadta(key=value)")

        @functools.wraps(test_case_function)
        def wrapper(self: BaseTestClass):
            try:
                test_case_function(self)
            finally:
                content = {
                    str(MetadataKey.TEST_NAME): test_case_function.__name__,
                    str(MetadataKey.TEST_CLASS): self.__class__.__name__
                }
                if pts_test_id:
                    content[str(MetadataKey.PTS_TEST_ID)] = str(pts_test_id)
                self.record_data(content)
        return fail

        return wrapper
    # Create a dictionary of optional parameters
    values = locals()
    args = {arg: values[arg] for arg in inspect.getfullargspec(metadata).args}
    del args["_do_not_use"]

    if _do_not_use is not None:
        asserts.assert_true(
            callable(_do_not_use), "No real positional argument is allowed for"
            " @metadata")
        asserts.assert_true(
            pts_test_id is None,
            "No additional positional argument is allowed for"
            " @metadata")
        return real_decorator(_do_not_use)

    return real_decorator
    # Check if at least one optional parameter is valid
    if not any(args):
        return _fail_decorator("at least one optional argument should be valid")

    # Validate pts_test_id and pts_test_name
    if (pts_test_id or pts_test_name) and \
        (not pts_test_id or not pts_test_name):
        return _fail_decorator("pts_test_id and pts_test_name must both "
                               "be valid if one of them is valid")

    return test_info(**args)