#!/usr/bin/env vpython3
# Copyright 2021 The Chromium Authors
# 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
from unittest import mock
_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
_FAKE_CONTEXT = {
'address': 'some-ip-address',
'auth_token': 'some-auth-token',
}
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': _FAKE_CONTEXT,
}
with mock.patch('builtins.open',
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.session.headers['Authorization'],
'ResultSink some-auth-token')
@mock.patch('requests.Session')
def testReuseSession(self, mock_session):
client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
client.Post('some-test', result_types.PASS, 0, 'some-test-log', None)
client.Post('some-test', result_types.PASS, 0, 'some-test-log', None)
self.assertEqual(mock_session.call_count, 1)
self.assertEqual(client.session.post.call_count, 2)
@mock.patch('requests.Session.close')
def testCloseClient(self, mock_close):
client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
client.close()
mock_close.assert_called_once()
@mock.patch('requests.Session.close')
def testClientAsContextManager(self, mock_close):
with result_sink.ResultSinkClient(_FAKE_CONTEXT) as client:
mock_close.assert_not_called()
mock_close.assert_called_once()
class ClientTest(unittest.TestCase):
def setUp(self):
self.client = result_sink.ResultSinkClient(_FAKE_CONTEXT)
@mock.patch('requests.Session.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.Session.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.Session.post')
def testPostWithTestLogAndHTMLSummary(self, mock_post):
# This is under max length, but will be over when test log
# artifact is included.
test_artifact = '' % 'b' * (
result_sink.HTML_SUMMARY_MAX - 35)
self.client.Post('some-test',
result_types.PASS,
0,
'some-test-log',
'//some/test.cc',
html_artifact=test_artifact)
data = json.loads(mock_post.call_args[1]['data'])
self.assertIsNotNone(data['testResults'][0]['summaryHtml'])
self.assertTrue(
len(data['testResults'][0]['summaryHtml']) <
result_sink.HTML_SUMMARY_MAX)
self.assertTrue(result_sink._HTML_SUMMARY_ARTIFACT in data['testResults'][0]
['summaryHtml'])
self.assertTrue(
result_sink._TEST_LOG_ARTIFACT in data['testResults'][0]['summaryHtml'])
@mock.patch('requests.Session.post')
def testPostWithTooLongSummary(self, mock_post):
# This will be over max length.
test_artifact = ('' % 'b' *
result_sink.HTML_SUMMARY_MAX)
self.client.Post('some-test',
result_types.PASS,
0,
'some-test-log',
'//some/test.cc',
html_artifact=test_artifact)
data = json.loads(mock_post.call_args[1]['data'])
self.assertIsNotNone(data['testResults'][0]['summaryHtml'])
self.assertTrue(
len(data['testResults'][0]['summaryHtml']) <
result_sink.HTML_SUMMARY_MAX)
self.assertTrue(result_sink._HTML_SUMMARY_ARTIFACT in data['testResults'][0]
['summaryHtml'])
@mock.patch('requests.Session.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'])
@mock.patch('requests.Session.post')
def testPostWithVariant(self, mock_post):
self.client.Post('some-test',
result_types.PASS,
0,
'some-test-log',
None,
variant={
'key1': 'value1',
'key2': 'value2'
})
data = json.loads(mock_post.call_args[1]['data'])
self.assertEqual(data['testResults'][0]['variant'],
{'def': {
'key1': 'value1',
'key2': 'value2'
}})
@mock.patch('requests.Session.post')
def testPostWithTags(self, mock_post):
self.client.Post('some-test',
result_types.PASS,
0,
'some-test-log',
None,
tags=[('key1', 'value1'), ('key2', 'value2')])
data = json.loads(mock_post.call_args[1]['data'])
self.assertIn({
'key': 'key1',
'value': 'value1'
}, data['testResults'][0]['tags'])
self.assertIn({
'key': 'key2',
'value': 'value2'
}, data['testResults'][0]['tags'])
if __name__ == '__main__':
unittest.main()