summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/util/lib/results
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/build/util/lib/results
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/libwebrtc/build/util/lib/results/__init__.py0
-rw-r--r--third_party/libwebrtc/build/util/lib/results/result_sink.py180
-rwxr-xr-xthird_party/libwebrtc/build/util/lib/results/result_sink_test.py102
-rw-r--r--third_party/libwebrtc/build/util/lib/results/result_types.py25
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'