|  | # 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() |