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