blob: 082a799d384e691a06f71ab9b4d5cb84d567e782 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Evan Hernandezf388cbf2019-04-01 11:15:23 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unittests for Artifacts operations."""
6
Evan Hernandezf388cbf2019-04-01 11:15:23 -06007import os
Greg Edelstondc941072021-08-11 12:32:30 -06008import pathlib
Varun Somani04dccd72021-10-09 01:06:11 +00009from typing import Optional
Mike Frysinger166fea02021-02-12 05:30:33 -050010from unittest import mock
Evan Hernandezf388cbf2019-04-01 11:15:23 -060011
Alex Klein231d2da2019-07-22 16:44:45 -060012from chromite.api import api_config
Evan Hernandezf388cbf2019-04-01 11:15:23 -060013from chromite.api.controller import artifacts
Greg Edelstondc941072021-08-11 12:32:30 -060014from chromite.api.controller import controller_util
Jack Neus26b94672022-10-27 17:33:21 +000015from chromite.api.controller import image as image_controller
16from chromite.api.controller import sysroot as sysroot_controller
17from chromite.api.controller import test as test_controller
Evan Hernandezf388cbf2019-04-01 11:15:23 -060018from chromite.api.gen.chromite.api import artifacts_pb2
Jack Neus26b94672022-10-27 17:33:21 +000019from chromite.api.gen.chromite.api import sysroot_pb2
Greg Edelstondc941072021-08-11 12:32:30 -060020from chromite.api.gen.chromiumos import common_pb2
Evan Hernandezf388cbf2019-04-01 11:15:23 -060021from chromite.cbuildbot import commands
Alex Kleinb9d810b2019-07-01 12:38:02 -060022from chromite.lib import chroot_lib
Evan Hernandezf388cbf2019-04-01 11:15:23 -060023from chromite.lib import constants
24from chromite.lib import cros_build_lib
25from chromite.lib import cros_test_lib
26from chromite.lib import osutils
Alex Klein238d8862019-05-07 11:32:46 -060027from chromite.lib import sysroot_lib
Alex Klein2275d692019-04-23 16:04:12 -060028from chromite.service import artifacts as artifacts_svc
Evan Hernandezf388cbf2019-04-01 11:15:23 -060029
30
Alex Kleind91e95a2019-09-17 10:39:02 -060031class BundleRequestMixin(object):
Alex Klein1699fab2022-09-08 08:46:06 -060032 """Mixin to provide bundle request methods."""
Alex Kleind91e95a2019-09-17 10:39:02 -060033
Alex Klein1699fab2022-09-08 08:46:06 -060034 def EmptyRequest(self):
35 return artifacts_pb2.BundleRequest()
Alex Kleind91e95a2019-09-17 10:39:02 -060036
Alex Klein1699fab2022-09-08 08:46:06 -060037 def BuildTargetRequest(
38 self, build_target=None, output_dir=None, chroot=None
39 ):
40 """Get a build target format request instance."""
41 request = self.EmptyRequest()
42 if build_target:
43 request.build_target.name = build_target
44 if output_dir:
45 request.output_dir = output_dir
46 if chroot:
47 request.chroot.path = chroot
Alex Kleind91e95a2019-09-17 10:39:02 -060048
Alex Klein1699fab2022-09-08 08:46:06 -060049 return request
Alex Kleind91e95a2019-09-17 10:39:02 -060050
Alex Klein1699fab2022-09-08 08:46:06 -060051 def SysrootRequest(
Brian Norris09937012023-03-31 15:16:55 -070052 self,
53 sysroot=None,
54 build_target=None,
55 output_dir=None,
56 chroot=None,
57 chroot_out=None,
Alex Klein1699fab2022-09-08 08:46:06 -060058 ):
59 """Get a sysroot format request instance."""
60 request = self.EmptyRequest()
61 if sysroot:
62 request.sysroot.path = sysroot
63 if build_target:
64 request.sysroot.build_target.name = build_target
65 if output_dir:
66 request.output_dir = output_dir
67 if chroot:
Brian Norris09937012023-03-31 15:16:55 -070068 request.chroot.path = str(chroot)
69 if chroot_out:
70 request.chroot.out_path = str(chroot_out)
Alex Kleind91e95a2019-09-17 10:39:02 -060071
Alex Klein1699fab2022-09-08 08:46:06 -060072 return request
Alex Kleind91e95a2019-09-17 10:39:02 -060073
74
Alex Klein1699fab2022-09-08 08:46:06 -060075class BundleTestCase(
76 cros_test_lib.MockTempDirTestCase,
77 api_config.ApiConfigMixin,
78 BundleRequestMixin,
79):
80 """Basic setup for all artifacts unittests."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -060081
Alex Klein1699fab2022-09-08 08:46:06 -060082 def setUp(self):
83 self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=False)
84 self.output_dir = os.path.join(self.tempdir, "artifacts")
85 osutils.SafeMakedirs(self.output_dir)
86 self.sysroot_path = "/build/target"
87 self.sysroot = sysroot_lib.Sysroot(self.sysroot_path)
Brian Norris09937012023-03-31 15:16:55 -070088 self.chroot_path = self.tempdir / "chroot"
89 self.chroot_out_path = self.tempdir / "out"
Alex Klein1699fab2022-09-08 08:46:06 -060090 full_sysroot_path = os.path.join(
91 self.chroot_path, self.sysroot_path.lstrip(os.sep)
92 )
93 osutils.SafeMakedirs(full_sysroot_path)
Alex Klein231d2da2019-07-22 16:44:45 -060094
Alex Klein1699fab2022-09-08 08:46:06 -060095 # All requests use same response type.
96 self.response = artifacts_pb2.BundleResponse()
Alex Klein231d2da2019-07-22 16:44:45 -060097
Alex Klein1699fab2022-09-08 08:46:06 -060098 # Build target request.
99 self.target_request = self.BuildTargetRequest(
100 build_target="target",
101 output_dir=self.output_dir,
Brian Norris09937012023-03-31 15:16:55 -0700102 chroot=str(self.chroot_path),
Alex Klein1699fab2022-09-08 08:46:06 -0600103 )
Alex Klein68c8fdf2019-09-25 15:09:11 -0600104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 # Sysroot request.
106 self.sysroot_request = self.SysrootRequest(
107 sysroot=self.sysroot_path,
108 build_target="target",
109 output_dir=self.output_dir,
110 chroot=self.chroot_path,
Brian Norris09937012023-03-31 15:16:55 -0700111 chroot_out=self.chroot_out_path,
Alex Klein1699fab2022-09-08 08:46:06 -0600112 )
Alex Klein68c8fdf2019-09-25 15:09:11 -0600113
Alex Klein1699fab2022-09-08 08:46:06 -0600114 self.source_root = self.tempdir
115 self.PatchObject(constants, "SOURCE_ROOT", new=self.tempdir)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600116
117
Alex Kleind91e95a2019-09-17 10:39:02 -0600118class BundleImageArchivesTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600119 """BundleImageArchives tests."""
Alex Kleind91e95a2019-09-17 10:39:02 -0600120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 def testValidateOnly(self):
122 """Quick check that a validate only call does not execute any logic."""
123 patch = self.PatchObject(artifacts_svc, "ArchiveImages")
124 artifacts.BundleImageArchives(
125 self.target_request, self.response, self.validate_only_config
126 )
127 patch.assert_not_called()
Alex Kleind91e95a2019-09-17 10:39:02 -0600128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700130 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600131 patch = self.PatchObject(artifacts_svc, "ArchiveImages")
132 artifacts.BundleImageArchives(
133 self.target_request, self.response, self.mock_call_config
134 )
135 patch.assert_not_called()
136 self.assertEqual(len(self.response.artifacts), 2)
137 self.assertEqual(
138 self.response.artifacts[0].path,
139 os.path.join(self.output_dir, "path0.tar.xz"),
140 )
141 self.assertEqual(
142 self.response.artifacts[1].path,
143 os.path.join(self.output_dir, "path1.tar.xz"),
144 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700145
Alex Klein1699fab2022-09-08 08:46:06 -0600146 def testNoBuildTarget(self):
147 """Test that no build target fails."""
148 request = self.BuildTargetRequest(output_dir=str(self.tempdir))
149 with self.assertRaises(cros_build_lib.DieSystemExit):
150 artifacts.BundleImageArchives(
151 request, self.response, self.api_config
152 )
Alex Kleind91e95a2019-09-17 10:39:02 -0600153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 def testNoOutputDir(self):
155 """Test no output dir fails."""
156 request = self.BuildTargetRequest(build_target="board")
157 with self.assertRaises(cros_build_lib.DieSystemExit):
158 artifacts.BundleImageArchives(
159 request, self.response, self.api_config
160 )
Alex Kleind91e95a2019-09-17 10:39:02 -0600161
Alex Klein1699fab2022-09-08 08:46:06 -0600162 def testInvalidOutputDir(self):
163 """Test invalid output dir fails."""
164 request = self.BuildTargetRequest(
165 build_target="board", output_dir=os.path.join(self.tempdir, "DNE")
166 )
167 with self.assertRaises(cros_build_lib.DieSystemExit):
168 artifacts.BundleImageArchives(
169 request, self.response, self.api_config
170 )
Alex Kleind91e95a2019-09-17 10:39:02 -0600171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 def testOutputHandling(self):
173 """Test the artifact output handling."""
174 expected = [os.path.join(self.output_dir, f) for f in ("a", "b", "c")]
175 self.PatchObject(artifacts_svc, "ArchiveImages", return_value=expected)
176 self.PatchObject(os.path, "exists", return_value=True)
Alex Kleind91e95a2019-09-17 10:39:02 -0600177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 artifacts.BundleImageArchives(
179 self.target_request, self.response, self.api_config
180 )
Alex Kleind91e95a2019-09-17 10:39:02 -0600181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 self.assertCountEqual(
183 expected, [a.path for a in self.response.artifacts]
184 )
Alex Kleind91e95a2019-09-17 10:39:02 -0600185
186
Evan Hernandez9f125ac2019-04-08 17:18:47 -0600187class BundleImageZipTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600188 """Unittests for BundleImageZip."""
Evan Hernandez9f125ac2019-04-08 17:18:47 -0600189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 def testValidateOnly(self):
191 """Quick check that a validate only call does not execute any logic."""
192 patch = self.PatchObject(commands, "BuildImageZip")
193 artifacts.BundleImageZip(
194 self.target_request, self.response, self.validate_only_config
195 )
196 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600197
Alex Klein1699fab2022-09-08 08:46:06 -0600198 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700199 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600200 patch = self.PatchObject(commands, "BuildImageZip")
201 artifacts.BundleImageZip(
202 self.target_request, self.response, self.mock_call_config
203 )
204 patch.assert_not_called()
205 self.assertEqual(len(self.response.artifacts), 1)
206 self.assertEqual(
207 self.response.artifacts[0].path,
208 os.path.join(self.output_dir, "image.zip"),
209 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700210
Alex Klein1699fab2022-09-08 08:46:06 -0600211 def testBundleImageZip(self):
212 """BundleImageZip calls cbuildbot/commands with correct args."""
213 bundle_image_zip = self.PatchObject(
214 artifacts_svc, "BundleImageZip", return_value="image.zip"
215 )
216 self.PatchObject(os.path, "exists", return_value=True)
217 artifacts.BundleImageZip(
218 self.target_request, self.response, self.api_config
219 )
220 self.assertEqual(
221 [artifact.path for artifact in self.response.artifacts],
222 [os.path.join(self.output_dir, "image.zip")],
223 )
Alex Klein231d2da2019-07-22 16:44:45 -0600224
Alex Klein1699fab2022-09-08 08:46:06 -0600225 latest = os.path.join(
226 self.source_root, "src/build/images/target/latest"
227 )
228 self.assertEqual(
229 bundle_image_zip.call_args_list,
230 [mock.call(self.output_dir, latest)],
231 )
Evan Hernandez9f125ac2019-04-08 17:18:47 -0600232
Alex Klein1699fab2022-09-08 08:46:06 -0600233 def testBundleImageZipNoImageDir(self):
234 """BundleImageZip dies when image dir does not exist."""
235 self.PatchObject(os.path, "exists", return_value=False)
236 with self.assertRaises(cros_build_lib.DieSystemExit):
237 artifacts.BundleImageZip(
238 self.target_request, self.response, self.api_config
239 )
Evan Hernandez9f125ac2019-04-08 17:18:47 -0600240
241
Alex Klein68c8fdf2019-09-25 15:09:11 -0600242class BundleAutotestFilesTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600243 """Unittests for BundleAutotestFiles."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600244
Alex Klein1699fab2022-09-08 08:46:06 -0600245 def testValidateOnly(self):
246 """Quick check that a validate only call does not execute any logic."""
247 patch = self.PatchObject(artifacts_svc, "BundleAutotestFiles")
248 artifacts.BundleAutotestFiles(
249 self.sysroot_request, self.response, self.validate_only_config
250 )
251 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700254 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600255 patch = self.PatchObject(artifacts_svc, "BundleAutotestFiles")
256 artifacts.BundleAutotestFiles(
257 self.sysroot_request, self.response, self.mock_call_config
258 )
259 patch.assert_not_called()
260 self.assertEqual(len(self.response.artifacts), 1)
261 self.assertEqual(
262 self.response.artifacts[0].path,
263 os.path.join(self.output_dir, "autotest-a.tar.gz"),
264 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 def testBundleAutotestFiles(self):
267 """BundleAutotestFiles calls service correctly."""
Alex Kleinab87ceb2023-01-24 12:00:51 -0700268
Alex Klein1699fab2022-09-08 08:46:06 -0600269 files = {
Alex Kleinab87ceb2023-01-24 12:00:51 -0700270 artifacts_svc.ARCHIVE_CONTROL_FILES: (
271 "/tmp/artifacts/autotest-a.tar.gz"
272 ),
Alex Klein1699fab2022-09-08 08:46:06 -0600273 artifacts_svc.ARCHIVE_PACKAGES: "/tmp/artifacts/autotest-b.tar.gz",
274 }
275 patch = self.PatchObject(
276 artifacts_svc, "BundleAutotestFiles", return_value=files
277 )
Alex Klein238d8862019-05-07 11:32:46 -0600278
Alex Klein1699fab2022-09-08 08:46:06 -0600279 artifacts.BundleAutotestFiles(
280 self.sysroot_request, self.response, self.api_config
281 )
Alex Klein238d8862019-05-07 11:32:46 -0600282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 # Verify the arguments are being passed through.
284 patch.assert_called_with(mock.ANY, self.sysroot, self.output_dir)
Alex Klein238d8862019-05-07 11:32:46 -0600285
Alex Klein1699fab2022-09-08 08:46:06 -0600286 # Verify the output proto is being populated correctly.
287 self.assertTrue(self.response.artifacts)
288 paths = [artifact.path for artifact in self.response.artifacts]
289 self.assertCountEqual(list(files.values()), paths)
Alex Klein238d8862019-05-07 11:32:46 -0600290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 def testInvalidOutputDir(self):
292 """Test invalid output directory argument."""
293 request = self.SysrootRequest(
294 chroot=self.chroot_path, sysroot=self.sysroot_path
295 )
Alex Klein238d8862019-05-07 11:32:46 -0600296
Alex Klein1699fab2022-09-08 08:46:06 -0600297 with self.assertRaises(cros_build_lib.DieSystemExit):
298 artifacts.BundleAutotestFiles(
299 request, self.response, self.api_config
300 )
Alex Klein238d8862019-05-07 11:32:46 -0600301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 def testInvalidSysroot(self):
303 """Test no sysroot directory."""
304 request = self.SysrootRequest(
305 chroot=self.chroot_path, output_dir=self.output_dir
306 )
Alex Klein238d8862019-05-07 11:32:46 -0600307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 with self.assertRaises(cros_build_lib.DieSystemExit):
309 artifacts.BundleAutotestFiles(
310 request, self.response, self.api_config
311 )
Alex Klein238d8862019-05-07 11:32:46 -0600312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 def testSysrootDoesNotExist(self):
314 """Test dies when no sysroot does not exist."""
315 request = self.SysrootRequest(
316 chroot=self.chroot_path,
317 sysroot="/does/not/exist",
318 output_dir=self.output_dir,
319 )
Alex Klein238d8862019-05-07 11:32:46 -0600320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 artifacts.BundleAutotestFiles(request, self.response, self.api_config)
322 self.assertFalse(self.response.artifacts)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600323
324
325class BundleTastFilesTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600326 """Unittests for BundleTastFiles."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600327
Alex Klein1699fab2022-09-08 08:46:06 -0600328 def testValidateOnly(self):
329 """Quick check that a validate only call does not execute any logic."""
330 patch = self.PatchObject(artifacts_svc, "BundleTastFiles")
331 artifacts.BundleTastFiles(
332 self.sysroot_request, self.response, self.validate_only_config
333 )
334 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600335
Alex Klein1699fab2022-09-08 08:46:06 -0600336 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700337 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600338 patch = self.PatchObject(artifacts_svc, "BundleTastFiles")
339 artifacts.BundleTastFiles(
340 self.sysroot_request, self.response, self.mock_call_config
341 )
342 patch.assert_not_called()
343 self.assertEqual(len(self.response.artifacts), 1)
344 self.assertEqual(
345 self.response.artifacts[0].path,
346 os.path.join(self.output_dir, "tast_bundles.tar.gz"),
347 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700348
Alex Klein1699fab2022-09-08 08:46:06 -0600349 def testBundleTastFilesNoLogs(self):
350 """BundleTasteFiles succeeds when no tast files found."""
351 self.PatchObject(commands, "BuildTastBundleTarball", return_value=None)
352 artifacts.BundleTastFiles(
353 self.sysroot_request, self.response, self.api_config
354 )
355 self.assertFalse(self.response.artifacts)
Alex Kleinb9d810b2019-07-01 12:38:02 -0600356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 def testBundleTastFiles(self):
358 """BundleTastFiles calls service correctly."""
Brian Norris09937012023-03-31 15:16:55 -0700359 chroot = chroot_lib.Chroot(
360 self.chroot_path, out_path=self.chroot_out_path
361 )
Alex Kleinb9d810b2019-07-01 12:38:02 -0600362
Alex Klein1699fab2022-09-08 08:46:06 -0600363 expected_archive = os.path.join(
364 self.output_dir, artifacts_svc.TAST_BUNDLE_NAME
365 )
366 # Patch the service being called.
367 bundle_patch = self.PatchObject(
368 artifacts_svc, "BundleTastFiles", return_value=expected_archive
369 )
Alex Kleinb9d810b2019-07-01 12:38:02 -0600370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 artifacts.BundleTastFiles(
372 self.sysroot_request, self.response, self.api_config
373 )
Alex Kleinb9d810b2019-07-01 12:38:02 -0600374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 # Make sure the artifact got recorded successfully.
376 self.assertTrue(self.response.artifacts)
377 self.assertEqual(expected_archive, self.response.artifacts[0].path)
378 # Make sure the service got called correctly.
379 bundle_patch.assert_called_once_with(
380 chroot, self.sysroot, self.output_dir
381 )
Alex Kleinb9d810b2019-07-01 12:38:02 -0600382
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600383
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600384class BundleFirmwareTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600385 """Unittests for BundleFirmware."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 def testValidateOnly(self):
388 """Quick check that a validate only call does not execute any logic."""
389 patch = self.PatchObject(artifacts_svc, "BundleTastFiles")
390 artifacts.BundleFirmware(
391 self.sysroot_request, self.response, self.validate_only_config
392 )
393 patch.assert_not_called()
Michael Mortensen38675192019-06-28 16:52:55 +0000394
Alex Klein1699fab2022-09-08 08:46:06 -0600395 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700396 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600397 patch = self.PatchObject(artifacts_svc, "BundleTastFiles")
398 artifacts.BundleFirmware(
399 self.sysroot_request, self.response, self.mock_call_config
400 )
401 patch.assert_not_called()
402 self.assertEqual(len(self.response.artifacts), 1)
403 self.assertEqual(
404 self.response.artifacts[0].path,
405 os.path.join(self.output_dir, "firmware.tar.gz"),
406 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 def testBundleFirmware(self):
409 """BundleFirmware calls cbuildbot/commands with correct args."""
410 self.PatchObject(
411 artifacts_svc,
412 "BuildFirmwareArchive",
413 return_value=os.path.join(self.output_dir, "firmware.tar.gz"),
414 )
Alex Klein231d2da2019-07-22 16:44:45 -0600415
Alex Klein1699fab2022-09-08 08:46:06 -0600416 artifacts.BundleFirmware(
417 self.sysroot_request, self.response, self.api_config
418 )
419 self.assertEqual(
420 [artifact.path for artifact in self.response.artifacts],
421 [os.path.join(self.output_dir, "firmware.tar.gz")],
422 )
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600423
Alex Klein1699fab2022-09-08 08:46:06 -0600424 def testBundleFirmwareNoLogs(self):
425 """BundleFirmware dies when no firmware found."""
426 self.PatchObject(commands, "BuildFirmwareArchive", return_value=None)
427 artifacts.BundleFirmware(
428 self.sysroot_request, self.response, self.api_config
429 )
430 self.assertEqual(len(self.response.artifacts), 0)
Evan Hernandez9a5d3122019-04-09 10:51:23 -0600431
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600432
Yicheng Liea1181f2020-09-22 11:51:10 -0700433class BundleFpmcuUnittestsTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600434 """Unittests for BundleFpmcuUnittests."""
Yicheng Liea1181f2020-09-22 11:51:10 -0700435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 def testValidateOnly(self):
437 """Quick check that a validate only call does not execute any logic."""
438 patch = self.PatchObject(artifacts_svc, "BundleFpmcuUnittests")
439 artifacts.BundleFpmcuUnittests(
440 self.sysroot_request, self.response, self.validate_only_config
441 )
442 patch.assert_not_called()
Yicheng Liea1181f2020-09-22 11:51:10 -0700443
Alex Klein1699fab2022-09-08 08:46:06 -0600444 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700445 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600446 patch = self.PatchObject(artifacts_svc, "BundleFpmcuUnittests")
447 artifacts.BundleFpmcuUnittests(
448 self.sysroot_request, self.response, self.mock_call_config
449 )
450 patch.assert_not_called()
451 self.assertEqual(len(self.response.artifacts), 1)
452 self.assertEqual(
453 self.response.artifacts[0].path,
454 os.path.join(self.output_dir, "fpmcu_unittests.tar.gz"),
455 )
Yicheng Liea1181f2020-09-22 11:51:10 -0700456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 def testBundleFpmcuUnittests(self):
458 """BundleFpmcuUnittests calls cbuildbot/commands with correct args."""
459 self.PatchObject(
460 artifacts_svc,
461 "BundleFpmcuUnittests",
462 return_value=os.path.join(
463 self.output_dir, "fpmcu_unittests.tar.gz"
464 ),
465 )
466 artifacts.BundleFpmcuUnittests(
467 self.sysroot_request, self.response, self.api_config
468 )
469 self.assertEqual(
470 [artifact.path for artifact in self.response.artifacts],
471 [os.path.join(self.output_dir, "fpmcu_unittests.tar.gz")],
472 )
Yicheng Liea1181f2020-09-22 11:51:10 -0700473
Alex Klein1699fab2022-09-08 08:46:06 -0600474 def testBundleFpmcuUnittestsNoLogs(self):
475 """BundleFpmcuUnittests does not die when no fpmcu unittests found."""
476 self.PatchObject(
477 artifacts_svc, "BundleFpmcuUnittests", return_value=None
478 )
479 artifacts.BundleFpmcuUnittests(
480 self.sysroot_request, self.response, self.api_config
481 )
482 self.assertFalse(self.response.artifacts)
Yicheng Liea1181f2020-09-22 11:51:10 -0700483
484
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600485class BundleEbuildLogsTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600486 """Unittests for BundleEbuildLogs."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600487
Alex Klein1699fab2022-09-08 08:46:06 -0600488 def testValidateOnly(self):
489 """Quick check that a validate only call does not execute any logic."""
490 patch = self.PatchObject(commands, "BuildEbuildLogsTarball")
491 artifacts.BundleEbuildLogs(
492 self.sysroot_request, self.response, self.validate_only_config
493 )
494 patch.assert_not_called()
Michael Mortensen3f382cb2019-07-29 13:21:49 -0600495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700497 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600498 patch = self.PatchObject(commands, "BuildEbuildLogsTarball")
499 artifacts.BundleEbuildLogs(
500 self.sysroot_request, self.response, self.mock_call_config
501 )
502 patch.assert_not_called()
503 self.assertEqual(len(self.response.artifacts), 1)
504 self.assertEqual(
505 self.response.artifacts[0].path,
506 os.path.join(self.output_dir, "ebuild-logs.tar.gz"),
507 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 def testBundleEbuildLogs(self):
510 """BundleEbuildLogs calls cbuildbot/commands with correct args."""
511 bundle_ebuild_logs_tarball = self.PatchObject(
512 artifacts_svc,
513 "BundleEBuildLogsTarball",
514 return_value="ebuild-logs.tar.gz",
515 )
516 artifacts.BundleEbuildLogs(
517 self.sysroot_request, self.response, self.api_config
518 )
519 self.assertEqual(
520 [artifact.path for artifact in self.response.artifacts],
521 [os.path.join(self.output_dir, "ebuild-logs.tar.gz")],
522 )
523 self.assertEqual(
524 bundle_ebuild_logs_tarball.call_args_list,
525 [mock.call(mock.ANY, self.sysroot, self.output_dir)],
526 )
Michael Mortensen3f382cb2019-07-29 13:21:49 -0600527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 def testBundleEbuildLogsNoLogs(self):
529 """BundleEbuildLogs dies when no logs found."""
530 self.PatchObject(commands, "BuildEbuildLogsTarball", return_value=None)
531 artifacts.BundleEbuildLogs(
532 self.sysroot_request, self.response, self.api_config
533 )
Alex Klein036833d2022-06-01 13:05:01 -0600534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 self.assertFalse(self.response.artifacts)
Evan Hernandez9a5d3122019-04-09 10:51:23 -0600536
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600537
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600538class BundleChromeOSConfigTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600539 """Unittests for BundleChromeOSConfig"""
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600540
Alex Klein1699fab2022-09-08 08:46:06 -0600541 def testValidateOnly(self):
542 """Quick check that a validate only call does not execute any logic."""
543 patch = self.PatchObject(artifacts_svc, "BundleChromeOSConfig")
544 artifacts.BundleChromeOSConfig(
545 self.sysroot_request, self.response, self.validate_only_config
546 )
547 patch.assert_not_called()
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700550 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600551 patch = self.PatchObject(artifacts_svc, "BundleChromeOSConfig")
552 artifacts.BundleChromeOSConfig(
553 self.sysroot_request, self.response, self.mock_call_config
554 )
555 patch.assert_not_called()
556 self.assertEqual(len(self.response.artifacts), 1)
557 self.assertEqual(
558 self.response.artifacts[0].path,
559 os.path.join(self.output_dir, "config.yaml"),
560 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700561
Alex Klein1699fab2022-09-08 08:46:06 -0600562 def testBundleChromeOSConfigSuccess(self):
563 """Test standard success case."""
564 bundle_chromeos_config = self.PatchObject(
565 artifacts_svc, "BundleChromeOSConfig", return_value="config.yaml"
566 )
567 artifacts.BundleChromeOSConfig(
568 self.sysroot_request, self.response, self.api_config
569 )
570 self.assertEqual(
571 [artifact.path for artifact in self.response.artifacts],
572 [os.path.join(self.output_dir, "config.yaml")],
573 )
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 self.assertEqual(
576 bundle_chromeos_config.call_args_list,
577 [mock.call(mock.ANY, self.sysroot, self.output_dir)],
578 )
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 def testBundleChromeOSConfigNoConfigFound(self):
581 """Empty results when the config payload isn't found."""
582 self.PatchObject(
583 artifacts_svc, "BundleChromeOSConfig", return_value=None
584 )
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600585
Alex Klein1699fab2022-09-08 08:46:06 -0600586 artifacts.BundleChromeOSConfig(
587 self.sysroot_request, self.response, self.api_config
588 )
589 self.assertFalse(self.response.artifacts)
Andrew Lamb67bd68f2019-08-15 09:09:15 -0600590
591
Alex Klein1699fab2022-09-08 08:46:06 -0600592class BundleTestUpdatePayloadsTest(
593 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
594):
595 """Unittests for BundleTestUpdatePayloads."""
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600596
Alex Klein1699fab2022-09-08 08:46:06 -0600597 def setUp(self):
598 self.source_root = os.path.join(self.tempdir, "cros")
599 osutils.SafeMakedirs(self.source_root)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600600
Alex Klein1699fab2022-09-08 08:46:06 -0600601 self.archive_root = os.path.join(self.tempdir, "output")
602 osutils.SafeMakedirs(self.archive_root)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600603
Alex Klein1699fab2022-09-08 08:46:06 -0600604 self.target = "target"
605 self.image_root = os.path.join(
606 self.source_root, "src/build/images/target/latest"
607 )
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600608
Alex Klein1699fab2022-09-08 08:46:06 -0600609 self.input_proto = artifacts_pb2.BundleRequest()
610 self.input_proto.build_target.name = self.target
611 self.input_proto.output_dir = self.archive_root
612 self.output_proto = artifacts_pb2.BundleResponse()
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600613
Alex Klein1699fab2022-09-08 08:46:06 -0600614 self.PatchObject(constants, "SOURCE_ROOT", new=self.source_root)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600615
Alex Klein1699fab2022-09-08 08:46:06 -0600616 def MockPayloads(image_path, archive_dir):
617 osutils.WriteFile(
618 os.path.join(archive_dir, "payload1.bin"), image_path
619 )
620 osutils.WriteFile(
621 os.path.join(archive_dir, "payload2.bin"), image_path
622 )
623 return [
624 os.path.join(archive_dir, "payload1.bin"),
625 os.path.join(archive_dir, "payload2.bin"),
626 ]
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600627
Alex Klein1699fab2022-09-08 08:46:06 -0600628 self.bundle_patch = self.PatchObject(
629 artifacts_svc, "BundleTestUpdatePayloads", side_effect=MockPayloads
630 )
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600631
Alex Klein1699fab2022-09-08 08:46:06 -0600632 def testValidateOnly(self):
633 """Quick check that a validate only call does not execute any logic."""
634 patch = self.PatchObject(artifacts_svc, "BundleTestUpdatePayloads")
635 artifacts.BundleTestUpdatePayloads(
636 self.input_proto, self.output_proto, self.validate_only_config
637 )
638 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600639
Alex Klein1699fab2022-09-08 08:46:06 -0600640 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700641 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600642 patch = self.PatchObject(artifacts_svc, "BundleTestUpdatePayloads")
643 artifacts.BundleTestUpdatePayloads(
644 self.input_proto, self.output_proto, self.mock_call_config
645 )
646 patch.assert_not_called()
647 self.assertEqual(len(self.output_proto.artifacts), 3)
648 self.assertEqual(
649 self.output_proto.artifacts[0].path,
650 os.path.join(self.archive_root, "payload1.bin"),
651 )
652 self.assertEqual(
653 self.output_proto.artifacts[1].path,
654 os.path.join(self.archive_root, "payload1.json"),
655 )
656 self.assertEqual(
657 self.output_proto.artifacts[2].path,
658 os.path.join(self.archive_root, "payload1.log"),
659 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700660
Alex Klein1699fab2022-09-08 08:46:06 -0600661 def testBundleTestUpdatePayloads(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700662 """BundleTestUpdatePayloads calls cbuildbot/commands correctly."""
Alex Klein1699fab2022-09-08 08:46:06 -0600663 image_path = os.path.join(self.image_root, constants.BASE_IMAGE_BIN)
664 osutils.WriteFile(image_path, "image!", makedirs=True)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600665
Alex Klein1699fab2022-09-08 08:46:06 -0600666 artifacts.BundleTestUpdatePayloads(
667 self.input_proto, self.output_proto, self.api_config
668 )
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600669
Alex Klein1699fab2022-09-08 08:46:06 -0600670 actual = [
671 os.path.relpath(artifact.path, self.archive_root)
672 for artifact in self.output_proto.artifacts
673 ]
674 expected = ["payload1.bin", "payload2.bin"]
675 self.assertCountEqual(actual, expected)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600676
Alex Klein1699fab2022-09-08 08:46:06 -0600677 actual = [
678 os.path.relpath(path, self.archive_root)
679 for path in osutils.DirectoryIterator(self.archive_root)
680 ]
681 self.assertCountEqual(actual, expected)
Evan Hernandezf388cbf2019-04-01 11:15:23 -0600682
Alex Klein1699fab2022-09-08 08:46:06 -0600683 def testBundleTestUpdatePayloadsNoImageDir(self):
684 """BundleTestUpdatePayloads dies if no image dir is found."""
685 # Intentionally do not write image directory.
686 artifacts.BundleTestUpdatePayloads(
687 self.input_proto, self.output_proto, self.api_config
688 )
689 self.assertFalse(self.output_proto.artifacts)
Evan Hernandez9f125ac2019-04-08 17:18:47 -0600690
Alex Klein1699fab2022-09-08 08:46:06 -0600691 def testBundleTestUpdatePayloadsNoImage(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700692 """BundleTestUpdatePayloads dies if no usable image found for target."""
Alex Klein1699fab2022-09-08 08:46:06 -0600693 # Intentionally do not write image, but create the directory.
694 osutils.SafeMakedirs(self.image_root)
695 with self.assertRaises(cros_build_lib.DieSystemExit):
696 artifacts.BundleTestUpdatePayloads(
697 self.input_proto, self.output_proto, self.api_config
698 )
Alex Klein6504eca2019-04-18 15:37:56 -0600699
700
Alex Klein1699fab2022-09-08 08:46:06 -0600701class BundleSimpleChromeArtifactsTest(
702 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
703):
704 """BundleSimpleChromeArtifacts tests."""
Alex Klein2275d692019-04-23 16:04:12 -0600705
Alex Klein1699fab2022-09-08 08:46:06 -0600706 def setUp(self):
707 self.chroot_dir = os.path.join(self.tempdir, "chroot_dir")
708 self.sysroot_path = "/sysroot"
709 self.sysroot_dir = os.path.join(self.chroot_dir, "sysroot")
710 osutils.SafeMakedirs(self.sysroot_dir)
711 self.output_dir = os.path.join(self.tempdir, "output_dir")
712 osutils.SafeMakedirs(self.output_dir)
Alex Klein2275d692019-04-23 16:04:12 -0600713
Alex Klein1699fab2022-09-08 08:46:06 -0600714 self.does_not_exist = os.path.join(self.tempdir, "does_not_exist")
Alex Klein2275d692019-04-23 16:04:12 -0600715
Alex Klein1699fab2022-09-08 08:46:06 -0600716 self.response = artifacts_pb2.BundleResponse()
Alex Klein231d2da2019-07-22 16:44:45 -0600717
Alex Klein1699fab2022-09-08 08:46:06 -0600718 def _GetRequest(
719 self,
720 chroot: Optional[str] = None,
721 sysroot: Optional[str] = None,
722 build_target: Optional[str] = None,
723 output_dir: Optional[str] = None,
724 ) -> artifacts_pb2.BundleRequest:
725 """Helper to create a request message instance.
Alex Klein2275d692019-04-23 16:04:12 -0600726
Alex Klein1699fab2022-09-08 08:46:06 -0600727 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600728 chroot: The chroot path.
729 sysroot: The sysroot path.
730 build_target: The build target name.
731 output_dir: The output directory.
Alex Klein1699fab2022-09-08 08:46:06 -0600732 """
733 return artifacts_pb2.BundleRequest(
734 sysroot={"path": sysroot, "build_target": {"name": build_target}},
735 chroot={"path": chroot},
736 output_dir=output_dir,
737 )
Alex Klein2275d692019-04-23 16:04:12 -0600738
Alex Klein1699fab2022-09-08 08:46:06 -0600739 def testValidateOnly(self):
740 """Quick check that a validate only call does not execute any logic."""
741 patch = self.PatchObject(artifacts_svc, "BundleSimpleChromeArtifacts")
742 request = self._GetRequest(
743 chroot=self.chroot_dir,
744 sysroot=self.sysroot_path,
745 build_target="board",
746 output_dir=self.output_dir,
747 )
748 artifacts.BundleSimpleChromeArtifacts(
749 request, self.response, self.validate_only_config
750 )
751 patch.assert_not_called()
Alex Klein2275d692019-04-23 16:04:12 -0600752
Alex Klein1699fab2022-09-08 08:46:06 -0600753 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700754 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600755 patch = self.PatchObject(artifacts_svc, "BundleSimpleChromeArtifacts")
756 request = self._GetRequest(
757 chroot=self.chroot_dir,
758 sysroot=self.sysroot_path,
759 build_target="board",
760 output_dir=self.output_dir,
761 )
762 artifacts.BundleSimpleChromeArtifacts(
763 request, self.response, self.mock_call_config
764 )
765 patch.assert_not_called()
766 self.assertEqual(len(self.response.artifacts), 1)
767 self.assertEqual(
768 self.response.artifacts[0].path,
769 os.path.join(self.output_dir, "simple_chrome.txt"),
770 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700771
Alex Klein1699fab2022-09-08 08:46:06 -0600772 def testNoBuildTarget(self):
773 """Test no build target fails."""
774 request = self._GetRequest(
775 chroot=self.chroot_dir,
776 sysroot=self.sysroot_path,
777 output_dir=self.output_dir,
778 )
779 response = self.response
780 with self.assertRaises(cros_build_lib.DieSystemExit):
781 artifacts.BundleSimpleChromeArtifacts(
782 request, response, self.api_config
783 )
Alex Klein2275d692019-04-23 16:04:12 -0600784
Alex Klein1699fab2022-09-08 08:46:06 -0600785 def testNoSysroot(self):
786 """Test no sysroot fails."""
787 request = self._GetRequest(
788 build_target="board", output_dir=self.output_dir
789 )
790 response = self.response
791 with self.assertRaises(cros_build_lib.DieSystemExit):
792 artifacts.BundleSimpleChromeArtifacts(
793 request, response, self.api_config
794 )
Alex Klein2275d692019-04-23 16:04:12 -0600795
Alex Klein1699fab2022-09-08 08:46:06 -0600796 def testSysrootDoesNotExist(self):
797 """Test no sysroot fails."""
798 request = self._GetRequest(
799 build_target="board",
800 output_dir=self.output_dir,
801 sysroot=self.does_not_exist,
802 )
803 response = self.response
Alex Kleinb6847e22022-11-07 10:44:48 -0700804 artifacts.BundleSimpleChromeArtifacts(
805 request, response, self.api_config
806 )
807 self.assertFalse(self.response.artifacts)
Alex Klein2275d692019-04-23 16:04:12 -0600808
Alex Klein1699fab2022-09-08 08:46:06 -0600809 def testNoOutputDir(self):
810 """Test no output dir fails."""
811 request = self._GetRequest(
812 chroot=self.chroot_dir,
813 sysroot=self.sysroot_path,
814 build_target="board",
815 )
816 response = self.response
817 with self.assertRaises(cros_build_lib.DieSystemExit):
818 artifacts.BundleSimpleChromeArtifacts(
819 request, response, self.api_config
820 )
Alex Klein2275d692019-04-23 16:04:12 -0600821
Alex Klein1699fab2022-09-08 08:46:06 -0600822 def testOutputDirDoesNotExist(self):
823 """Test no output dir fails."""
824 request = self._GetRequest(
825 chroot=self.chroot_dir,
826 sysroot=self.sysroot_path,
827 build_target="board",
828 output_dir=self.does_not_exist,
829 )
830 response = self.response
831 with self.assertRaises(cros_build_lib.DieSystemExit):
832 artifacts.BundleSimpleChromeArtifacts(
833 request, response, self.api_config
834 )
Alex Klein2275d692019-04-23 16:04:12 -0600835
Alex Klein1699fab2022-09-08 08:46:06 -0600836 def testOutputHandling(self):
837 """Test response output."""
838 files = ["file1", "file2", "file3"]
839 expected_files = [os.path.join(self.output_dir, f) for f in files]
840 self.PatchObject(
841 artifacts_svc,
842 "BundleSimpleChromeArtifacts",
843 return_value=expected_files,
844 )
845 request = self._GetRequest(
846 chroot=self.chroot_dir,
847 sysroot=self.sysroot_path,
848 build_target="board",
849 output_dir=self.output_dir,
850 )
851 response = self.response
Alex Klein2275d692019-04-23 16:04:12 -0600852
Alex Klein1699fab2022-09-08 08:46:06 -0600853 artifacts.BundleSimpleChromeArtifacts(
854 request, response, self.api_config
855 )
Alex Klein2275d692019-04-23 16:04:12 -0600856
Alex Klein1699fab2022-09-08 08:46:06 -0600857 self.assertTrue(response.artifacts)
858 self.assertCountEqual(
859 expected_files, [a.path for a in response.artifacts]
860 )
Alex Klein2275d692019-04-23 16:04:12 -0600861
862
Alex Klein1699fab2022-09-08 08:46:06 -0600863class BundleVmFilesTest(
864 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
865):
866 """BuildVmFiles tests."""
Alex Klein6504eca2019-04-18 15:37:56 -0600867
Alex Klein1699fab2022-09-08 08:46:06 -0600868 def setUp(self):
869 self.output_dir = os.path.join(self.tempdir, "output")
870 osutils.SafeMakedirs(self.output_dir)
Alex Klein231d2da2019-07-22 16:44:45 -0600871
Alex Klein1699fab2022-09-08 08:46:06 -0600872 self.response = artifacts_pb2.BundleResponse()
Alex Klein231d2da2019-07-22 16:44:45 -0600873
Alex Klein1699fab2022-09-08 08:46:06 -0600874 def _GetInput(
875 self,
876 chroot: Optional[str] = None,
877 sysroot: Optional[str] = None,
878 test_results_dir: Optional[str] = None,
879 output_dir: Optional[str] = None,
880 ) -> artifacts_pb2.BundleVmFilesRequest:
881 """Helper to build out an input message instance.
Alex Klein6504eca2019-04-18 15:37:56 -0600882
Alex Klein1699fab2022-09-08 08:46:06 -0600883 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600884 chroot: The chroot path.
885 sysroot: The sysroot path relative to the chroot.
Alex Kleinab87ceb2023-01-24 12:00:51 -0700886 test_results_dir: The test results directory relative to the
887 sysroot.
Alex Klein611dddd2022-10-11 17:02:01 -0600888 output_dir: The directory where the results tarball should be saved.
Alex Klein1699fab2022-09-08 08:46:06 -0600889 """
890 return artifacts_pb2.BundleVmFilesRequest(
891 chroot={"path": chroot},
892 sysroot={"path": sysroot},
893 test_results_dir=test_results_dir,
894 output_dir=output_dir,
895 )
Alex Klein6504eca2019-04-18 15:37:56 -0600896
Alex Klein1699fab2022-09-08 08:46:06 -0600897 def testValidateOnly(self):
898 """Quick check that a validate only call does not execute any logic."""
899 patch = self.PatchObject(artifacts_svc, "BundleVmFiles")
900 in_proto = self._GetInput(
901 chroot="/chroot/dir",
902 sysroot="/build/board",
903 test_results_dir="/test/results",
904 output_dir=self.output_dir,
905 )
906 artifacts.BundleVmFiles(
907 in_proto, self.response, self.validate_only_config
908 )
909 patch.assert_not_called()
Alex Klein6504eca2019-04-18 15:37:56 -0600910
Alex Klein1699fab2022-09-08 08:46:06 -0600911 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700912 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600913 patch = self.PatchObject(artifacts_svc, "BundleVmFiles")
914 in_proto = self._GetInput(
915 chroot="/chroot/dir",
916 sysroot="/build/board",
917 test_results_dir="/test/results",
918 output_dir=self.output_dir,
919 )
920 artifacts.BundleVmFiles(in_proto, self.response, self.mock_call_config)
921 patch.assert_not_called()
922 self.assertEqual(len(self.response.artifacts), 1)
923 self.assertEqual(
924 self.response.artifacts[0].path,
925 os.path.join(self.output_dir, "f1.tar"),
926 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -0700927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 def testChrootMissing(self):
929 """Test error handling for missing chroot."""
930 in_proto = self._GetInput(
931 sysroot="/build/board",
932 test_results_dir="/test/results",
933 output_dir=self.output_dir,
934 )
Alex Klein6504eca2019-04-18 15:37:56 -0600935
Alex Klein1699fab2022-09-08 08:46:06 -0600936 with self.assertRaises(cros_build_lib.DieSystemExit):
937 artifacts.BundleVmFiles(in_proto, self.response, self.api_config)
Alex Klein6504eca2019-04-18 15:37:56 -0600938
Alex Klein1699fab2022-09-08 08:46:06 -0600939 def testTestResultsDirMissing(self):
940 """Test error handling for missing test results directory."""
941 in_proto = self._GetInput(
942 chroot="/chroot/dir",
943 sysroot="/build/board",
944 output_dir=self.output_dir,
945 )
Alex Klein6504eca2019-04-18 15:37:56 -0600946
Alex Klein1699fab2022-09-08 08:46:06 -0600947 with self.assertRaises(cros_build_lib.DieSystemExit):
948 artifacts.BundleVmFiles(in_proto, self.response, self.api_config)
Alex Klein6504eca2019-04-18 15:37:56 -0600949
Alex Klein1699fab2022-09-08 08:46:06 -0600950 def testOutputDirMissing(self):
951 """Test error handling for missing output directory."""
952 in_proto = self._GetInput(
953 chroot="/chroot/dir",
954 sysroot="/build/board",
955 test_results_dir="/test/results",
956 )
Alex Klein6504eca2019-04-18 15:37:56 -0600957
Alex Klein1699fab2022-09-08 08:46:06 -0600958 with self.assertRaises(cros_build_lib.DieSystemExit):
959 artifacts.BundleVmFiles(in_proto, self.response, self.api_config)
Alex Klein231d2da2019-07-22 16:44:45 -0600960
Alex Klein1699fab2022-09-08 08:46:06 -0600961 def testOutputDirDoesNotExist(self):
962 """Test error handling for output directory that does not exist."""
963 in_proto = self._GetInput(
964 chroot="/chroot/dir",
965 sysroot="/build/board",
966 output_dir=os.path.join(self.tempdir, "dne"),
967 test_results_dir="/test/results",
968 )
Alex Klein231d2da2019-07-22 16:44:45 -0600969
Alex Klein1699fab2022-09-08 08:46:06 -0600970 with self.assertRaises(cros_build_lib.DieSystemExit):
971 artifacts.BundleVmFiles(in_proto, self.response, self.api_config)
Alex Klein6504eca2019-04-18 15:37:56 -0600972
Alex Klein1699fab2022-09-08 08:46:06 -0600973 def testValidCall(self):
974 """Test image dir building."""
975 in_proto = self._GetInput(
976 chroot="/chroot/dir",
977 sysroot="/build/board",
978 test_results_dir="/test/results",
979 output_dir=self.output_dir,
980 )
Alex Klein231d2da2019-07-22 16:44:45 -0600981
Alex Klein1699fab2022-09-08 08:46:06 -0600982 expected_files = ["/tmp/output/f1.tar", "/tmp/output/f2.tar"]
983 patch = self.PatchObject(
984 artifacts_svc, "BundleVmFiles", return_value=expected_files
985 )
Alex Klein6504eca2019-04-18 15:37:56 -0600986
Alex Klein1699fab2022-09-08 08:46:06 -0600987 artifacts.BundleVmFiles(in_proto, self.response, self.api_config)
Alex Klein6504eca2019-04-18 15:37:56 -0600988
Alex Klein1699fab2022-09-08 08:46:06 -0600989 patch.assert_called_with(mock.ANY, "/test/results", self.output_dir)
Alex Klein6504eca2019-04-18 15:37:56 -0600990
Alex Kleinab87ceb2023-01-24 12:00:51 -0700991 # Make sure we have artifacts, and that every artifact is an expected
992 # file.
Alex Klein1699fab2022-09-08 08:46:06 -0600993 self.assertTrue(self.response.artifacts)
994 for artifact in self.response.artifacts:
995 self.assertIn(artifact.path, expected_files)
996 expected_files.remove(artifact.path)
Alex Klein6504eca2019-04-18 15:37:56 -0600997
Alex Klein1699fab2022-09-08 08:46:06 -0600998 # Make sure we've seen all of the expected files.
999 self.assertFalse(expected_files)
Alex Kleinb9d810b2019-07-01 12:38:02 -06001000
Tiancong Wang50b80a92019-08-01 14:46:15 -07001001
Alex Klein036833d2022-06-01 13:05:01 -06001002class ExportCpeReportTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001003 """ExportCpeReport tests."""
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001004
Alex Klein1699fab2022-09-08 08:46:06 -06001005 def testValidateOnly(self):
1006 """Quick check validate only calls don't execute."""
1007 patch = self.PatchObject(artifacts_svc, "GenerateCpeReport")
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001008
Alex Klein1699fab2022-09-08 08:46:06 -06001009 artifacts.ExportCpeReport(
1010 self.sysroot_request, self.response, self.validate_only_config
1011 )
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001012
Alex Klein1699fab2022-09-08 08:46:06 -06001013 patch.assert_not_called()
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001014
Alex Klein1699fab2022-09-08 08:46:06 -06001015 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -07001016 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -06001017 patch = self.PatchObject(artifacts_svc, "GenerateCpeReport")
Michael Mortensen2d6a2402019-11-26 13:40:40 -07001018
Alex Klein1699fab2022-09-08 08:46:06 -06001019 artifacts.ExportCpeReport(
1020 self.sysroot_request, self.response, self.mock_call_config
1021 )
Michael Mortensen2d6a2402019-11-26 13:40:40 -07001022
Alex Klein1699fab2022-09-08 08:46:06 -06001023 patch.assert_not_called()
1024 self.assertEqual(len(self.response.artifacts), 2)
1025 self.assertEqual(
1026 self.response.artifacts[0].path,
1027 os.path.join(self.output_dir, "cpe_report.txt"),
1028 )
1029 self.assertEqual(
1030 self.response.artifacts[1].path,
1031 os.path.join(self.output_dir, "cpe_warnings.txt"),
1032 )
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001033
Alex Klein1699fab2022-09-08 08:46:06 -06001034 def testSuccess(self):
1035 """Test success case."""
1036 expected = artifacts_svc.CpeResult(
1037 report="/output/report.json", warnings="/output/warnings.json"
1038 )
1039 self.PatchObject(
1040 artifacts_svc, "GenerateCpeReport", return_value=expected
1041 )
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001042
Alex Klein1699fab2022-09-08 08:46:06 -06001043 artifacts.ExportCpeReport(
1044 self.sysroot_request, self.response, self.api_config
1045 )
Alex Klein0b1cbfc2019-08-14 10:09:58 -06001046
Alex Klein1699fab2022-09-08 08:46:06 -06001047 for artifact in self.response.artifacts:
1048 self.assertIn(artifact.path, [expected.report, expected.warnings])
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001049
1050
1051class BundleGceTarballTest(BundleTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001052 """Unittests for BundleGceTarball."""
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001053
Alex Klein1699fab2022-09-08 08:46:06 -06001054 def testValidateOnly(self):
1055 """Check that a validate only call does not execute any logic."""
1056 patch = self.PatchObject(artifacts_svc, "BundleGceTarball")
1057 artifacts.BundleGceTarball(
1058 self.target_request, self.response, self.validate_only_config
1059 )
1060 patch.assert_not_called()
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001061
Alex Klein1699fab2022-09-08 08:46:06 -06001062 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -07001063 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -06001064 patch = self.PatchObject(artifacts_svc, "BundleGceTarball")
1065 artifacts.BundleGceTarball(
1066 self.target_request, self.response, self.mock_call_config
1067 )
1068 patch.assert_not_called()
1069 self.assertEqual(len(self.response.artifacts), 1)
1070 self.assertEqual(
1071 self.response.artifacts[0].path,
1072 os.path.join(self.output_dir, constants.TEST_IMAGE_GCE_TAR),
1073 )
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001074
Alex Klein1699fab2022-09-08 08:46:06 -06001075 def testBundleGceTarball(self):
1076 """BundleGceTarball calls cbuildbot/commands with correct args."""
1077 bundle_gce_tarball = self.PatchObject(
1078 artifacts_svc,
1079 "BundleGceTarball",
1080 return_value=os.path.join(
1081 self.output_dir, constants.TEST_IMAGE_GCE_TAR
1082 ),
1083 )
1084 self.PatchObject(os.path, "exists", return_value=True)
1085 artifacts.BundleGceTarball(
1086 self.target_request, self.response, self.api_config
1087 )
1088 self.assertEqual(
1089 [artifact.path for artifact in self.response.artifacts],
1090 [os.path.join(self.output_dir, constants.TEST_IMAGE_GCE_TAR)],
1091 )
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 latest = os.path.join(
1094 self.source_root, "src/build/images/target/latest"
1095 )
1096 self.assertEqual(
1097 bundle_gce_tarball.call_args_list,
1098 [mock.call(self.output_dir, latest)],
1099 )
Shao-Chuan Leea44dddc2020-10-30 17:16:55 +09001100
Alex Klein1699fab2022-09-08 08:46:06 -06001101 def testBundleGceTarballNoImageDir(self):
1102 """BundleGceTarball dies when image dir does not exist."""
1103 self.PatchObject(os.path, "exists", return_value=False)
1104 with self.assertRaises(cros_build_lib.DieSystemExit):
1105 artifacts.BundleGceTarball(
1106 self.target_request, self.response, self.api_config
1107 )
Greg Edelstondc941072021-08-11 12:32:30 -06001108
Greg Edelstondc941072021-08-11 12:32:30 -06001109
Alex Klein1699fab2022-09-08 08:46:06 -06001110class FetchMetadataTestCase(
1111 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
1112):
1113 """Unittests for FetchMetadata."""
Greg Edelstondc941072021-08-11 12:32:30 -06001114
Alex Klein1699fab2022-09-08 08:46:06 -06001115 sysroot_path = "/build/coral"
1116 chroot_name = "chroot"
Greg Edelstondc941072021-08-11 12:32:30 -06001117
Alex Klein1699fab2022-09-08 08:46:06 -06001118 def setUp(self):
1119 self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=False)
1120 self.chroot_path = os.path.join(self.tempdir, "chroot")
1121 pathlib.Path(self.chroot_path).touch()
1122 self.expected_filepaths = [
1123 os.path.join(self.chroot_path, fp)
1124 for fp in (
1125 "build/coral/usr/local/build/autotest/autotest_metadata.pb",
1126 "build/coral/usr/share/tast/metadata/local/cros.pb",
1127 "build/coral/build/share/tast/metadata/local/crosint.pb",
1128 "usr/share/tast/metadata/remote/cros.pb",
1129 )
1130 ]
1131 self.PatchObject(cros_build_lib, "AssertOutsideChroot")
Greg Edelstondc941072021-08-11 12:32:30 -06001132
Alex Klein1699fab2022-09-08 08:46:06 -06001133 def createFetchMetadataRequest(
1134 self, use_sysroot_path=True, use_chroot=True
1135 ):
1136 """Construct a FetchMetadataRequest for use in test cases."""
1137 request = artifacts_pb2.FetchMetadataRequest()
1138 if use_sysroot_path:
1139 request.sysroot.path = self.sysroot_path
1140 if use_chroot:
1141 request.chroot.path = self.chroot_path
1142 return request
Greg Edelstondc941072021-08-11 12:32:30 -06001143
Alex Klein1699fab2022-09-08 08:46:06 -06001144 def testValidateOnly(self):
1145 """Check that a validate only call does not execute any logic."""
1146 patch = self.PatchObject(controller_util, "ParseSysroot")
1147 request = self.createFetchMetadataRequest()
1148 response = artifacts_pb2.FetchMetadataResponse()
1149 artifacts.FetchMetadata(request, response, self.validate_only_config)
1150 patch.assert_not_called()
Greg Edelstondc941072021-08-11 12:32:30 -06001151
Alex Klein1699fab2022-09-08 08:46:06 -06001152 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -07001153 """Test a mock call does not execute logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -06001154 patch = self.PatchObject(controller_util, "ParseSysroot")
1155 request = self.createFetchMetadataRequest()
1156 response = artifacts_pb2.FetchMetadataResponse()
1157 artifacts.FetchMetadata(request, response, self.mock_call_config)
1158 patch.assert_not_called()
1159 self.assertGreater(len(response.filepaths), 0)
Greg Edelstondc941072021-08-11 12:32:30 -06001160
Alex Klein1699fab2022-09-08 08:46:06 -06001161 def testNoSysrootPath(self):
1162 """Check that a request with no sysroot.path results in failure."""
1163 request = self.createFetchMetadataRequest(use_sysroot_path=False)
1164 response = artifacts_pb2.FetchMetadataResponse()
1165 with self.assertRaises(cros_build_lib.DieSystemExit):
1166 artifacts.FetchMetadata(request, response, self.api_config)
Greg Edelstondc941072021-08-11 12:32:30 -06001167
Alex Klein1699fab2022-09-08 08:46:06 -06001168 def testNoChroot(self):
1169 """Check that a request with no chroot results in failure."""
1170 request = self.createFetchMetadataRequest(use_chroot=False)
1171 response = artifacts_pb2.FetchMetadataResponse()
1172 with self.assertRaises(cros_build_lib.DieSystemExit):
1173 artifacts.FetchMetadata(request, response, self.api_config)
1174
1175 def testSuccess(self):
1176 """Check that a well-formed request yields the expected results."""
1177 request = self.createFetchMetadataRequest(use_chroot=True)
1178 response = artifacts_pb2.FetchMetadataResponse()
1179 artifacts.FetchMetadata(request, response, self.api_config)
1180 actual_filepaths = [fp.path.path for fp in response.filepaths]
1181 self.assertEqual(
1182 sorted(actual_filepaths), sorted(self.expected_filepaths)
1183 )
1184 self.assertTrue(
1185 all(
1186 fp.path.location == common_pb2.Path.OUTSIDE
1187 for fp in response.filepaths
1188 )
1189 )
Jack Neus26b94672022-10-27 17:33:21 +00001190
1191
1192class GetTest(cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin):
1193 """Get function tests."""
1194
1195 def setUp(self):
1196 self.sysroot_path = "/build/target"
1197 self.sysroot = sysroot_lib.Sysroot(self.sysroot_path)
1198
1199 def _InputProto(self):
1200 """Helper to build an input proto instance."""
Alex Kleinab87ceb2023-01-24 12:00:51 -07001201 # pylint: disable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001202 return artifacts_pb2.GetRequest(
1203 sysroot=sysroot_pb2.Sysroot(path=self.sysroot_path),
1204 artifact_info=common_pb2.ArtifactsByService(
1205 sysroot=common_pb2.ArtifactsByService.Sysroot(
1206 output_artifacts=[
1207 common_pb2.ArtifactsByService.Sysroot.ArtifactInfo(
1208 artifact_types=[
1209 common_pb2.ArtifactsByService.Sysroot.ArtifactType.FUZZER_SYSROOT
1210 ]
1211 )
1212 ],
1213 ),
1214 image=common_pb2.ArtifactsByService.Image(
1215 output_artifacts=[
1216 common_pb2.ArtifactsByService.Image.ArtifactInfo(
1217 artifact_types=[
1218 common_pb2.ArtifactsByService.Image.ArtifactType.LICENSE_CREDITS
1219 ]
1220 )
1221 ],
1222 ),
1223 test=common_pb2.ArtifactsByService.Test(
1224 output_artifacts=[
1225 common_pb2.ArtifactsByService.Test.ArtifactInfo(
1226 artifact_types=[
1227 common_pb2.ArtifactsByService.Test.ArtifactType.HWQUAL
1228 ]
1229 )
1230 ],
1231 ),
1232 ),
1233 result_path=common_pb2.ResultPath(
1234 path=common_pb2.Path(path=str(self.tempdir))
1235 ),
1236 )
Alex Kleinab87ceb2023-01-24 12:00:51 -07001237 # pylint: enable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001238
1239 def _OutputProto(self):
1240 """Helper to build an output proto instance."""
1241 return artifacts_pb2.GetResponse()
1242
1243 def testSuccess(self):
1244 """Test Get."""
Alex Kleinab87ceb2023-01-24 12:00:51 -07001245 # pylint: disable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001246 image_mock = self.PatchObject(
1247 image_controller,
1248 "GetArtifacts",
1249 return_value=[
1250 {
1251 "paths": ["/foo/bar/license_credits.html"],
1252 "type": common_pb2.ArtifactsByService.Image.ArtifactType.LICENSE_CREDITS,
1253 }
1254 ],
1255 )
1256 sysroot_mock = self.PatchObject(
1257 sysroot_controller,
1258 "GetArtifacts",
1259 return_value=[
1260 {
1261 "type": common_pb2.ArtifactsByService.Sysroot.ArtifactType.FUZZER_SYSROOT,
1262 "failed": True,
1263 "failure_reason": "Bad data!",
1264 }
1265 ],
1266 )
1267 test_mock = self.PatchObject(
1268 test_controller,
1269 "GetArtifacts",
1270 return_value=[
1271 {
1272 "paths": ["/foo/bar/hwqual.tar.xz"],
1273 "type": common_pb2.ArtifactsByService.Test.ArtifactType.HWQUAL,
1274 }
1275 ],
1276 )
Alex Kleinab87ceb2023-01-24 12:00:51 -07001277 # pylint: enable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001278
1279 in_proto = self._InputProto()
1280 out_proto = self._OutputProto()
1281 artifacts.Get(
1282 in_proto,
1283 out_proto,
1284 self.api_config,
1285 )
1286
1287 image_mock.assert_called_once()
1288 sysroot_mock.assert_called_once()
1289 test_mock.assert_called_once()
1290
Alex Kleinab87ceb2023-01-24 12:00:51 -07001291 # pylint: disable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001292 expected = common_pb2.UploadedArtifactsByService(
1293 sysroot=common_pb2.UploadedArtifactsByService.Sysroot(
1294 artifacts=[
1295 common_pb2.UploadedArtifactsByService.Sysroot.ArtifactPaths(
1296 artifact_type=common_pb2.ArtifactsByService.Sysroot.ArtifactType.FUZZER_SYSROOT,
1297 failed=True,
1298 failure_reason="Bad data!",
1299 )
1300 ]
1301 ),
1302 image=common_pb2.UploadedArtifactsByService.Image(
1303 artifacts=[
1304 common_pb2.UploadedArtifactsByService.Image.ArtifactPaths(
1305 artifact_type=common_pb2.ArtifactsByService.Image.ArtifactType.LICENSE_CREDITS,
1306 paths=[
1307 common_pb2.Path(
1308 path="/foo/bar/license_credits.html",
1309 location=common_pb2.Path.OUTSIDE,
1310 )
1311 ],
1312 )
1313 ]
1314 ),
1315 test=common_pb2.UploadedArtifactsByService.Test(
1316 artifacts=[
1317 common_pb2.UploadedArtifactsByService.Test.ArtifactPaths(
1318 artifact_type=common_pb2.ArtifactsByService.Test.ArtifactType.HWQUAL,
1319 paths=[
1320 common_pb2.Path(
1321 path="/foo/bar/hwqual.tar.xz",
1322 location=common_pb2.Path.OUTSIDE,
1323 )
1324 ],
1325 )
1326 ]
1327 ),
1328 )
Alex Kleinab87ceb2023-01-24 12:00:51 -07001329 # pylint: enable=line-too-long
Jack Neus26b94672022-10-27 17:33:21 +00001330 self.assertEqual(out_proto.artifacts, expected)