BuildAPI: Add unit tests and mocks for Artifacts service.
Also added unit tests for FetchPinnedGuestImages.
BUG=chromium:1000845
TEST=run_tests
Change-Id: Ib8d44b03c0ac849055fb8330fef72b2737efce2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1937729
Reviewed-by: Alex Klein <saklein@chromium.org>
Tested-by: Michael Mortensen <mmortensen@google.com>
Commit-Queue: Nicolas Boichat <drinkcat@chromium.org>
diff --git a/api/controller/artifacts.py b/api/controller/artifacts.py
index 11d88d9..4a69f6f 100644
--- a/api/controller/artifacts.py
+++ b/api/controller/artifacts.py
@@ -45,7 +45,16 @@
return image_dir
-@faux.all_empty
+def _BundleImageArchivesResponse(input_proto, output_proto, _config):
+ """Add artifact paths to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'path0.tar.xz')
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'path1.tar.xz')
+
+
+@faux.success(_BundleImageArchivesResponse)
+@faux.empty_error
@validate.require('build_target.name')
@validate.exists('output_dir')
@validate.validation_complete
@@ -63,7 +72,14 @@
output_proto.artifacts.add().path = os.path.join(output_dir, archive)
-@faux.all_empty
+def _BundleImageZipResponse(input_proto, output_proto, _config):
+ """Add artifact zip files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'image.zip')
+
+
+@faux.success(_BundleImageZipResponse)
+@faux.empty_error
@validate.require('build_target.name', 'output_dir')
@validate.exists('output_dir')
@validate.validation_complete
@@ -85,7 +101,14 @@
output_proto.artifacts.add().path = os.path.join(output_dir, archive)
-@faux.all_empty
+def _BundleTestUpdatePayloadsResponse(input_proto, output_proto, _config):
+ """Add test payload files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'payload1.bin')
+
+
+@faux.success(_BundleTestUpdatePayloadsResponse)
+@faux.empty_error
@validate.require('build_target.name', 'output_dir')
@validate.exists('output_dir')
@validate.validation_complete
@@ -123,7 +146,14 @@
output_proto.artifacts.add().path = payload
-@faux.all_empty
+def _BundleAutotestFilesResponse(input_proto, output_proto, _config):
+ """Add test autotest files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'autotest-a.tar.gz')
+
+
+@faux.success(_BundleAutotestFilesResponse)
+@faux.empty_error
@validate.require('output_dir')
@validate.exists('output_dir')
def BundleAutotestFiles(input_proto, output_proto, config):
@@ -166,7 +196,14 @@
output_proto.artifacts.add().path = archive
-@faux.all_empty
+def _BundleTastFilesResponse(input_proto, output_proto, _config):
+ """Add test tast files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(input_proto.output_dir,
+ 'tast_bundles.tar.gz')
+
+
+@faux.success(_BundleTastFilesResponse)
+@faux.empty_error
@validate.require('output_dir')
@validate.exists('output_dir')
def BundleTastFiles(input_proto, output_proto, config):
@@ -214,7 +251,14 @@
output_proto.artifacts.add().path = archive
-@faux.all_empty
+def _BundlePinnedGuestImagesResponse(input_proto, output_proto, _config):
+ """Add test pinned guest image files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'pinned-guest-images.tar.gz')
+
+
+@faux.success(_BundlePinnedGuestImagesResponse)
+@faux.empty_error
@validate.require('build_target.name', 'output_dir')
@validate.exists('output_dir')
@validate.validation_complete
@@ -241,7 +285,15 @@
output_proto.artifacts.add().path = os.path.join(output_dir, archive)
-@faux.all_empty
+def _FetchPinnedGuestImagesResponse(_input_proto, output_proto, _config):
+ """Add test fetched pinned guest image files to a successful response."""
+ pinned_image = output_proto.pinned_images.add()
+ pinned_image.filename = 'pinned_file.tar.gz'
+ pinned_image.uri = 'https://testuri.com'
+
+
+@faux.success(_FetchPinnedGuestImagesResponse)
+@faux.empty_error
@validate.require('sysroot.path')
@validate.validation_complete
def FetchPinnedGuestImages(input_proto, output_proto, _config):
@@ -265,7 +317,14 @@
pinned_image.uri = pin.uri
-@faux.all_empty
+def _BundleFirmwareResponse(input_proto, output_proto, _config):
+ """Add test firmware image files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'firmware.tar.gz')
+
+
+@faux.success(_BundleFirmwareResponse)
+@faux.empty_error
@validate.require('output_dir', 'sysroot.path')
@validate.exists('output_dir')
@validate.validation_complete
@@ -298,7 +357,14 @@
output_proto.artifacts.add().path = archive
-@faux.all_empty
+def _BundleEbuildLogsResponse(input_proto, output_proto, _config):
+ """Add test log files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'ebuild-logs.tar.gz')
+
+
+@faux.success(_BundleEbuildLogsResponse)
+@faux.empty_error
@validate.exists('output_dir')
def BundleEbuildLogs(input_proto, output_proto, config):
"""Tar the ebuild logs for a build target.
@@ -334,7 +400,14 @@
output_proto.artifacts.add().path = os.path.join(output_dir, archive)
-@faux.all_empty
+def _BundleChromeOSConfigResponse(input_proto, output_proto, _config):
+ """Add test config files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'config.yaml')
+
+
+@faux.success(_BundleChromeOSConfigResponse)
+@faux.empty_error
@validate.exists('output_dir')
@validate.validation_complete
def BundleChromeOSConfig(input_proto, output_proto, _config):
@@ -366,7 +439,14 @@
output_proto.artifacts.add().path = os.path.join(output_dir, chromeos_config)
-@faux.all_empty
+def _BundleSimpleChromeArtifactsResponse(input_proto, output_proto, _config):
+ """Add test simple chrome files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'simple_chrome.txt')
+
+
+@faux.success(_BundleSimpleChromeArtifactsResponse)
+@faux.empty_error
@validate.require('output_dir', 'sysroot.build_target.name', 'sysroot.path')
@validate.exists('output_dir')
@validate.validation_complete
@@ -403,7 +483,14 @@
output_proto.artifacts.add().path = file_name
-@faux.all_empty
+def _BundleVmFilesResponse(input_proto, output_proto, _config):
+ """Add test vm files to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'f1.tar')
+
+
+@faux.success(_BundleVmFilesResponse)
+@faux.empty_error
@validate.require('chroot.path', 'test_results_dir', 'output_dir')
@validate.exists('output_dir')
@validate.validation_complete
@@ -424,17 +511,23 @@
for archive in archives:
output_proto.artifacts.add().path = archive
+def _BundleAFDOGenerationArtifactsResponse(input_proto, output_proto, _config):
+ """Add test tarball AFDO file to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'artifact1')
+
_VALID_ARTIFACT_TYPES = [toolchain_pb2.BENCHMARK_AFDO,
toolchain_pb2.ORDERFILE]
-@faux.all_empty
+@faux.success(_BundleAFDOGenerationArtifactsResponse)
+@faux.empty_error
@validate.require('build_target.name', 'output_dir')
@validate.is_in('artifact_type', _VALID_ARTIFACT_TYPES)
@validate.exists('output_dir')
@validate.exists('chroot.chrome_dir')
@validate.validation_complete
def BundleAFDOGenerationArtifacts(input_proto, output_proto, _config):
- """Generic function for creating tarballs of both AFDO and orerfile.
+ """Generic function for creating tarballs of both AFDO and orderfile.
Args:
input_proto (BundleChromeAFDORequest): The input proto.
@@ -465,7 +558,16 @@
output_proto.artifacts.add().path = file_name
-@faux.all_empty
+def _ExportCpeReportResponse(input_proto, output_proto, _config):
+ """Add test cpe results to a successful response."""
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'cpe_report.txt')
+ output_proto.artifacts.add().path = os.path.join(
+ input_proto.output_dir, 'cpe_warnings.txt')
+
+
+@faux.success(_ExportCpeReportResponse)
+@faux.empty_error
@validate.exists('output_dir')
def ExportCpeReport(input_proto, output_proto, config):
"""Export a CPE report.
diff --git a/api/controller/artifacts_unittest.py b/api/controller/artifacts_unittest.py
index d11a595..3dc2271 100644
--- a/api/controller/artifacts_unittest.py
+++ b/api/controller/artifacts_unittest.py
@@ -7,6 +7,7 @@
from __future__ import print_function
+import collections
import os
import mock
@@ -25,6 +26,10 @@
from chromite.service import artifacts as artifacts_svc
+PinnedGuestImage = collections.namedtuple('PinnedGuestImage',
+ ['filename', 'uri'])
+
+
class BundleRequestMixin(object):
"""Mixin to provide bundle request methods."""
@@ -106,6 +111,18 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'ArchiveImages')
+ artifacts.BundleImageArchives(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 2)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'path0.tar.xz'))
+ self.assertEqual(self.response.artifacts[1].path,
+ os.path.join(self.output_dir, 'path1.tar.xz'))
+
def testNoBuildTarget(self):
"""Test that no build target fails."""
request = self.BuildTargetRequest(output_dir=self.tempdir)
@@ -147,6 +164,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(commands, 'BuildImageZip')
+ artifacts.BundleImageZip(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'image.zip'))
+
def testBundleImageZip(self):
"""BundleImageZip calls cbuildbot/commands with correct args."""
bundle_image_zip = self.PatchObject(
@@ -181,6 +208,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleAutotestFiles')
+ artifacts.BundleAutotestFiles(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'autotest-a.tar.gz'))
+
def testBundleAutotestFilesLegacy(self):
"""BundleAutotestFiles calls service correctly with legacy args."""
files = {
@@ -257,6 +294,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleTastFiles')
+ artifacts.BundleTastFiles(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'tast_bundles.tar.gz'))
+
def testBundleTastFilesNoLogs(self):
"""BundleTasteFiles dies when no tast files found."""
self.PatchObject(commands, 'BuildTastBundleTarball',
@@ -322,6 +369,17 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(commands, 'BuildPinnedGuestImagesTarball')
+ artifacts.BundlePinnedGuestImages(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir,
+ 'pinned-guest-images.tar.gz'))
+
def testBundlePinnedGuestImages(self):
"""BundlePinnedGuestImages calls cbuildbot/commands with correct args."""
build_pinned_guest_images_tarball = self.PatchObject(
@@ -345,6 +403,59 @@
self.assertFalse(self.response.artifacts)
+class FetchPinnedGuestImagesTest(cros_test_lib.MockTempDirTestCase,
+ api_config.ApiConfigMixin, BundleRequestMixin):
+ """Unittests for FetchPinnedGuestImages."""
+
+ def setUp(self):
+ self.build_target = 'board'
+ self.chroot_dir = os.path.join(self.tempdir, 'chroot_dir')
+ self.sysroot_path = '/sysroot'
+ self.sysroot_dir = os.path.join(self.chroot_dir, 'sysroot')
+ osutils.SafeMakedirs(self.sysroot_dir)
+
+ self.input_request = artifacts_pb2.PinnedGuestImageUriRequest(
+ sysroot={'path': self.sysroot_path,
+ 'build_target': {'name': self.build_target}},
+ chroot={'path': self.chroot_dir})
+
+ self.response = artifacts_pb2.PinnedGuestImageUriResponse()
+
+ def testValidateOnly(self):
+ """Sanity check that a validate only call does not execute any logic."""
+ patch = self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages')
+ artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+ self.validate_only_config)
+ patch.assert_not_called()
+
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages')
+ artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.pinned_images), 1)
+ self.assertEqual(self.response.pinned_images[0].filename,
+ 'pinned_file.tar.gz')
+ self.assertEqual(self.response.pinned_images[0].uri,
+ 'https://testuri.com')
+
+ def testFetchPinnedGuestImages(self):
+ """FetchPinnedGuestImages calls service with correct args."""
+ pins = []
+ pins.append(PinnedGuestImage(
+ filename='my_pinned_file.tar.gz', uri='https://the_testuri.com'))
+ self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages',
+ return_value=pins)
+ artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+ self.api_config)
+ self.assertEqual(len(self.response.pinned_images), 1)
+ self.assertEqual(self.response.pinned_images[0].filename,
+ 'my_pinned_file.tar.gz')
+ self.assertEqual(self.response.pinned_images[0].uri,
+ 'https://the_testuri.com')
+
+
class BundleFirmwareTest(BundleTestCase):
"""Unittests for BundleFirmware."""
@@ -355,6 +466,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleTastFiles')
+ artifacts.BundleFirmware(self.sysroot_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'firmware.tar.gz'))
+
def testBundleFirmware(self):
"""BundleFirmware calls cbuildbot/commands with correct args."""
self.PatchObject(
@@ -386,6 +507,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(commands, 'BuildEbuildLogsTarball')
+ artifacts.BundleEbuildLogs(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'ebuild-logs.tar.gz'))
+
def testBundleEbuildLogs(self):
"""BundleEbuildLogs calls cbuildbot/commands with correct args."""
bundle_ebuild_logs_tarball = self.PatchObject(
@@ -430,6 +561,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleChromeOSConfig')
+ artifacts.BundleChromeOSConfig(self.target_request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'config.yaml'))
+
def testBundleChromeOSConfigCallWithSysroot(self):
"""Call with a request that sets sysroot."""
bundle_chromeos_config = self.PatchObject(
@@ -504,6 +645,16 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleTestUpdatePayloads')
+ artifacts.BundleTestUpdatePayloads(self.input_proto, self.output_proto,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.output_proto.artifacts), 1)
+ self.assertEqual(self.output_proto.artifacts[0].path,
+ os.path.join(self.archive_root, 'payload1.bin'))
+
def testBundleTestUpdatePayloads(self):
"""BundleTestUpdatePayloads calls cbuildbot/commands with correct args."""
image_path = os.path.join(self.image_root, constants.BASE_IMAGE_BIN)
@@ -581,6 +732,19 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleSimpleChromeArtifacts')
+ request = self._GetRequest(chroot=self.chroot_dir,
+ sysroot=self.sysroot_path,
+ build_target='board', output_dir=self.output_dir)
+ artifacts.BundleSimpleChromeArtifacts(request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'simple_chrome.txt'))
+
def testNoBuildTarget(self):
"""Test no build target fails."""
request = self._GetRequest(chroot=self.chroot_dir,
@@ -677,6 +841,18 @@
artifacts.BundleVmFiles(in_proto, self.response, self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'BundleVmFiles')
+ in_proto = self._GetInput(chroot='/chroot/dir', sysroot='/build/board',
+ test_results_dir='/test/results',
+ output_dir=self.output_dir)
+ artifacts.BundleVmFiles(in_proto, self.response, self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'f1.tar'))
+
def testChrootMissing(self):
"""Test error handling for missing chroot."""
in_proto = self._GetInput(sysroot='/build/board',
@@ -793,6 +969,22 @@
self.validate_only_config)
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc,
+ 'BundleAFDOGenerationArtifacts')
+ request = self._GetRequest(chroot=self.chroot_dir,
+ build_target=self.build_target,
+ chrome_root=self.chrome_root,
+ output_dir=self.output_dir,
+ artifact_type=self.valid_artifact_type)
+ artifacts.BundleAFDOGenerationArtifacts(request, self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 1)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.output_dir, 'artifact1'))
+
def testNoBuildTarget(self):
"""Test no build target fails."""
request = self._GetRequest(chroot=self.chroot_dir,
@@ -914,6 +1106,23 @@
patch.assert_not_called()
+ def testMockCall(self):
+ """Test that a mock call does not execute logic, returns mocked value."""
+ patch = self.PatchObject(artifacts_svc, 'GenerateCpeReport')
+
+ request = artifacts_pb2.BundleRequest()
+ request.build_target.name = 'board'
+ request.output_dir = self.tempdir
+
+ artifacts.ExportCpeReport(request, self.response, self.mock_call_config)
+
+ patch.assert_not_called()
+ self.assertEqual(len(self.response.artifacts), 2)
+ self.assertEqual(self.response.artifacts[0].path,
+ os.path.join(self.tempdir, 'cpe_report.txt'))
+ self.assertEqual(self.response.artifacts[1].path,
+ os.path.join(self.tempdir, 'cpe_warnings.txt'))
+
def testNoBuildTarget(self):
request = artifacts_pb2.BundleRequest()
request.output_dir = self.tempdir