diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/build/util/lib/results | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/build/util/lib/results')
4 files changed, 307 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/util/lib/results/__init__.py b/third_party/libwebrtc/build/util/lib/results/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/__init__.py diff --git a/third_party/libwebrtc/build/util/lib/results/result_sink.py b/third_party/libwebrtc/build/util/lib/results/result_sink.py new file mode 100644 index 0000000000..7d5ce6f980 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_sink.py @@ -0,0 +1,180 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +from __future__ import absolute_import +import base64 +import json +import os + +import six + +import requests # pylint: disable=import-error +from lib.results import result_types + +# Maps result_types to the luci test-result.proto. +# https://godoc.org/go.chromium.org/luci/resultdb/proto/v1#TestStatus +RESULT_MAP = { + result_types.UNKNOWN: 'ABORT', + result_types.PASS: 'PASS', + result_types.FAIL: 'FAIL', + result_types.CRASH: 'CRASH', + result_types.TIMEOUT: 'ABORT', + result_types.SKIP: 'SKIP', + result_types.NOTRUN: 'SKIP', +} + + +def TryInitClient(): + """Tries to initialize a result_sink_client object. + + Assumes that rdb stream is already running. + + Returns: + A ResultSinkClient for the result_sink server else returns None. + """ + try: + with open(os.environ['LUCI_CONTEXT']) as f: + sink = json.load(f)['result_sink'] + return ResultSinkClient(sink) + except KeyError: + return None + + +class ResultSinkClient(object): + """A class to store the sink's post configurations and make post requests. + + This assumes that the rdb stream has been called already and that the + server is listening. + """ + + def __init__(self, context): + base_url = 'http://%s/prpc/luci.resultsink.v1.Sink' % context['address'] + self.test_results_url = base_url + '/ReportTestResults' + self.report_artifacts_url = base_url + '/ReportInvocationLevelArtifacts' + + self.headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'ResultSink %s' % context['auth_token'], + } + + def Post(self, + test_id, + status, + duration, + test_log, + test_file, + artifacts=None, + failure_reason=None): + """Uploads the test result to the ResultSink server. + + This assumes that the rdb stream has been called already and that + server is ready listening. + + Args: + test_id: A string representing the test's name. + status: A string representing if the test passed, failed, etc... + duration: An int representing time in ms. + test_log: A string representing the test's output. + test_file: A string representing the file location of the test. + artifacts: An optional dict of artifacts to attach to the test. + failure_reason: An optional string with the reason why the test failed. + Should be None if the test did not fail. + + Returns: + N/A + """ + assert status in RESULT_MAP + expected = status in (result_types.PASS, result_types.SKIP) + result_db_status = RESULT_MAP[status] + + tr = { + 'expected': + expected, + 'status': + result_db_status, + 'tags': [ + { + 'key': 'test_name', + 'value': test_id, + }, + { + # Status before getting mapped to result_db statuses. + 'key': 'raw_status', + 'value': status, + } + ], + 'testId': + test_id, + 'testMetadata': { + 'name': test_id, + } + } + + artifacts = artifacts or {} + if test_log: + # Upload the original log without any modifications. + b64_log = six.ensure_str(base64.b64encode(six.ensure_binary(test_log))) + artifacts.update({'Test Log': {'contents': b64_log}}) + tr['summaryHtml'] = '<text-artifact artifact-id="Test Log" />' + if artifacts: + tr['artifacts'] = artifacts + if failure_reason: + tr['failureReason'] = { + 'primaryErrorMessage': _TruncateToUTF8Bytes(failure_reason, 1024) + } + + if duration is not None: + # Duration must be formatted to avoid scientific notation in case + # number is too small or too large. Result_db takes seconds, not ms. + # Need to use float() otherwise it does substitution first then divides. + tr['duration'] = '%.9fs' % float(duration / 1000.0) + + if test_file and str(test_file).startswith('//'): + tr['testMetadata']['location'] = { + 'file_name': test_file, + 'repo': 'https://chromium.googlesource.com/chromium/src', + } + + res = requests.post(url=self.test_results_url, + headers=self.headers, + data=json.dumps({'testResults': [tr]})) + res.raise_for_status() + + def ReportInvocationLevelArtifacts(self, artifacts): + """Uploads invocation-level artifacts to the ResultSink server. + + This is for artifacts that don't apply to a single test but to the test + invocation as a whole (eg: system logs). + + Args: + artifacts: A dict of artifacts to attach to the invocation. + """ + req = {'artifacts': artifacts} + res = requests.post(url=self.report_artifacts_url, + headers=self.headers, + data=json.dumps(req)) + res.raise_for_status() + + +def _TruncateToUTF8Bytes(s, length): + """ Truncates a string to a given number of bytes when encoded as UTF-8. + + Ensures the given string does not take more than length bytes when encoded + as UTF-8. Adds trailing ellipsis (...) if truncation occurred. A truncated + string may end up encoding to a length slightly shorter than length because + only whole Unicode codepoints are dropped. + + Args: + s: The string to truncate. + length: the length (in bytes) to truncate to. + """ + encoded = s.encode('utf-8') + if len(encoded) > length: + # Truncate, leaving space for trailing ellipsis (...). + encoded = encoded[:length - 3] + # Truncating the string encoded as UTF-8 may have left the final codepoint + # only partially present. Pass 'ignore' to acknowledge and ensure this is + # dropped. + return encoded.decode('utf-8', 'ignore') + "..." + return s diff --git a/third_party/libwebrtc/build/util/lib/results/result_sink_test.py b/third_party/libwebrtc/build/util/lib/results/result_sink_test.py new file mode 100755 index 0000000000..3486ad90d1 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_sink_test.py @@ -0,0 +1,102 @@ +#!/usr/bin/env vpython3 +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import os +import sys +import unittest + +# The following non-std imports are fetched via vpython. See the list at +# //.vpython3 +import mock # pylint: disable=import-error +import six + +_BUILD_UTIL_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) +if _BUILD_UTIL_PATH not in sys.path: + sys.path.insert(0, _BUILD_UTIL_PATH) + +from lib.results import result_sink +from lib.results import result_types + + +class InitClientTest(unittest.TestCase): + @mock.patch.dict(os.environ, {}, clear=True) + def testEmptyClient(self): + # No LUCI_CONTEXT env var should prevent a client from being created. + client = result_sink.TryInitClient() + self.assertIsNone(client) + + @mock.patch.dict(os.environ, {'LUCI_CONTEXT': 'some-file.json'}) + def testBasicClient(self): + luci_context_json = { + 'result_sink': { + 'address': 'some-ip-address', + 'auth_token': 'some-auth-token', + }, + } + if six.PY2: + open_builtin_path = '__builtin__.open' + else: + open_builtin_path = 'builtins.open' + with mock.patch(open_builtin_path, + mock.mock_open(read_data=json.dumps(luci_context_json))): + client = result_sink.TryInitClient() + self.assertEqual( + client.test_results_url, + 'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults') + self.assertEqual(client.headers['Authorization'], + 'ResultSink some-auth-token') + + +class ClientTest(unittest.TestCase): + def setUp(self): + context = { + 'address': 'some-ip-address', + 'auth_token': 'some-auth-token', + } + self.client = result_sink.ResultSinkClient(context) + + @mock.patch('requests.post') + def testPostPassingTest(self, mock_post): + self.client.Post('some-test', result_types.PASS, 0, 'some-test-log', None) + self.assertEqual( + mock_post.call_args[1]['url'], + 'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual(data['testResults'][0]['testId'], 'some-test') + self.assertEqual(data['testResults'][0]['status'], 'PASS') + + @mock.patch('requests.post') + def testPostFailingTest(self, mock_post): + self.client.Post('some-test', + result_types.FAIL, + 0, + 'some-test-log', + None, + failure_reason='omg test failure') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual(data['testResults'][0]['status'], 'FAIL') + self.assertEqual(data['testResults'][0]['testMetadata']['name'], + 'some-test') + self.assertEqual( + data['testResults'][0]['failureReason']['primaryErrorMessage'], + 'omg test failure') + + @mock.patch('requests.post') + def testPostWithTestFile(self, mock_post): + self.client.Post('some-test', result_types.PASS, 0, 'some-test-log', + '//some/test.cc') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual( + data['testResults'][0]['testMetadata']['location']['file_name'], + '//some/test.cc') + self.assertEqual(data['testResults'][0]['testMetadata']['name'], + 'some-test') + self.assertIsNotNone(data['testResults'][0]['summaryHtml']) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/util/lib/results/result_types.py b/third_party/libwebrtc/build/util/lib/results/result_types.py new file mode 100644 index 0000000000..48ba88cdbe --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_types.py @@ -0,0 +1,25 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Module containing base test results classes.""" + +# The test passed. +PASS = 'SUCCESS' + +# The test was intentionally skipped. +SKIP = 'SKIPPED' + +# The test failed. +FAIL = 'FAILURE' + +# The test caused the containing process to crash. +CRASH = 'CRASH' + +# The test timed out. +TIMEOUT = 'TIMEOUT' + +# The test ran, but we couldn't determine what happened. +UNKNOWN = 'UNKNOWN' + +# The test did not run. +NOTRUN = 'NOTRUN' |