| # Copyright 2017 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. | |
| """Tests for upload_test_result_artifacts.""" | |
| import json | |
| import mock | |
| import os | |
| import random | |
| import string | |
| import tempfile | |
| import unittest | |
| import upload_test_result_artifacts | |
| class UploadTestResultArtifactsTest(unittest.TestCase): | |
| def setUp(self): | |
| # Used for load tests | |
| self._temp_files = [] | |
| def tearDown(self): | |
| # Used for load tests | |
| for fname in self._temp_files: | |
| os.unlink(fname) | |
| ### These are load tests useful for seeing how long it takes to upload | |
| ### different kinds of test results files. They won't be run as part of | |
| ### presubmit testing, since they take a while and talk to the network, | |
| ### but the code will stay here in case anyone wants to edit the code | |
| ### and wants to check performance. Change the test names from 'loadTestBlah' | |
| ### to 'testBlah' to get them to run. | |
| def makeTemp(self, size): | |
| _, fname = tempfile.mkstemp() | |
| with open(fname, 'w') as f: | |
| f.write(random.choice(string.ascii_letters) * size) | |
| self._temp_files.append(fname) | |
| return os.path.basename(fname) | |
| def makeTestJson(self, num_tests, artifact_size): | |
| return { | |
| 'tests': { | |
| 'suite': { | |
| 'test%d' % i: { | |
| 'artifacts': { | |
| 'artifact': self.makeTemp(artifact_size), | |
| }, | |
| 'expected': 'PASS', | |
| 'actual': 'PASS', | |
| } for i in range(num_tests) | |
| } | |
| }, | |
| 'artifact_type_info': { | |
| 'artifact': 'text/plain' | |
| } | |
| } | |
| def _loadTest(self, json_data, upload): | |
| return upload_test_result_artifacts.upload_artifacts( | |
| json_data, '/tmp', upload, 'test-bucket') | |
| def loadTestEndToEndSimple(self): | |
| test_data = self.makeTestJson(1, 10) | |
| print self._loadTest(test_data, False) | |
| def loadTestEndToEndManySmall(self): | |
| test_data = self.makeTestJson(1000, 10) | |
| self._loadTest(test_data, False) | |
| def loadTestEndToEndSomeBig(self): | |
| test_data = self.makeTestJson(100, 10000000) | |
| self._loadTest(test_data, False) | |
| def loadTestEndToEndVeryBig(self): | |
| test_data = self.makeTestJson(2, 1000000000) | |
| self._loadTest(test_data, False) | |
| ### End load test section. | |
| def testGetTestsSimple(self): | |
| self.assertEqual(upload_test_result_artifacts.get_tests({ | |
| 'foo': { | |
| 'expected': 'PASS', | |
| 'actual': 'PASS', | |
| }, | |
| }), { | |
| ('foo',): { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| } | |
| }) | |
| def testGetTestsNested(self): | |
| self.assertEqual(upload_test_result_artifacts.get_tests({ | |
| 'foo': { | |
| 'bar': { | |
| 'baz': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| }, | |
| 'bam': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| }, | |
| }, | |
| }, | |
| }), { | |
| ('foo', 'bar', 'baz'): { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| }, | |
| ('foo', 'bar', 'bam'): { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| } | |
| }) | |
| def testGetTestsError(self): | |
| with self.assertRaises(ValueError): | |
| upload_test_result_artifacts.get_tests([]) | |
| def testUploadArtifactsMissingType(self): | |
| """Tests that the type information is used for validation.""" | |
| data = { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain' | |
| }, | |
| 'tests': { | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'screenshot': 'foo.png', | |
| } | |
| } | |
| } | |
| } | |
| with self.assertRaises(ValueError): | |
| upload_test_result_artifacts.upload_artifacts( | |
| data, '/tmp', True, 'test-bucket') | |
| @mock.patch('upload_test_result_artifacts.get_file_digest') | |
| @mock.patch('upload_test_result_artifacts.tempfile.mkdtemp') | |
| @mock.patch('upload_test_result_artifacts.shutil.rmtree') | |
| @mock.patch('upload_test_result_artifacts.shutil.copyfile') | |
| def testUploadArtifactsNoUpload( | |
| self, copy_patch, rmtree_patch, mkd_patch, digest_patch): | |
| """Simple test; no artifacts, so data shouldn't change.""" | |
| mkd_patch.return_value = 'foo_dir' | |
| data = { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain' | |
| }, | |
| 'tests': { | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| } | |
| } | |
| } | |
| self.assertEqual(upload_test_result_artifacts.upload_artifacts( | |
| data, '/tmp', True, 'test-bucket'), data) | |
| mkd_patch.assert_called_once_with(prefix='upload_test_artifacts') | |
| digest_patch.assert_not_called() | |
| copy_patch.assert_not_called() | |
| rmtree_patch.assert_called_once_with('foo_dir') | |
| @mock.patch('upload_test_result_artifacts.get_file_digest') | |
| @mock.patch('upload_test_result_artifacts.tempfile.mkdtemp') | |
| @mock.patch('upload_test_result_artifacts.shutil.rmtree') | |
| @mock.patch('upload_test_result_artifacts.shutil.copyfile') | |
| @mock.patch('upload_test_result_artifacts.os.path.exists') | |
| def testUploadArtifactsBasic( | |
| self, exists_patch, copy_patch, rmtree_patch, mkd_patch, digest_patch): | |
| """Upload a single artifact.""" | |
| mkd_patch.return_value = 'foo_dir' | |
| exists_patch.return_value = False | |
| digest_patch.return_value = 'deadbeef' | |
| data = { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain' | |
| }, | |
| 'tests': { | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'foo.txt', | |
| } | |
| } | |
| } | |
| } | |
| self.assertEqual(upload_test_result_artifacts.upload_artifacts( | |
| data, '/tmp', True, 'test-bucket'), { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain' | |
| }, | |
| 'tests': { | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'deadbeef', | |
| } | |
| } | |
| }, | |
| 'artifact_permanent_location': 'gs://chromium-test-artifacts/sha1', | |
| }) | |
| mkd_patch.assert_called_once_with(prefix='upload_test_artifacts') | |
| digest_patch.assert_called_once_with('/tmp/foo.txt') | |
| copy_patch.assert_called_once_with('/tmp/foo.txt', 'foo_dir/deadbeef') | |
| rmtree_patch.assert_called_once_with('foo_dir') | |
| @mock.patch('upload_test_result_artifacts.get_file_digest') | |
| @mock.patch('upload_test_result_artifacts.tempfile.mkdtemp') | |
| @mock.patch('upload_test_result_artifacts.shutil.rmtree') | |
| @mock.patch('upload_test_result_artifacts.shutil.copyfile') | |
| @mock.patch('upload_test_result_artifacts.os.path.exists') | |
| def testUploadArtifactsComplex( | |
| self, exists_patch, copy_patch, rmtree_patch, mkd_patch, digest_patch): | |
| """Upload multiple artifacts.""" | |
| mkd_patch.return_value = 'foo_dir' | |
| exists_patch.return_value = False | |
| digest_patch.side_effect = [ | |
| 'deadbeef1', 'deadbeef2', 'deadbeef3', 'deadbeef4'] | |
| data = { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain', | |
| 'screenshot': 'image/png', | |
| }, | |
| 'tests': { | |
| 'bar': { | |
| 'baz': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'baz.log.txt', | |
| 'screenshot': 'baz.png', | |
| } | |
| } | |
| }, | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'foo.log.txt', | |
| 'screenshot': 'foo.png', | |
| } | |
| }, | |
| } | |
| } | |
| self.assertEqual(upload_test_result_artifacts.upload_artifacts( | |
| data, '/tmp', True, 'test-bucket'), { | |
| 'artifact_type_info': { | |
| 'log': 'text/plain', | |
| 'screenshot': 'image/png', | |
| }, | |
| 'tests': { | |
| 'bar': { | |
| 'baz': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'deadbeef1', | |
| 'screenshot': 'deadbeef2', | |
| } | |
| } | |
| }, | |
| 'foo': { | |
| 'actual': 'PASS', | |
| 'expected': 'PASS', | |
| 'artifacts': { | |
| 'log': 'deadbeef3', | |
| 'screenshot': 'deadbeef4', | |
| } | |
| }, | |
| }, | |
| 'artifact_permanent_location': 'gs://chromium-test-artifacts/sha1', | |
| }) | |
| mkd_patch.assert_called_once_with(prefix='upload_test_artifacts') | |
| digest_patch.assert_has_calls([ | |
| mock.call('/tmp/baz.log.txt'), mock.call('/tmp/baz.png'), | |
| mock.call('/tmp/foo.log.txt'), mock.call('/tmp/foo.png')]) | |
| copy_patch.assert_has_calls([ | |
| mock.call('/tmp/baz.log.txt', 'foo_dir/deadbeef1'), | |
| mock.call('/tmp/baz.png', 'foo_dir/deadbeef2'), | |
| mock.call('/tmp/foo.log.txt', 'foo_dir/deadbeef3'), | |
| mock.call('/tmp/foo.png', 'foo_dir/deadbeef4'), | |
| ]) | |
| rmtree_patch.assert_called_once_with('foo_dir') | |
| def testFileDigest(self): | |
| _, path = tempfile.mkstemp(prefix='file_digest_test') | |
| with open(path, 'w') as f: | |
| f.write('a') | |
| self.assertEqual( | |
| upload_test_result_artifacts.get_file_digest(path), | |
| '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8') | |
| if __name__ == '__main__': | |
| unittest.main() |