blob: 3bda2e47128982a21dd5b61f46efa9df1fd4b074 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2020 The ChromiumOS Authors
George Burgess IV853d65b2020-02-25 13:13:15 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unit tests for tricium_clang_tidy.py."""
6
7import io
8import json
9import multiprocessing
10import os
11from pathlib import Path
12import subprocess
George Burgess IV853d65b2020-02-25 13:13:15 -080013import tempfile
14from typing import NamedTuple
15from unittest import mock
16
17from chromite.lib import cros_test_lib
18from chromite.lib import osutils
19from chromite.scripts import tricium_clang_tidy
20
George Burgess IV853d65b2020-02-25 13:13:15 -080021
22class Replacement(NamedTuple):
Alex Klein1699fab2022-09-08 08:46:06 -060023 """A YAML `tricium_clang_tidy.TidyReplacement`.
George Burgess IV853d65b2020-02-25 13:13:15 -080024
Trent Aptedc20bb6d2023-05-10 15:00:03 +100025 The data contained in YAML is slightly different than what
26 `TidyReplacement`s
Alex Klein1699fab2022-09-08 08:46:06 -060027 carry.
28 """
29
30 file_path: str
31 text: str
32 offset: int
33 length: int
George Burgess IV853d65b2020-02-25 13:13:15 -080034
35
36class Note(NamedTuple):
Alex Klein1699fab2022-09-08 08:46:06 -060037 """A clang-tidy `note` from the YAML file."""
38
39 message: str
40 file_path: str
41 file_offset: int
George Burgess IV853d65b2020-02-25 13:13:15 -080042
43
Alex Klein1699fab2022-09-08 08:46:06 -060044def default_tidy_diagnostic(
45 file_path="/tidy/file.c",
46 line_number=1,
47 diag_name="${diag_name}",
48 message="${message}",
49 replacements=(),
50 expansion_locs=(),
51):
52 """Creates a TidyDiagnostic with reasonable defaults.
George Burgess IV853d65b2020-02-25 13:13:15 -080053
Alex Klein1699fab2022-09-08 08:46:06 -060054 Defaults here and yaml_diagnostic are generally intended to match where
55 possible.
56 """
57 return tricium_clang_tidy.TidyDiagnostic(
58 file_path=file_path,
59 line_number=line_number,
60 diag_name=diag_name,
61 message=message,
62 replacements=replacements,
63 expansion_locs=expansion_locs,
64 )
George Burgess IV853d65b2020-02-25 13:13:15 -080065
66
Alex Klein1699fab2022-09-08 08:46:06 -060067def yaml_diagnostic(
68 name="${diag_name}",
69 message="${message}",
70 file_path="/tidy/file.c",
71 file_offset=1,
72 replacements=(),
73 notes=(),
74):
75 """Creates a diagnostic serializable as YAML with reasonable defaults."""
76 result = {
77 "DiagnosticName": name,
78 "DiagnosticMessage": {
79 "Message": message,
80 "FilePath": file_path,
81 "FileOffset": file_offset,
82 },
83 }
George Burgess IV853d65b2020-02-25 13:13:15 -080084
Alex Klein1699fab2022-09-08 08:46:06 -060085 if replacements:
86 result["DiagnosticMessage"]["Replacements"] = [
87 {
88 "FilePath": x.file_path,
89 "Offset": x.offset,
90 "Length": x.length,
91 "ReplacementText": x.text,
92 }
93 for x in replacements
94 ]
George Burgess IV853d65b2020-02-25 13:13:15 -080095
Alex Klein1699fab2022-09-08 08:46:06 -060096 if notes:
97 result["Notes"] = [
98 {
99 "Message": x.message,
100 "FilePath": x.file_path,
101 "FileOffset": x.file_offset,
102 }
103 for x in notes
104 ]
George Burgess IV853d65b2020-02-25 13:13:15 -0800105
Alex Klein1699fab2022-09-08 08:46:06 -0600106 return result
George Burgess IV853d65b2020-02-25 13:13:15 -0800107
108
109def mocked_nop_realpath(f):
Alex Klein1699fab2022-09-08 08:46:06 -0600110 """Mocks os.path.realpath to just return its argument."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 @mock.patch.object(os.path, "realpath")
113 @mock.patch.object(Path, "resolve")
114 def inner(self, replace_mock, realpath_mock, *args, **kwargs):
115 """Mocker for realpath."""
116 identity = lambda x: x
117 realpath_mock.side_effect = identity
118 replace_mock.side_effect = identity
119 return f(self, *args, **kwargs)
George Burgess IV853d65b2020-02-25 13:13:15 -0800120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 return inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800122
123
124def mocked_readonly_open(contents=None, default=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600125 """Mocks out open() so it always returns things from |contents|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 Writing to open'ed files is not supported.
George Burgess IV853d65b2020-02-25 13:13:15 -0800128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000130 contents: a |dict| mapping |file_path| => file_contents.
131 default: a default string to return if the given |file_path| doesn't
132 exist in |contents|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000135 |contents[file_path]| if it exists; otherwise, |default|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800136
Alex Klein1699fab2022-09-08 08:46:06 -0600137 Raises:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000138 If |default| is None and |contents[file_path]| does not exist, this will
139 raise a |ValueError|.
Alex Klein1699fab2022-09-08 08:46:06 -0600140 """
George Burgess IV853d65b2020-02-25 13:13:15 -0800141
Alex Klein1699fab2022-09-08 08:46:06 -0600142 if contents is None:
143 contents = {}
George Burgess IV853d65b2020-02-25 13:13:15 -0800144
Alex Klein1699fab2022-09-08 08:46:06 -0600145 def inner(f):
146 """mocked_open impl."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800147
Alex Klein1699fab2022-09-08 08:46:06 -0600148 @mock.mock_open()
149 def inner_inner(self, open_mock, *args, **kwargs):
150 """the impl of mocked_readonly_open's impl!"""
George Burgess IV853d65b2020-02-25 13:13:15 -0800151
Alex Klein1699fab2022-09-08 08:46:06 -0600152 def get_data(file_path, mode="r", encoding=None):
153 """the impl of the impl of mocked_readonly_open's impl!!"""
154 data = contents.get(file_path, default)
155 if data is None:
156 raise ValueError(
157 "No %r file was found; options were %r"
158 % (file_path, sorted(contents.keys()))
159 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800160
Alex Klein1699fab2022-09-08 08:46:06 -0600161 assert mode == "r", f"File mode {mode} isn't supported."
162 if encoding is None:
163 return io.BytesIO(data)
164 return io.StringIO(data)
George Burgess IV853d65b2020-02-25 13:13:15 -0800165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 open_mock.side_effect = get_data
George Burgess IV853d65b2020-02-25 13:13:15 -0800167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 def get_data_stream(file_path):
169 return io.StringIO(get_data(file_path))
George Burgess IV853d65b2020-02-25 13:13:15 -0800170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 open_mock.side_effect = get_data_stream
172 return f(self, *args, **kwargs)
George Burgess IV853d65b2020-02-25 13:13:15 -0800173
Alex Klein1699fab2022-09-08 08:46:06 -0600174 return inner_inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800175
Alex Klein1699fab2022-09-08 08:46:06 -0600176 return inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800177
178
179class TriciumClangTidyTests(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600180 """Various tests for tricium support."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 def test_tidy_diagnostic_path_normalization(self):
183 expanded_from = tricium_clang_tidy.TidyExpandedFrom(
184 file_path=Path("/old2/foo"),
185 line_number=2,
186 )
187 diag = default_tidy_diagnostic(
188 file_path=Path("/old/foo"),
189 expansion_locs=(expanded_from,),
190 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800191
Alex Klein1699fab2022-09-08 08:46:06 -0600192 normalized = diag.normalize_paths_to("/new")
193 self.assertEqual(
194 normalized,
195 diag._replace(
196 file_path=Path("../old/foo"),
197 expansion_locs=(
198 expanded_from._replace(file_path=Path("../old2/foo")),
199 ),
George Burgess IV853d65b2020-02-25 13:13:15 -0800200 ),
Alex Klein1699fab2022-09-08 08:46:06 -0600201 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 def test_line_offest_map_works(self):
204 # (input_char, line_number_of_char, line_offset_of_char)
205 line_offset_pairs = [
206 ("a", 1, 0),
207 ("b", 1, 1),
208 ("\n", 1, 2),
209 ("c", 2, 0),
210 ("\n", 2, 1),
211 ("\n", 3, 0),
212 ("d", 4, 0),
213 ("", 4, 1),
214 ("", 4, 2),
215 ]
216 text = tricium_clang_tidy.LineOffsetMap.for_text(
217 "".join(x for x, _, _ in line_offset_pairs)
218 )
219 for offset, (_, line_number, line_offset) in enumerate(
220 line_offset_pairs
221 ):
222 self.assertEqual(text.get_line_number(offset), line_number)
223 self.assertEqual(text.get_line_offset(offset), line_offset)
224
225 def test_package_ebuild_resolution(self):
226 run_mock = self.StartPatcher(cros_test_lib.RunCommandMock())
227 run_mock.SetDefaultCmdResult(stdout="${package1_ebuild}\n")
228 ebuilds = tricium_clang_tidy.resolve_package_ebuilds(
229 "${board}",
230 [
231 "package1",
232 "package2.ebuild",
George Burgess IV853d65b2020-02-25 13:13:15 -0800233 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600234 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800235
Alex Klein1699fab2022-09-08 08:46:06 -0600236 run_mock.assertCommandContains(
237 ["equery-${board}", "w", "package1"],
238 check=True,
239 stdout=subprocess.PIPE,
240 encoding="utf-8",
241 )
242 self.assertEqual(ebuilds, ["${package1_ebuild}", "package2.ebuild"])
243
244 @mocked_readonly_open(default="")
245 def test_parse_tidy_invocation_returns_exception_on_error(
246 self, read_file_mock
247 ):
248 oh_no = ValueError("${oh_no}!")
249 read_file_mock.side_effect = oh_no
250 result = tricium_clang_tidy.parse_tidy_invocation(
251 Path("/some/file/that/doesnt/exist.json")
252 )
253 self.assertIn(str(oh_no), str(result))
254
255 @mocked_readonly_open(
256 {
257 "/file/path.json": json.dumps(
258 {
259 "exit_code": 1,
260 "executable": "${clang_tidy}",
261 "args": ["foo", "bar"],
262 "lint_target": "${target}",
263 "stdstreams": "brrrrrrr",
264 "wd": "/path/to/wd",
265 }
266 ),
267 # |yaml.dumps| doesn't exist, but json parses cleanly as yaml, so...
268 "/file/path.yaml": json.dumps(
269 {
270 "Diagnostics": [
271 yaml_diagnostic(
272 name="some-diag",
273 message="${message}",
274 file_path="",
275 ),
276 ]
277 }
278 ),
279 }
280 )
281 def test_parse_tidy_invocation_functions_on_success(self):
282 result = tricium_clang_tidy.parse_tidy_invocation("/file/path.json")
283 # If we got an |Exception|, print it out.
284 self.assertNotIsInstance(result, tricium_clang_tidy.Error)
285 meta, info = result
286 self.assertEqual(
287 meta,
288 tricium_clang_tidy.InvocationMetadata(
289 exit_code=1,
290 invocation=["${clang_tidy}", "foo", "bar"],
291 lint_target="${target}",
292 stdstreams="brrrrrrr",
293 wd="/path/to/wd",
294 ),
295 )
296
297 self.assertEqual(
298 info,
299 [
300 default_tidy_diagnostic(
301 diag_name="some-diag",
302 message="${message}",
303 file_path="",
304 ),
George Burgess IV853d65b2020-02-25 13:13:15 -0800305 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600306 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 @mocked_nop_realpath
309 @mocked_readonly_open(default="")
310 def test_parse_fixes_file_absolutizes_paths(self):
311 results = tricium_clang_tidy.parse_tidy_fixes_file(
312 "/tidy",
313 {
314 "Diagnostics": [
315 yaml_diagnostic(file_path="foo.c"),
316 yaml_diagnostic(file_path="/tidy/bar.c"),
317 yaml_diagnostic(file_path=""),
George Burgess IV853d65b2020-02-25 13:13:15 -0800318 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600319 },
320 )
321 file_paths = [x.file_path for x in results]
322 self.assertEqual(file_paths, ["/tidy/foo.c", "/tidy/bar.c", ""])
George Burgess IV853d65b2020-02-25 13:13:15 -0800323
Alex Klein1699fab2022-09-08 08:46:06 -0600324 @mocked_nop_realpath
325 @mocked_readonly_open(
326 {
327 "/tidy/foo.c": "",
328 "/tidy/foo.h": "a\n\n",
329 }
330 )
331 def test_parse_fixes_file_interprets_offsets_correctly(self):
332 results = tricium_clang_tidy.parse_tidy_fixes_file(
333 "/tidy",
334 {
335 "Diagnostics": [
336 yaml_diagnostic(file_path="/tidy/foo.c", file_offset=1),
337 yaml_diagnostic(file_path="/tidy/foo.c", file_offset=2),
338 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=1),
339 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=2),
340 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=3),
George Burgess IV853d65b2020-02-25 13:13:15 -0800341 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600342 },
343 )
344 file_locations = [(x.file_path, x.line_number) for x in results]
345 self.assertEqual(
346 file_locations,
347 [
348 ("/tidy/foo.c", 1),
349 ("/tidy/foo.c", 1),
350 ("/tidy/foo.h", 1),
351 ("/tidy/foo.h", 2),
352 ("/tidy/foo.h", 3),
353 ],
354 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800355
Alex Klein1699fab2022-09-08 08:46:06 -0600356 @mocked_nop_realpath
357 @mocked_readonly_open({"/tidy/foo.c": "a \n\n"})
358 def test_parse_fixes_file_handles_replacements(self):
359 results = list(
360 tricium_clang_tidy.parse_tidy_fixes_file(
361 "/tidy",
362 {
363 "Diagnostics": [
364 yaml_diagnostic(
365 file_path="/tidy/foo.c",
366 file_offset=1,
367 replacements=[
368 Replacement(
369 file_path="foo.c",
370 text="whee",
371 offset=2,
372 length=2,
373 ),
374 ],
375 ),
376 ],
377 },
378 )
379 )
380 self.assertEqual(len(results), 1, results)
381 self.assertEqual(
382 results[0].replacements,
383 (
384 tricium_clang_tidy.TidyReplacement(
385 new_text="whee",
386 start_line=1,
387 end_line=3,
388 start_char=2,
389 end_char=0,
390 ),
391 ),
392 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800393
Alex Klein1699fab2022-09-08 08:46:06 -0600394 @mocked_nop_realpath
395 @mocked_readonly_open({"/whee.c": "", "/whee.h": "\n\n"})
396 def test_parse_fixes_file_handles_macro_expansions(self):
397 results = list(
398 tricium_clang_tidy.parse_tidy_fixes_file(
399 "/tidy",
400 {
401 "Diagnostics": [
402 yaml_diagnostic(
403 file_path="/whee.c",
404 file_offset=1,
405 notes=[
406 Note(
407 message="not relevant",
408 file_path="/whee.c",
409 file_offset=1,
410 ),
411 Note(
412 message='expanded from macro "Foo"',
413 file_path="/whee.h",
414 file_offset=9,
415 ),
416 ],
417 ),
418 ],
419 },
420 )
421 )
422 self.assertEqual(len(results), 1, results)
423 self.assertEqual(
424 results[0].expansion_locs,
425 (
426 tricium_clang_tidy.TidyExpandedFrom(
427 file_path="/whee.h",
428 line_number=3,
429 ),
430 ),
431 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800432
Alex Klein1699fab2022-09-08 08:46:06 -0600433 @mock.patch.object(Path, "glob")
434 @mock.patch.object(tricium_clang_tidy, "parse_tidy_invocation")
435 def test_collect_lints_functions(self, parse_invocation_mock, glob_mock):
436 glob_mock.return_value = ("/lint/foo.json", "/lint/bar.json")
George Burgess IV853d65b2020-02-25 13:13:15 -0800437
Alex Klein1699fab2022-09-08 08:46:06 -0600438 diag_1 = default_tidy_diagnostic()
439 diag_2 = diag_1._replace(line_number=diag_1.line_number + 1)
440 diag_3 = diag_2._replace(line_number=diag_2.line_number + 1)
George Burgess IV853d65b2020-02-25 13:13:15 -0800441
Alex Klein1699fab2022-09-08 08:46:06 -0600442 # Because we want to test unique'ing, ensure these aren't equal.
443 all_diags = [diag_1, diag_2, diag_3]
444 self.assertEqual(sorted(all_diags), sorted(set(all_diags)))
George Burgess IV853d65b2020-02-25 13:13:15 -0800445
Alex Klein1699fab2022-09-08 08:46:06 -0600446 per_file_lints = {
447 "/lint/foo.json": {diag_1, diag_2},
448 "/lint/bar.json": {diag_2, diag_3},
449 }
George Burgess IV853d65b2020-02-25 13:13:15 -0800450
Alex Klein1699fab2022-09-08 08:46:06 -0600451 def parse_invocation_side_effect(json_file):
452 self.assertIn(json_file, per_file_lints)
453 meta = mock.Mock()
454 meta.exit_code = 0
455 return meta, per_file_lints[json_file]
George Burgess IV853d65b2020-02-25 13:13:15 -0800456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 parse_invocation_mock.side_effect = parse_invocation_side_effect
George Burgess IV853d65b2020-02-25 13:13:15 -0800458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 with multiprocessing.pool.ThreadPool(1) as yaml_pool:
460 lints = tricium_clang_tidy.collect_lints(Path("/lint"), yaml_pool)
George Burgess IV853d65b2020-02-25 13:13:15 -0800461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 self.assertEqual(set(all_diags), lints)
George Burgess IV853d65b2020-02-25 13:13:15 -0800463
Alex Klein1699fab2022-09-08 08:46:06 -0600464 def test_filter_tidy_lints_filters_nothing_by_default(self):
465 basis = default_tidy_diagnostic()
466 diag2 = default_tidy_diagnostic(line_number=basis.line_number + 1)
467 diags = [basis, diag2]
468 diags.sort()
George Burgess IV853d65b2020-02-25 13:13:15 -0800469
Alex Klein1699fab2022-09-08 08:46:06 -0600470 self.assertEqual(
471 diags,
472 tricium_clang_tidy.filter_tidy_lints(
473 only_files=None,
474 git_repo_base=None,
475 diags=diags,
476 ),
477 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800478
Alex Klein1699fab2022-09-08 08:46:06 -0600479 def test_filter_tidy_lints_filters_paths_outside_of_only_files(self):
480 in_only_files = default_tidy_diagnostic(file_path="foo.c")
481 out_of_only_files = default_tidy_diagnostic(file_path="bar.c")
482 self.assertEqual(
483 [in_only_files],
484 tricium_clang_tidy.filter_tidy_lints(
485 only_files={Path("foo.c")},
486 git_repo_base=None,
487 diags=[in_only_files, out_of_only_files],
488 ),
489 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 def test_filter_tidy_lints_normalizes_to_git_repo_baes(self):
492 git = default_tidy_diagnostic(file_path="/git/foo.c")
493 nogit = default_tidy_diagnostic(file_path="/nogit/bar.c")
494 self.assertEqual(
495 [git.normalize_paths_to("/git")],
496 tricium_clang_tidy.filter_tidy_lints(
497 only_files=None,
498 git_repo_base=Path("/git"),
499 diags=[git, nogit],
500 ),
501 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800502
Alex Klein1699fab2022-09-08 08:46:06 -0600503 def test_filter_tidy_lints_normalizes_and_restricts_properly(self):
504 git_and_only = default_tidy_diagnostic(file_path="/git/foo.c")
505 git_and_noonly = default_tidy_diagnostic(file_path="/git/bar.c")
506 self.assertEqual(
507 [git_and_only.normalize_paths_to("/git")],
508 tricium_clang_tidy.filter_tidy_lints(
509 only_files={Path("/git/foo.c")},
510 git_repo_base=Path("/git"),
511 diags=[git_and_only, git_and_noonly],
512 ),
513 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 @mock.patch.object(osutils, "CopyDirContents")
516 @mock.patch.object(osutils, "SafeMakedirs")
517 def test_lint_generation_functions(
518 self, safe_makedirs_mock, copy_dir_contents_mock
519 ):
520 run_mock = self.StartPatcher(cros_test_lib.PopenMock())
521 run_mock.SetDefaultCmdResult()
George Burgess IV853d65b2020-02-25 13:13:15 -0800522
Alex Klein1699fab2022-09-08 08:46:06 -0600523 # Mock mkdtemp last, since PopenMock() makes a tempdir.
524 mkdtemp_mock = self.PatchObject(tempfile, "mkdtemp")
525 mkdtemp_path = "/path/to/temp/dir"
526 mkdtemp_mock.return_value = mkdtemp_path
527 with mock.patch.object(osutils, "RmDir") as rmdir_mock:
528 dir_name = str(
529 tricium_clang_tidy.generate_lints(
530 "${board}", "/path/to/the.ebuild"
531 )
532 )
533 self.assertEqual(mkdtemp_path, dir_name)
George Burgess IV853d65b2020-02-25 13:13:15 -0800534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 rmdir_mock.assert_called_with(
536 tricium_clang_tidy.LINT_BASE, ignore_missing=True, sudo=True
537 )
538 safe_makedirs_mock.assert_called_with(
539 tricium_clang_tidy.LINT_BASE, 0o777, sudo=True
540 )
541
542 desired_env = dict(os.environ)
543 desired_env["WITH_TIDY"] = "tricium"
Alex Kleine913c5e2023-01-11 16:24:54 -0700544 # cros_build_lib.run adds LC_MESSAGES to the environment by default, so
545 # it is always in the actual env. It isn't guaranteed to be set in the
546 # ambient environment, so desired_env doesn't always have it, causing
547 # flakes. Explicitly set it to make sure it matches.
548 desired_env["LC_MESSAGES"] = "C"
Alex Klein1699fab2022-09-08 08:46:06 -0600549 run_mock.assertCommandContains(
550 ["ebuild-${board}", "/path/to/the.ebuild", "clean", "compile"],
551 env=desired_env,
552 )
553
554 copy_dir_contents_mock.assert_called_with(
555 tricium_clang_tidy.LINT_BASE, dir_name
556 )