xref: /aosp_15_r20/external/cronet/build/util/lib/results/result_sink_test.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env vpython3
2# Copyright 2021 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import json
7import os
8import sys
9import unittest
10
11from unittest import mock
12
13_BUILD_UTIL_PATH = os.path.abspath(
14    os.path.join(os.path.dirname(__file__), '..', '..'))
15if _BUILD_UTIL_PATH not in sys.path:
16  sys.path.insert(0, _BUILD_UTIL_PATH)
17
18from lib.results import result_sink
19from lib.results import result_types
20
21_FAKE_CONTEXT = {
22    'address': 'some-ip-address',
23    'auth_token': 'some-auth-token',
24}
25
26
27class InitClientTest(unittest.TestCase):
28  @mock.patch.dict(os.environ, {}, clear=True)
29  def testEmptyClient(self):
30    # No LUCI_CONTEXT env var should prevent a client from being created.
31    client = result_sink.TryInitClient()
32    self.assertIsNone(client)
33
34  @mock.patch.dict(os.environ, {'LUCI_CONTEXT': 'some-file.json'})
35  def testBasicClient(self):
36    luci_context_json = {
37        'result_sink': _FAKE_CONTEXT,
38    }
39    with mock.patch('builtins.open',
40                    mock.mock_open(read_data=json.dumps(luci_context_json))):
41      client = result_sink.TryInitClient()
42    self.assertEqual(
43        client.test_results_url,
44        'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults')
45    self.assertEqual(client.session.headers['Authorization'],
46                     'ResultSink some-auth-token')
47
48  @mock.patch('requests.Session')
49  def testReuseSession(self, mock_session):
50    client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
51    client.Post('some-test', result_types.PASS, 0, 'some-test-log', None)
52    client.Post('some-test', result_types.PASS, 0, 'some-test-log', None)
53    self.assertEqual(mock_session.call_count, 1)
54    self.assertEqual(client.session.post.call_count, 2)
55
56  @mock.patch('requests.Session.close')
57  def testCloseClient(self, mock_close):
58    client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
59    client.close()
60    mock_close.assert_called_once()
61
62  @mock.patch('requests.Session.close')
63  def testClientAsContextManager(self, mock_close):
64    with result_sink.ResultSinkClient(_FAKE_CONTEXT) as client:
65      mock_close.assert_not_called()
66    mock_close.assert_called_once()
67
68
69class ClientTest(unittest.TestCase):
70  def setUp(self):
71    self.client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
72
73  @mock.patch('requests.Session.post')
74  def testPostPassingTest(self, mock_post):
75    self.client.Post('some-test', result_types.PASS, 0, 'some-test-log', None)
76    self.assertEqual(
77        mock_post.call_args[1]['url'],
78        'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults')
79    data = json.loads(mock_post.call_args[1]['data'])
80    self.assertEqual(data['testResults'][0]['testId'], 'some-test')
81    self.assertEqual(data['testResults'][0]['status'], 'PASS')
82
83  @mock.patch('requests.Session.post')
84  def testPostFailingTest(self, mock_post):
85    self.client.Post('some-test',
86                     result_types.FAIL,
87                     0,
88                     'some-test-log',
89                     None,
90                     failure_reason='omg test failure')
91    data = json.loads(mock_post.call_args[1]['data'])
92    self.assertEqual(data['testResults'][0]['status'], 'FAIL')
93    self.assertEqual(data['testResults'][0]['testMetadata']['name'],
94                     'some-test')
95    self.assertEqual(
96        data['testResults'][0]['failureReason']['primaryErrorMessage'],
97        'omg test failure')
98
99  @mock.patch('requests.Session.post')
100  def testPostWithTestLogAndHTMLSummary(self, mock_post):
101    # This is under max length, but will be over when test log
102    # artifact is included.
103    test_artifact = '<text-artifact artifact-id="%s" />' % 'b' * (
104        result_sink.HTML_SUMMARY_MAX - 35)
105    self.client.Post('some-test',
106                     result_types.PASS,
107                     0,
108                     'some-test-log',
109                     '//some/test.cc',
110                     html_artifact=test_artifact)
111    data = json.loads(mock_post.call_args[1]['data'])
112    self.assertIsNotNone(data['testResults'][0]['summaryHtml'])
113    self.assertTrue(
114        len(data['testResults'][0]['summaryHtml']) <
115        result_sink.HTML_SUMMARY_MAX)
116    self.assertTrue(result_sink._HTML_SUMMARY_ARTIFACT in data['testResults'][0]
117                    ['summaryHtml'])
118    self.assertTrue(
119        result_sink._TEST_LOG_ARTIFACT in data['testResults'][0]['summaryHtml'])
120
121  @mock.patch('requests.Session.post')
122  def testPostWithTooLongSummary(self, mock_post):
123    # This will be over max length.
124    test_artifact = ('<text-artifact artifact-id="%s" />' % 'b' *
125                     result_sink.HTML_SUMMARY_MAX)
126    self.client.Post('some-test',
127                     result_types.PASS,
128                     0,
129                     'some-test-log',
130                     '//some/test.cc',
131                     html_artifact=test_artifact)
132    data = json.loads(mock_post.call_args[1]['data'])
133    self.assertIsNotNone(data['testResults'][0]['summaryHtml'])
134    self.assertTrue(
135        len(data['testResults'][0]['summaryHtml']) <
136        result_sink.HTML_SUMMARY_MAX)
137    self.assertTrue(result_sink._HTML_SUMMARY_ARTIFACT in data['testResults'][0]
138                    ['summaryHtml'])
139
140  @mock.patch('requests.Session.post')
141  def testPostWithTestFile(self, mock_post):
142    self.client.Post('some-test', result_types.PASS, 0, 'some-test-log',
143                     '//some/test.cc')
144    data = json.loads(mock_post.call_args[1]['data'])
145    self.assertEqual(
146        data['testResults'][0]['testMetadata']['location']['file_name'],
147        '//some/test.cc')
148    self.assertEqual(data['testResults'][0]['testMetadata']['name'],
149                     'some-test')
150    self.assertIsNotNone(data['testResults'][0]['summaryHtml'])
151
152  @mock.patch('requests.Session.post')
153  def testPostWithVariant(self, mock_post):
154    self.client.Post('some-test',
155                     result_types.PASS,
156                     0,
157                     'some-test-log',
158                     None,
159                     variant={
160                         'key1': 'value1',
161                         'key2': 'value2'
162                     })
163    data = json.loads(mock_post.call_args[1]['data'])
164    self.assertEqual(data['testResults'][0]['variant'],
165                     {'def': {
166                         'key1': 'value1',
167                         'key2': 'value2'
168                     }})
169
170  @mock.patch('requests.Session.post')
171  def testPostWithTags(self, mock_post):
172    self.client.Post('some-test',
173                     result_types.PASS,
174                     0,
175                     'some-test-log',
176                     None,
177                     tags=[('key1', 'value1'), ('key2', 'value2')])
178    data = json.loads(mock_post.call_args[1]['data'])
179    self.assertIn({
180        'key': 'key1',
181        'value': 'value1'
182    }, data['testResults'][0]['tags'])
183    self.assertIn({
184        'key': 'key2',
185        'value': 'value2'
186    }, data['testResults'][0]['tags'])
187
188
189if __name__ == '__main__':
190  unittest.main()
191