blob: 1ab02a10e4741c6a94793fefd79bc0fb9d364d1a [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Alex Kleina2e42c42019-04-17 16:13:19 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""The test controller tests."""
6
Lizzy Presland4feb2372022-01-20 05:16:30 +00007import datetime
Mike Frysingeref94e4c2020-02-10 23:59:54 -05008import os
Mike Frysinger40443592022-05-05 13:03:40 -04009from typing import Union
Mike Frysingeref94e4c2020-02-10 23:59:54 -050010
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040011from chromite.third_party.google.protobuf import json_format
12
Alex Klein231d2da2019-07-22 16:44:45 -060013from chromite.api import api_config
Alex Klein8cb365a2019-05-15 16:24:53 -060014from chromite.api import controller
Lizzy Presland4feb2372022-01-20 05:16:30 +000015from chromite.api.controller import controller_util
Alex Kleina2e42c42019-04-17 16:13:19 -060016from chromite.api.controller import test as test_controller
17from chromite.api.gen.chromite.api import test_pb2
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040018from chromite.api.gen.chromiumos import common_pb2
Sean McAllister3834fef2021-10-08 15:45:18 -060019from chromite.api.gen.chromiumos.build.api import container_metadata_pb2
Jack Neusc9707c32021-07-23 21:48:54 +000020from chromite.lib import build_target_lib
Evan Hernandeze1e05d32019-07-19 12:32:18 -060021from chromite.lib import chroot_lib
Alex Kleina2e42c42019-04-17 16:13:19 -060022from chromite.lib import cros_build_lib
23from chromite.lib import cros_test_lib
Alex Kleina2e42c42019-04-17 16:13:19 -060024from chromite.lib import osutils
David Wellingc1433c22021-06-25 16:29:48 +000025from chromite.lib import sysroot_lib
Alex Klein18a60af2020-06-11 12:08:47 -060026from chromite.lib.parser import package_info
Evan Hernandezdc3f0bb2019-06-06 12:46:52 -060027from chromite.service import test as test_service
Alex Kleina2e42c42019-04-17 16:13:19 -060028
29
Alex Klein1699fab2022-09-08 08:46:06 -060030class DebugInfoTestTest(
31 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
32):
33 """Tests for the DebugInfoTest function."""
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070034
Alex Klein1699fab2022-09-08 08:46:06 -060035 def setUp(self):
36 self.board = "board"
37 self.chroot_path = os.path.join(self.tempdir, "chroot")
38 self.sysroot_path = "/build/board"
39 self.full_sysroot_path = os.path.join(
40 self.chroot_path, self.sysroot_path.lstrip(os.sep)
41 )
42 osutils.SafeMakedirs(self.full_sysroot_path)
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070043
Alex Klein1699fab2022-09-08 08:46:06 -060044 def _GetInput(self, sysroot_path=None, build_target=None):
45 """Helper to build an input message instance."""
46 proto = test_pb2.DebugInfoTestRequest()
47 if sysroot_path:
48 proto.sysroot.path = sysroot_path
49 if build_target:
50 proto.sysroot.build_target.name = build_target
51 return proto
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070052
Alex Klein1699fab2022-09-08 08:46:06 -060053 def _GetOutput(self):
54 """Helper to get an empty output message instance."""
55 return test_pb2.DebugInfoTestResponse()
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070056
Alex Klein1699fab2022-09-08 08:46:06 -060057 def testValidateOnly(self):
58 """Sanity check that a validate only call does not execute any logic."""
59 patch = self.PatchObject(test_service, "DebugInfoTest")
60 input_msg = self._GetInput(sysroot_path=self.full_sysroot_path)
61 test_controller.DebugInfoTest(
62 input_msg, self._GetOutput(), self.validate_only_config
63 )
64 patch.assert_not_called()
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070065
Alex Klein1699fab2022-09-08 08:46:06 -060066 def testMockError(self):
67 """Test mock error call does not execute any logic, returns error."""
68 patch = self.PatchObject(test_service, "DebugInfoTest")
Michael Mortensen85d38402019-12-12 09:50:29 -070069
Alex Klein1699fab2022-09-08 08:46:06 -060070 input_msg = self._GetInput(sysroot_path=self.full_sysroot_path)
71 rc = test_controller.DebugInfoTest(
72 input_msg, self._GetOutput(), self.mock_error_config
73 )
74 patch.assert_not_called()
75 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Michael Mortensen85d38402019-12-12 09:50:29 -070076
Alex Klein1699fab2022-09-08 08:46:06 -060077 def testMockCall(self):
78 """Test mock call does not execute any logic, returns success."""
79 patch = self.PatchObject(test_service, "DebugInfoTest")
Michael Mortensen85d38402019-12-12 09:50:29 -070080
Alex Klein1699fab2022-09-08 08:46:06 -060081 input_msg = self._GetInput(sysroot_path=self.full_sysroot_path)
82 rc = test_controller.DebugInfoTest(
83 input_msg, self._GetOutput(), self.mock_call_config
84 )
85 patch.assert_not_called()
86 self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
Michael Mortensen85d38402019-12-12 09:50:29 -070087
Alex Klein1699fab2022-09-08 08:46:06 -060088 def testNoBuildTargetNoSysrootFails(self):
89 """Test missing build target name and sysroot path fails."""
90 input_msg = self._GetInput()
91 output_msg = self._GetOutput()
92 with self.assertRaises(cros_build_lib.DieSystemExit):
93 test_controller.DebugInfoTest(
94 input_msg, output_msg, self.api_config
95 )
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -070096
Alex Klein1699fab2022-09-08 08:46:06 -060097 def testDebugInfoTest(self):
98 """Call DebugInfoTest with valid sysroot_path."""
99 request = self._GetInput(sysroot_path=self.full_sysroot_path)
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 test_controller.DebugInfoTest(
102 request, self._GetOutput(), self.api_config
103 )
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700104
105
Alex Klein1699fab2022-09-08 08:46:06 -0600106class BuildTargetUnitTestTest(
107 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
108):
109 """Tests for the UnitTest function."""
Alex Kleina2e42c42019-04-17 16:13:19 -0600110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 def setUp(self):
112 # Set up portage log directory.
113 self.sysroot = os.path.join(self.tempdir, "build", "board")
114 osutils.SafeMakedirs(self.sysroot)
115 self.target_sysroot = sysroot_lib.Sysroot(self.sysroot)
116 self.portage_dir = os.path.join(self.tempdir, "portage_logdir")
117 self.PatchObject(
118 sysroot_lib.Sysroot, "portage_logdir", new=self.portage_dir
119 )
120 osutils.SafeMakedirs(self.portage_dir)
Lizzy Presland4feb2372022-01-20 05:16:30 +0000121
Alex Klein1699fab2022-09-08 08:46:06 -0600122 def _GetInput(
123 self,
124 board=None,
125 chroot_path=None,
126 cache_dir=None,
127 empty_sysroot=None,
128 packages=None,
129 blocklist=None,
130 ):
131 """Helper to build an input message instance."""
132 formatted_packages = []
133 for pkg in packages or []:
134 formatted_packages.append(
135 {"category": pkg.category, "package_name": pkg.package}
136 )
137 formatted_blocklist = []
138 for pkg in blocklist or []:
139 formatted_blocklist.append(
140 {"category": pkg.category, "package_name": pkg.package}
141 )
Alex Kleinf2674462019-05-16 16:47:24 -0600142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 return test_pb2.BuildTargetUnitTestRequest(
144 build_target={"name": board},
145 chroot={"path": chroot_path, "cache_dir": cache_dir},
146 flags={"empty_sysroot": empty_sysroot},
147 packages=formatted_packages,
148 package_blocklist=formatted_blocklist,
149 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600150
Alex Klein1699fab2022-09-08 08:46:06 -0600151 def _GetOutput(self):
152 """Helper to get an empty output message instance."""
153 return test_pb2.BuildTargetUnitTestResponse()
Alex Kleina2e42c42019-04-17 16:13:19 -0600154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 def _CreatePortageLogFile(
156 self,
157 log_path: Union[str, os.PathLike],
158 pkg_info: package_info.PackageInfo,
159 timestamp: datetime.datetime,
160 ) -> str:
161 """Creates a log file for testing for individual packages built by Portage.
Lizzy Presland4feb2372022-01-20 05:16:30 +0000162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 Args:
164 log_path: The PORTAGE_LOGDIR path.
165 pkg_info: name components for log file.
166 timestamp: Timestamp used to name the file.
167 """
168 path = os.path.join(
169 log_path,
170 f"{pkg_info.category}:{pkg_info.pvr}:"
171 f'{timestamp.strftime("%Y%m%d-%H%M%S")}.log',
172 )
173 osutils.WriteFile(
174 path,
175 f"Test log file for package {pkg_info.category}/"
176 f"{pkg_info.package} written to {path}",
177 )
178 return path
Lizzy Presland4feb2372022-01-20 05:16:30 +0000179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 def testValidateOnly(self):
181 """Sanity check that a validate only call does not execute any logic."""
182 patch = self.PatchObject(test_service, "BuildTargetUnitTest")
Alex Klein231d2da2019-07-22 16:44:45 -0600183
Alex Klein1699fab2022-09-08 08:46:06 -0600184 input_msg = self._GetInput(board="board")
185 test_controller.BuildTargetUnitTest(
186 input_msg, self._GetOutput(), self.validate_only_config
187 )
188 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 def testMockCall(self):
191 """Test that a mock call does not execute logic, returns mocked value."""
192 patch = self.PatchObject(test_service, "BuildTargetUnitTest")
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 input_msg = self._GetInput(board="board")
195 response = self._GetOutput()
196 test_controller.BuildTargetUnitTest(
197 input_msg, response, self.mock_call_config
198 )
199 patch.assert_not_called()
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700200
Alex Klein1699fab2022-09-08 08:46:06 -0600201 def testMockError(self):
202 """Test that a mock error does not execute logic, returns error."""
203 patch = self.PatchObject(test_service, "BuildTargetUnitTest")
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700204
Alex Klein1699fab2022-09-08 08:46:06 -0600205 input_msg = self._GetInput(board="board")
206 response = self._GetOutput()
207 rc = test_controller.BuildTargetUnitTest(
208 input_msg, response, self.mock_error_config
209 )
210 patch.assert_not_called()
211 self.assertEqual(
212 controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE, rc
213 )
214 self.assertTrue(response.failed_package_data)
215 self.assertEqual(response.failed_package_data[0].name.category, "foo")
216 self.assertEqual(
217 response.failed_package_data[0].name.package_name, "bar"
218 )
219 self.assertEqual(response.failed_package_data[1].name.category, "cat")
220 self.assertEqual(
221 response.failed_package_data[1].name.package_name, "pkg"
222 )
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700223
Alex Klein1699fab2022-09-08 08:46:06 -0600224 def testInvalidPackageFails(self):
225 """Test missing result path fails."""
226 # Missing result_path.
227 pkg = package_info.PackageInfo(package="bar")
228 input_msg = self._GetInput(board="board", packages=[pkg])
229 output_msg = self._GetOutput()
230 with self.assertRaises(cros_build_lib.DieSystemExit):
231 test_controller.BuildTargetUnitTest(
232 input_msg, output_msg, self.api_config
233 )
Alex Klein64ac34c2020-09-23 10:21:33 -0600234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 def testPackageBuildFailure(self):
236 """Test handling of raised BuildPackageFailure."""
237 tempdir = osutils.TempDir(base_dir=self.tempdir)
238 self.PatchObject(osutils, "TempDir", return_value=tempdir)
Alex Kleina2e42c42019-04-17 16:13:19 -0600239
Alex Klein1699fab2022-09-08 08:46:06 -0600240 pkgs = ["cat/pkg-1.0-r1", "foo/bar-2.0-r1"]
241 cpvrs = [package_info.parse(pkg) for pkg in pkgs]
242 expected = [("cat", "pkg"), ("foo", "bar")]
243 new_logs = {}
244 for i, pkg in enumerate(pkgs):
245 self._CreatePortageLogFile(
246 self.portage_dir,
247 cpvrs[i],
248 datetime.datetime(2021, 6, 9, 13, 37, 0),
249 )
250 new_logs[pkg] = self._CreatePortageLogFile(
251 self.portage_dir,
252 cpvrs[i],
253 datetime.datetime(2021, 6, 9, 16, 20, 0),
254 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 result = test_service.BuildTargetUnitTestResult(1, None)
257 result.failed_pkgs = [package_info.parse(p) for p in pkgs]
258 self.PatchObject(
259 test_service, "BuildTargetUnitTest", return_value=result
260 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600261
Alex Klein1699fab2022-09-08 08:46:06 -0600262 input_msg = self._GetInput(board="board")
263 output_msg = self._GetOutput()
Alex Kleina2e42c42019-04-17 16:13:19 -0600264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 rc = test_controller.BuildTargetUnitTest(
266 input_msg, output_msg, self.api_config
267 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600268
Alex Klein1699fab2022-09-08 08:46:06 -0600269 self.assertEqual(
270 controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE, rc
271 )
272 self.assertTrue(output_msg.failed_package_data)
Alex Kleina2e42c42019-04-17 16:13:19 -0600273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 failed_with_logs = []
275 for data in output_msg.failed_package_data:
276 failed_with_logs.append(
277 (data.name.category, data.name.package_name)
278 )
279 package = controller_util.deserialize_package_info(data.name)
280 self.assertEqual(data.log_path.path, new_logs[package.cpvr])
281 self.assertCountEqual(expected, failed_with_logs)
Lizzy Presland4feb2372022-01-20 05:16:30 +0000282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 def testOtherBuildScriptFailure(self):
284 """Test build script failure due to non-package emerge error."""
285 tempdir = osutils.TempDir(base_dir=self.tempdir)
286 self.PatchObject(osutils, "TempDir", return_value=tempdir)
Alex Kleina2e42c42019-04-17 16:13:19 -0600287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 result = test_service.BuildTargetUnitTestResult(1, None)
289 self.PatchObject(
290 test_service, "BuildTargetUnitTest", return_value=result
291 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600292
Alex Klein1699fab2022-09-08 08:46:06 -0600293 pkgs = ["foo/bar", "cat/pkg"]
294 blocklist = [package_info.SplitCPV(p, strict=False) for p in pkgs]
295 input_msg = self._GetInput(
296 board="board", empty_sysroot=True, blocklist=blocklist
297 )
298 output_msg = self._GetOutput()
Alex Kleina2e42c42019-04-17 16:13:19 -0600299
Alex Klein1699fab2022-09-08 08:46:06 -0600300 rc = test_controller.BuildTargetUnitTest(
301 input_msg, output_msg, self.api_config
302 )
Alex Kleina2e42c42019-04-17 16:13:19 -0600303
Alex Klein1699fab2022-09-08 08:46:06 -0600304 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
305 self.assertFalse(output_msg.failed_package_data)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 def testBuildTargetUnitTest(self):
308 """Test BuildTargetUnitTest successful call."""
309 pkgs = ["foo/bar", "cat/pkg"]
310 packages = [package_info.SplitCPV(p, strict=False) for p in pkgs]
311 input_msg = self._GetInput(board="board", packages=packages)
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 result = test_service.BuildTargetUnitTestResult(0, None)
314 self.PatchObject(
315 test_service, "BuildTargetUnitTest", return_value=result
316 )
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 response = self._GetOutput()
319 test_controller.BuildTargetUnitTest(
320 input_msg, response, self.api_config
321 )
322 self.assertFalse(response.failed_package_data)
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700323
Evan Hernandez4e388a52019-05-01 12:16:33 -0600324
Sean McAllister17eed8d2021-09-21 10:41:16 -0600325class DockerConstraintsTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600326 """Tests for Docker argument constraints."""
Sean McAllister17eed8d2021-09-21 10:41:16 -0600327
Alex Klein1699fab2022-09-08 08:46:06 -0600328 def assertValid(self, output):
329 return output is None
Sean McAllister17eed8d2021-09-21 10:41:16 -0600330
Alex Klein1699fab2022-09-08 08:46:06 -0600331 def assertInvalid(self, output):
332 return not self.assertValid(output)
Sean McAllister17eed8d2021-09-21 10:41:16 -0600333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 def testValidDockerTag(self):
335 """Check logic for validating docker tag format."""
336 # pylint: disable=protected-access
Sean McAllister17eed8d2021-09-21 10:41:16 -0600337
Alex Klein1699fab2022-09-08 08:46:06 -0600338 invalid_tags = [
339 ".invalid-tag",
340 "-invalid-tag",
341 "invalid-tag;",
342 "invalid" * 100,
343 ]
Sean McAllister17eed8d2021-09-21 10:41:16 -0600344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 for tag in invalid_tags:
346 self.assertInvalid(test_controller._ValidDockerTag(tag))
Sean McAllister17eed8d2021-09-21 10:41:16 -0600347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 valid_tags = [
349 "valid-tag",
350 "valid-tag-",
351 "valid.tag.",
352 ]
Sean McAllister17eed8d2021-09-21 10:41:16 -0600353
Alex Klein1699fab2022-09-08 08:46:06 -0600354 for tag in valid_tags:
355 self.assertValid(test_controller._ValidDockerTag(tag))
Sean McAllister17eed8d2021-09-21 10:41:16 -0600356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 def testValidDockerLabelKey(self):
358 """Check logic for validating docker label key format."""
359 # pylint: disable=protected-access
Sean McAllister17eed8d2021-09-21 10:41:16 -0600360
Alex Klein1699fab2022-09-08 08:46:06 -0600361 invalid_keys = [
362 "Invalid-keY",
363 "Invalid-key",
364 "invalid-keY",
365 "iNVALID-KEy",
366 "invalid_key",
367 "invalid-key;",
368 ]
Sean McAllister17eed8d2021-09-21 10:41:16 -0600369
Alex Klein1699fab2022-09-08 08:46:06 -0600370 for key in invalid_keys:
371 self.assertInvalid(test_controller._ValidDockerLabelKey(key))
Sean McAllister17eed8d2021-09-21 10:41:16 -0600372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 valid_keys = [
374 "chromeos.valid-key",
375 "chromeos.valid-key-2",
376 ]
Sean McAllister17eed8d2021-09-21 10:41:16 -0600377
Alex Klein1699fab2022-09-08 08:46:06 -0600378 for key in valid_keys:
379 self.assertValid(test_controller._ValidDockerLabelKey(key))
Sean McAllister17eed8d2021-09-21 10:41:16 -0600380
381
Alex Klein1699fab2022-09-08 08:46:06 -0600382class BuildTestServiceContainers(
383 cros_test_lib.RunCommandTempDirTestCase, api_config.ApiConfigMixin
384):
385 """Tests for the BuildTestServiceContainers function."""
C Shapiro91af1ce2021-06-17 12:42:09 -0500386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 def setUp(self):
388 self.request = test_pb2.BuildTestServiceContainersRequest(
389 chroot={"path": "/path/to/chroot"},
390 build_target={"name": "build_target"},
391 version="R93-14033.0.0",
392 )
C Shapiro91af1ce2021-06-17 12:42:09 -0500393
Alex Klein1699fab2022-09-08 08:46:06 -0600394 def testSuccess(self):
395 """Check passing case with mocked cros_build_lib.run."""
Sean McAllister3834fef2021-10-08 15:45:18 -0600396
Alex Klein1699fab2022-09-08 08:46:06 -0600397 def ContainerMetadata():
398 """Return mocked ContainerImageInfo proto"""
399 metadata = container_metadata_pb2.ContainerImageInfo()
400 metadata.repository.hostname = "gcr.io"
401 metadata.repository.project = "chromeos-bot"
402 metadata.name = "random-container-name"
403 metadata.digest = "09b730f8b6a862f9c2705cb3acf3554563325f5fca5c784bf5c98beb2e56f6db"
404 metadata.tags[:] = [
405 "staging-cq-amd64-generic.R96-1.2.3",
406 "8834106026340379089",
407 ]
408 return metadata
Sean McAllister3834fef2021-10-08 15:45:18 -0600409
Alex Klein1699fab2022-09-08 08:46:06 -0600410 def WriteContainerMetadata(path):
411 """Write json formatted metadata to the given file."""
412 osutils.WriteFile(
413 path,
414 json_format.MessageToJson(ContainerMetadata()),
415 )
Sean McAllister3834fef2021-10-08 15:45:18 -0600416
Alex Klein1699fab2022-09-08 08:46:06 -0600417 # Write out mocked container metadata to a temporary file.
418 output_path = os.path.join(self.tempdir, "metadata.jsonpb")
419 self.rc.SetDefaultCmdResult(
420 returncode=0,
421 side_effect=lambda *_, **__: WriteContainerMetadata(output_path),
422 )
Sean McAllister3834fef2021-10-08 15:45:18 -0600423
Alex Klein1699fab2022-09-08 08:46:06 -0600424 # Patch TempDir so that we always use this test's directory.
425 self.PatchObject(
426 osutils.TempDir, "__enter__", return_value=self.tempdir
427 )
C Shapiro91af1ce2021-06-17 12:42:09 -0500428
Alex Klein1699fab2022-09-08 08:46:06 -0600429 response = test_pb2.BuildTestServiceContainersResponse()
430 test_controller.BuildTestServiceContainers(
431 self.request, response, self.api_config
432 )
Sean McAllister3834fef2021-10-08 15:45:18 -0600433
Alex Klein1699fab2022-09-08 08:46:06 -0600434 self.assertTrue(self.rc.called)
435 for result in response.results:
436 self.assertEqual(result.WhichOneof("result"), "success")
437 self.assertEqual(result.success.image_info, ContainerMetadata())
C Shapiro91af1ce2021-06-17 12:42:09 -0500438
Alex Klein1699fab2022-09-08 08:46:06 -0600439 def testFailure(self):
440 """Check failure case with mocked cros_build_lib.run."""
441 patch = self.PatchObject(
442 cros_build_lib,
443 "run",
444 return_value=cros_build_lib.CompletedProcess(returncode=1),
445 )
C Shapiro91af1ce2021-06-17 12:42:09 -0500446
Alex Klein1699fab2022-09-08 08:46:06 -0600447 response = test_pb2.BuildTestServiceContainersResponse()
448 test_controller.BuildTestServiceContainers(
449 self.request, response, self.api_config
450 )
451 patch.assert_called()
452 for result in response.results:
453 self.assertEqual(result.WhichOneof("result"), "failure")
454 self.assertEqual(result.name, "Service Builder")
C Shapiro91af1ce2021-06-17 12:42:09 -0500455
Alex Klein4f215432022-05-23 10:41:14 -0600456
Alex Klein1699fab2022-09-08 08:46:06 -0600457class ChromiteUnitTestTest(
458 cros_test_lib.MockTestCase, api_config.ApiConfigMixin
459):
460 """Tests for the ChromiteInfoTest function."""
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 def setUp(self):
463 self.board = "board"
464 self.chroot_path = "/path/to/chroot"
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 def _GetInput(self, chroot_path=None):
467 """Helper to build an input message instance."""
468 proto = test_pb2.ChromiteUnitTestRequest(
469 chroot={"path": chroot_path},
470 )
471 return proto
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 def _GetOutput(self):
474 """Helper to get an empty output message instance."""
475 return test_pb2.ChromiteUnitTestResponse()
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700476
Alex Klein1699fab2022-09-08 08:46:06 -0600477 def testValidateOnly(self):
478 """Sanity check that a validate only call does not execute any logic."""
479 patch = self.PatchObject(cros_build_lib, "run")
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700480
Alex Klein1699fab2022-09-08 08:46:06 -0600481 input_msg = self._GetInput(chroot_path=self.chroot_path)
482 test_controller.ChromiteUnitTest(
483 input_msg, self._GetOutput(), self.validate_only_config
484 )
485 patch.assert_not_called()
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700486
Alex Klein1699fab2022-09-08 08:46:06 -0600487 def testMockError(self):
488 """Test mock error call does not execute any logic, returns error."""
489 patch = self.PatchObject(cros_build_lib, "run")
Michael Mortensen7a860eb2019-12-03 20:25:15 -0700490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 input_msg = self._GetInput(chroot_path=self.chroot_path)
492 rc = test_controller.ChromiteUnitTest(
493 input_msg, self._GetOutput(), self.mock_error_config
494 )
495 patch.assert_not_called()
496 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Michael Mortensen7a860eb2019-12-03 20:25:15 -0700497
Alex Klein1699fab2022-09-08 08:46:06 -0600498 def testMockCall(self):
499 """Test mock call does not execute any logic, returns success."""
500 patch = self.PatchObject(cros_build_lib, "run")
Michael Mortensen7a860eb2019-12-03 20:25:15 -0700501
Alex Klein1699fab2022-09-08 08:46:06 -0600502 input_msg = self._GetInput(chroot_path=self.chroot_path)
503 rc = test_controller.ChromiteUnitTest(
504 input_msg, self._GetOutput(), self.mock_call_config
505 )
506 patch.assert_not_called()
507 self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
Michael Mortensen7a860eb2019-12-03 20:25:15 -0700508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 def testChromiteUnitTest(self):
510 """Call ChromiteUnitTest with mocked cros_build_lib.run."""
511 request = self._GetInput(chroot_path=self.chroot_path)
512 patch = self.PatchObject(
513 cros_build_lib,
514 "run",
515 return_value=cros_build_lib.CompletedProcess(returncode=0),
516 )
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700517
Alex Klein1699fab2022-09-08 08:46:06 -0600518 test_controller.ChromiteUnitTest(
519 request, self._GetOutput(), self.api_config
520 )
521 patch.assert_called_once()
Michael Mortensen8ca4d3b2019-11-27 09:35:22 -0700522
523
Alex Klein1699fab2022-09-08 08:46:06 -0600524class CrosSigningTestTest(
525 cros_test_lib.RunCommandTestCase, api_config.ApiConfigMixin
526):
527 """CrosSigningTest tests."""
Alex Klein4bc8f4f2019-08-16 14:53:30 -0600528
Alex Klein1699fab2022-09-08 08:46:06 -0600529 def setUp(self):
530 self.chroot_path = "/path/to/chroot"
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700531
Alex Klein1699fab2022-09-08 08:46:06 -0600532 def _GetInput(self, chroot_path=None):
533 """Helper to build an input message instance."""
534 proto = test_pb2.CrosSigningTestRequest(
535 chroot={"path": chroot_path},
536 )
537 return proto
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 def _GetOutput(self):
540 """Helper to get an empty output message instance."""
541 return test_pb2.CrosSigningTestResponse()
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700542
Alex Klein1699fab2022-09-08 08:46:06 -0600543 def testValidateOnly(self):
544 """Sanity check that a validate only call does not execute any logic."""
545 test_controller.CrosSigningTest(None, None, self.validate_only_config)
546 self.assertFalse(self.rc.call_count)
Alex Klein4bc8f4f2019-08-16 14:53:30 -0600547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 def testMockCall(self):
549 """Test mock call does not execute any logic, returns success."""
550 rc = test_controller.CrosSigningTest(None, None, self.mock_call_config)
551 self.assertFalse(self.rc.call_count)
552 self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
Michael Mortensen7a7646d2019-12-12 15:36:14 -0700553
Alex Klein1699fab2022-09-08 08:46:06 -0600554 def testCrosSigningTest(self):
555 """Call CrosSigningTest with mocked cros_build_lib.run."""
556 request = self._GetInput(chroot_path=self.chroot_path)
557 patch = self.PatchObject(
558 cros_build_lib,
559 "run",
560 return_value=cros_build_lib.CompletedProcess(returncode=0),
561 )
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700562
Alex Klein1699fab2022-09-08 08:46:06 -0600563 test_controller.CrosSigningTest(
564 request, self._GetOutput(), self.api_config
565 )
566 patch.assert_called_once()
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700567
Alex Klein4bc8f4f2019-08-16 14:53:30 -0600568
Alex Klein1699fab2022-09-08 08:46:06 -0600569class SimpleChromeWorkflowTestTest(
570 cros_test_lib.MockTestCase, api_config.ApiConfigMixin
571):
572 """Test the SimpleChromeWorkflowTest endpoint."""
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600573
Alex Klein1699fab2022-09-08 08:46:06 -0600574 @staticmethod
575 def _Output():
576 return test_pb2.SimpleChromeWorkflowTestResponse()
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 def _Input(
579 self,
580 sysroot_path=None,
Alex Klein4f215432022-05-23 10:41:14 -0600581 build_target=None,
Alex Klein1699fab2022-09-08 08:46:06 -0600582 chrome_root=None,
583 goma_config=None,
584 ):
585 proto = test_pb2.SimpleChromeWorkflowTestRequest()
586 if sysroot_path:
587 proto.sysroot.path = sysroot_path
588 if build_target:
589 proto.sysroot.build_target.name = build_target
590 if chrome_root:
591 proto.chrome_root = chrome_root
592 if goma_config:
593 proto.goma_config = goma_config
594 return proto
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600595
Alex Klein1699fab2022-09-08 08:46:06 -0600596 def setUp(self):
597 self.chrome_path = "path/to/chrome"
598 self.sysroot_dir = "build/board"
599 self.build_target = "amd64"
600 self.mock_simple_chrome_workflow_test = self.PatchObject(
601 test_service, "SimpleChromeWorkflowTest"
602 )
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600603
Alex Klein1699fab2022-09-08 08:46:06 -0600604 def testMissingBuildTarget(self):
605 """Test SimpleChromeWorkflowTest dies when build_target not set."""
606 input_proto = self._Input(
607 build_target=None,
608 sysroot_path="/sysroot/dir",
609 chrome_root="/chrome/path",
610 )
611 with self.assertRaises(cros_build_lib.DieSystemExit):
612 test_controller.SimpleChromeWorkflowTest(
613 input_proto, None, self.api_config
614 )
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600615
Alex Klein1699fab2022-09-08 08:46:06 -0600616 def testMissingSysrootPath(self):
617 """Test SimpleChromeWorkflowTest dies when build_target not set."""
618 input_proto = self._Input(
619 build_target="board", sysroot_path=None, chrome_root="/chrome/path"
620 )
621 with self.assertRaises(cros_build_lib.DieSystemExit):
622 test_controller.SimpleChromeWorkflowTest(
623 input_proto, None, self.api_config
624 )
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600625
Alex Klein1699fab2022-09-08 08:46:06 -0600626 def testMissingChromeRoot(self):
627 """Test SimpleChromeWorkflowTest dies when build_target not set."""
628 input_proto = self._Input(
629 build_target="board", sysroot_path="/sysroot/dir", chrome_root=None
630 )
631 with self.assertRaises(cros_build_lib.DieSystemExit):
632 test_controller.SimpleChromeWorkflowTest(
633 input_proto, None, self.api_config
634 )
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600635
Alex Klein1699fab2022-09-08 08:46:06 -0600636 def testSimpleChromeWorkflowTest(self):
637 """Call SimpleChromeWorkflowTest with valid args and temp dir."""
638 request = self._Input(
639 sysroot_path="sysroot_path",
640 build_target="board",
641 chrome_root="/path/to/chrome",
642 )
643 response = self._Output()
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600644
Alex Klein1699fab2022-09-08 08:46:06 -0600645 test_controller.SimpleChromeWorkflowTest(
646 request, response, self.api_config
647 )
648 self.mock_simple_chrome_workflow_test.assert_called()
Michael Mortensen7a7646d2019-12-12 15:36:14 -0700649
Alex Klein1699fab2022-09-08 08:46:06 -0600650 def testValidateOnly(self):
651 request = self._Input(
652 sysroot_path="sysroot_path",
653 build_target="board",
654 chrome_root="/path/to/chrome",
655 )
656 test_controller.SimpleChromeWorkflowTest(
657 request, self._Output(), self.validate_only_config
658 )
659 self.mock_simple_chrome_workflow_test.assert_not_called()
660
661 def testMockCall(self):
662 """Test mock call does not execute any logic, returns success."""
663 patch = self.mock_simple_chrome_workflow_test = self.PatchObject(
664 test_service, "SimpleChromeWorkflowTest"
665 )
666
667 request = self._Input(
668 sysroot_path="sysroot_path",
669 build_target="board",
670 chrome_root="/path/to/chrome",
671 )
672 rc = test_controller.SimpleChromeWorkflowTest(
673 request, self._Output(), self.mock_call_config
674 )
675 patch.assert_not_called()
676 self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
Michael Mortensen7a7646d2019-12-12 15:36:14 -0700677
Michael Mortensenc28d6f12019-10-03 13:34:51 -0600678
Alex Klein231d2da2019-07-22 16:44:45 -0600679class VmTestTest(cros_test_lib.RunCommandTestCase, api_config.ApiConfigMixin):
Alex Klein1699fab2022-09-08 08:46:06 -0600680 """Test the VmTest endpoint."""
Evan Hernandez4e388a52019-05-01 12:16:33 -0600681
Alex Klein1699fab2022-09-08 08:46:06 -0600682 def _GetInput(self, **kwargs):
683 values = dict(
684 build_target=common_pb2.BuildTarget(name="target"),
685 vm_path=common_pb2.Path(
686 path="/path/to/image.bin", location=common_pb2.Path.INSIDE
687 ),
688 test_harness=test_pb2.VmTestRequest.TAST,
689 vm_tests=[test_pb2.VmTestRequest.VmTest(pattern="suite")],
690 ssh_options=test_pb2.VmTestRequest.SshOptions(
691 port=1234,
692 private_key_path={
693 "path": "/path/to/id_rsa",
694 "location": common_pb2.Path.INSIDE,
695 },
696 ),
697 )
698 values.update(kwargs)
699 return test_pb2.VmTestRequest(**values)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600700
Alex Klein1699fab2022-09-08 08:46:06 -0600701 def _Output(self):
702 return test_pb2.VmTestResponse()
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700703
Alex Klein1699fab2022-09-08 08:46:06 -0600704 def testValidateOnly(self):
705 """Sanity check that a validate only call does not execute any logic."""
706 test_controller.VmTest(
707 self._GetInput(), None, self.validate_only_config
708 )
709 self.assertEqual(0, self.rc.call_count)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600710
Alex Klein1699fab2022-09-08 08:46:06 -0600711 def testMockCall(self):
712 """Test mock call does not execute any logic."""
713 patch = self.PatchObject(cros_build_lib, "run")
Michael Mortensen7a7646d2019-12-12 15:36:14 -0700714
Alex Klein1699fab2022-09-08 08:46:06 -0600715 request = self._GetInput()
716 response = self._Output()
717 # VmTest does not return a value, checking mocked value is flagged by lint.
718 test_controller.VmTest(request, response, self.mock_call_config)
719 patch.assert_not_called()
Michael Mortensen7a7646d2019-12-12 15:36:14 -0700720
Alex Klein1699fab2022-09-08 08:46:06 -0600721 def testTastAllOptions(self):
722 """Test VmTest for Tast with all options set."""
723 test_controller.VmTest(self._GetInput(), None, self.api_config)
724 self.assertCommandContains(
725 [
726 "cros_run_test",
727 "--debug",
728 "--no-display",
729 "--copy-on-write",
730 "--board",
731 "target",
732 "--image-path",
733 "/path/to/image.bin",
734 "--tast",
735 "suite",
736 "--ssh-port",
737 "1234",
738 "--private-key",
739 "/path/to/id_rsa",
740 ]
741 )
Evan Hernandez4e388a52019-05-01 12:16:33 -0600742
Alex Klein1699fab2022-09-08 08:46:06 -0600743 def testAutotestAllOptions(self):
744 """Test VmTest for Autotest with all options set."""
745 input_proto = self._GetInput(
746 test_harness=test_pb2.VmTestRequest.AUTOTEST
747 )
748 test_controller.VmTest(input_proto, None, self.api_config)
749 self.assertCommandContains(
750 [
751 "cros_run_test",
752 "--debug",
753 "--no-display",
754 "--copy-on-write",
755 "--board",
756 "target",
757 "--image-path",
758 "/path/to/image.bin",
759 "--autotest",
760 "suite",
761 "--ssh-port",
762 "1234",
763 "--private-key",
764 "/path/to/id_rsa",
765 "--test_that-args=--allow-chrome-crashes",
766 ]
767 )
Evan Hernandez4e388a52019-05-01 12:16:33 -0600768
Alex Klein1699fab2022-09-08 08:46:06 -0600769 def testMissingBuildTarget(self):
770 """Test VmTest dies when build_target not set."""
771 input_proto = self._GetInput(build_target=None)
772 with self.assertRaises(cros_build_lib.DieSystemExit):
773 test_controller.VmTest(input_proto, None, self.api_config)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600774
Alex Klein1699fab2022-09-08 08:46:06 -0600775 def testMissingVmImage(self):
776 """Test VmTest dies when vm_image not set."""
777 input_proto = self._GetInput(vm_path=None)
778 with self.assertRaises(cros_build_lib.DieSystemExit):
779 test_controller.VmTest(input_proto, None, self.api_config)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600780
Alex Klein1699fab2022-09-08 08:46:06 -0600781 def testMissingTestHarness(self):
782 """Test VmTest dies when test_harness not specified."""
783 input_proto = self._GetInput(
784 test_harness=test_pb2.VmTestRequest.UNSPECIFIED
785 )
786 with self.assertRaises(cros_build_lib.DieSystemExit):
787 test_controller.VmTest(input_proto, None, self.api_config)
Evan Hernandez4e388a52019-05-01 12:16:33 -0600788
Alex Klein1699fab2022-09-08 08:46:06 -0600789 def testMissingVmTests(self):
790 """Test VmTest dies when vm_tests not set."""
791 input_proto = self._GetInput(vm_tests=[])
792 with self.assertRaises(cros_build_lib.DieSystemExit):
793 test_controller.VmTest(input_proto, None, self.api_config)
Evan Hernandezdc3f0bb2019-06-06 12:46:52 -0600794
Alex Klein1699fab2022-09-08 08:46:06 -0600795 def testVmTest(self):
796 """Call VmTest with valid args and temp dir."""
797 request = self._GetInput()
798 response = self._Output()
799 patch = self.PatchObject(
800 cros_build_lib,
801 "run",
802 return_value=cros_build_lib.CompletedProcess(returncode=0),
803 )
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700804
Alex Klein1699fab2022-09-08 08:46:06 -0600805 test_controller.VmTest(request, response, self.api_config)
806 patch.assert_called()
Michael Mortensen82cd62d2019-12-01 14:58:54 -0700807
Evan Hernandezdc3f0bb2019-06-06 12:46:52 -0600808
David Wellingc1433c22021-06-25 16:29:48 +0000809class GetArtifactsTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600810 """Test GetArtifacts."""
David Wellingc1433c22021-06-25 16:29:48 +0000811
Alex Klein1699fab2022-09-08 08:46:06 -0600812 CODE_COVERAGE_LLVM_ARTIFACT_TYPE = (
813 common_pb2.ArtifactsByService.Test.ArtifactType.CODE_COVERAGE_LLVM_JSON
814 )
David Wellingc1433c22021-06-25 16:29:48 +0000815
Alex Klein1699fab2022-09-08 08:46:06 -0600816 def setUp(self):
817 """Set up the class for tests."""
818 chroot_dir = os.path.join(self.tempdir, "chroot")
819 osutils.SafeMakedirs(chroot_dir)
820 osutils.SafeMakedirs(os.path.join(chroot_dir, "tmp"))
821 self.chroot = chroot_lib.Chroot(chroot_dir)
David Wellingc1433c22021-06-25 16:29:48 +0000822
Alex Klein1699fab2022-09-08 08:46:06 -0600823 sysroot_path = os.path.join(chroot_dir, "build", "board")
824 osutils.SafeMakedirs(sysroot_path)
825 self.sysroot = sysroot_lib.Sysroot(sysroot_path)
David Wellingc1433c22021-06-25 16:29:48 +0000826
Alex Klein1699fab2022-09-08 08:46:06 -0600827 self.build_target = build_target_lib.BuildTarget("board")
Jack Neusc9707c32021-07-23 21:48:54 +0000828
Alex Klein1699fab2022-09-08 08:46:06 -0600829 def testReturnsEmptyListWhenNoOutputArtifactsProvided(self):
830 """Test empty list is returned when there are no output_artifacts."""
831 result = test_controller.GetArtifacts(
832 common_pb2.ArtifactsByService.Test(output_artifacts=[]),
833 self.chroot,
834 self.sysroot,
835 self.build_target,
836 self.tempdir,
837 )
David Wellingc1433c22021-06-25 16:29:48 +0000838
Alex Klein1699fab2022-09-08 08:46:06 -0600839 self.assertEqual(len(result), 0)
David Wellingc1433c22021-06-25 16:29:48 +0000840
Alex Klein1699fab2022-09-08 08:46:06 -0600841 def testShouldCallBundleCodeCoverageLlvmJsonForEachValidArtifact(self):
842 """Test BundleCodeCoverageLlvmJson is called on each valid artifact."""
843 BundleCodeCoverageLlvmJson_mock = self.PatchObject(
844 test_service, "BundleCodeCoverageLlvmJson", return_value="test"
845 )
846
847 test_controller.GetArtifacts(
848 common_pb2.ArtifactsByService.Test(
849 output_artifacts=[
850 # Valid
851 common_pb2.ArtifactsByService.Test.ArtifactInfo(
852 artifact_types=[self.CODE_COVERAGE_LLVM_ARTIFACT_TYPE]
853 ),
854 # Invalid
855 common_pb2.ArtifactsByService.Test.ArtifactInfo(
856 artifact_types=[
857 common_pb2.ArtifactsByService.Test.ArtifactType.UNIT_TESTS
858 ]
859 ),
860 ]
861 ),
862 self.chroot,
863 self.sysroot,
864 self.build_target,
865 self.tempdir,
866 )
867
868 BundleCodeCoverageLlvmJson_mock.assert_called_once()
869
870 def testShouldReturnValidResult(self):
871 """Test result contains paths and code_coverage_llvm_json type."""
Sean McAllister17eed8d2021-09-21 10:41:16 -0600872 self.PatchObject(
Alex Klein1699fab2022-09-08 08:46:06 -0600873 test_service, "BundleCodeCoverageLlvmJson", return_value="test"
874 )
David Wellingc1433c22021-06-25 16:29:48 +0000875
Alex Klein1699fab2022-09-08 08:46:06 -0600876 result = test_controller.GetArtifacts(
877 common_pb2.ArtifactsByService.Test(
878 output_artifacts=[
879 # Valid
880 common_pb2.ArtifactsByService.Test.ArtifactInfo(
881 artifact_types=[self.CODE_COVERAGE_LLVM_ARTIFACT_TYPE]
882 ),
883 ]
884 ),
885 self.chroot,
886 self.sysroot,
887 self.build_target,
888 self.tempdir,
889 )
David Wellingc1433c22021-06-25 16:29:48 +0000890
Alex Klein1699fab2022-09-08 08:46:06 -0600891 self.assertEqual(result[0]["paths"], ["test"])
892 self.assertEqual(
893 result[0]["type"], self.CODE_COVERAGE_LLVM_ARTIFACT_TYPE
894 )