blob: 9fe83d7835c082e67a6d2c39d3ce97369709974e [file] [log] [blame]
George Burgess IV853d65b2020-02-25 13:13:15 -08001# Copyright 2020 The Chromium OS Authors. All rights reserved.
2# 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
Alex Klein1699fab2022-09-08 08:46:06 -060025 The data contained in YAML is slightly different than what `TidyReplacement`s
26 carry.
27 """
28
29 file_path: str
30 text: str
31 offset: int
32 length: int
George Burgess IV853d65b2020-02-25 13:13:15 -080033
34
35class Note(NamedTuple):
Alex Klein1699fab2022-09-08 08:46:06 -060036 """A clang-tidy `note` from the YAML file."""
37
38 message: str
39 file_path: str
40 file_offset: int
George Burgess IV853d65b2020-02-25 13:13:15 -080041
42
Alex Klein1699fab2022-09-08 08:46:06 -060043def default_tidy_diagnostic(
44 file_path="/tidy/file.c",
45 line_number=1,
46 diag_name="${diag_name}",
47 message="${message}",
48 replacements=(),
49 expansion_locs=(),
50):
51 """Creates a TidyDiagnostic with reasonable defaults.
George Burgess IV853d65b2020-02-25 13:13:15 -080052
Alex Klein1699fab2022-09-08 08:46:06 -060053 Defaults here and yaml_diagnostic are generally intended to match where
54 possible.
55 """
56 return tricium_clang_tidy.TidyDiagnostic(
57 file_path=file_path,
58 line_number=line_number,
59 diag_name=diag_name,
60 message=message,
61 replacements=replacements,
62 expansion_locs=expansion_locs,
63 )
George Burgess IV853d65b2020-02-25 13:13:15 -080064
65
Alex Klein1699fab2022-09-08 08:46:06 -060066def yaml_diagnostic(
67 name="${diag_name}",
68 message="${message}",
69 file_path="/tidy/file.c",
70 file_offset=1,
71 replacements=(),
72 notes=(),
73):
74 """Creates a diagnostic serializable as YAML with reasonable defaults."""
75 result = {
76 "DiagnosticName": name,
77 "DiagnosticMessage": {
78 "Message": message,
79 "FilePath": file_path,
80 "FileOffset": file_offset,
81 },
82 }
George Burgess IV853d65b2020-02-25 13:13:15 -080083
Alex Klein1699fab2022-09-08 08:46:06 -060084 if replacements:
85 result["DiagnosticMessage"]["Replacements"] = [
86 {
87 "FilePath": x.file_path,
88 "Offset": x.offset,
89 "Length": x.length,
90 "ReplacementText": x.text,
91 }
92 for x in replacements
93 ]
George Burgess IV853d65b2020-02-25 13:13:15 -080094
Alex Klein1699fab2022-09-08 08:46:06 -060095 if notes:
96 result["Notes"] = [
97 {
98 "Message": x.message,
99 "FilePath": x.file_path,
100 "FileOffset": x.file_offset,
101 }
102 for x in notes
103 ]
George Burgess IV853d65b2020-02-25 13:13:15 -0800104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 return result
George Burgess IV853d65b2020-02-25 13:13:15 -0800106
107
108def mocked_nop_realpath(f):
Alex Klein1699fab2022-09-08 08:46:06 -0600109 """Mocks os.path.realpath to just return its argument."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 @mock.patch.object(os.path, "realpath")
112 @mock.patch.object(Path, "resolve")
113 def inner(self, replace_mock, realpath_mock, *args, **kwargs):
114 """Mocker for realpath."""
115 identity = lambda x: x
116 realpath_mock.side_effect = identity
117 replace_mock.side_effect = identity
118 return f(self, *args, **kwargs)
George Burgess IV853d65b2020-02-25 13:13:15 -0800119
Alex Klein1699fab2022-09-08 08:46:06 -0600120 return inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800121
122
123def mocked_readonly_open(contents=None, default=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600124 """Mocks out open() so it always returns things from |contents|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 Writing to open'ed files is not supported.
George Burgess IV853d65b2020-02-25 13:13:15 -0800127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 Args:
129 contents: a |dict| mapping |file_path| => file_contents.
130 default: a default string to return if the given |file_path| doesn't
131 exist in |contents|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800132
Alex Klein1699fab2022-09-08 08:46:06 -0600133 Returns:
134 |contents[file_path]| if it exists; otherwise, |default|.
George Burgess IV853d65b2020-02-25 13:13:15 -0800135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 Raises:
137 If |default| is None and |contents[file_path]| does not exist, this will
138 raise a |ValueError|.
139 """
George Burgess IV853d65b2020-02-25 13:13:15 -0800140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 if contents is None:
142 contents = {}
George Burgess IV853d65b2020-02-25 13:13:15 -0800143
Alex Klein1699fab2022-09-08 08:46:06 -0600144 def inner(f):
145 """mocked_open impl."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 @mock.mock_open()
148 def inner_inner(self, open_mock, *args, **kwargs):
149 """the impl of mocked_readonly_open's impl!"""
George Burgess IV853d65b2020-02-25 13:13:15 -0800150
Alex Klein1699fab2022-09-08 08:46:06 -0600151 def get_data(file_path, mode="r", encoding=None):
152 """the impl of the impl of mocked_readonly_open's impl!!"""
153 data = contents.get(file_path, default)
154 if data is None:
155 raise ValueError(
156 "No %r file was found; options were %r"
157 % (file_path, sorted(contents.keys()))
158 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 assert mode == "r", f"File mode {mode} isn't supported."
161 if encoding is None:
162 return io.BytesIO(data)
163 return io.StringIO(data)
George Burgess IV853d65b2020-02-25 13:13:15 -0800164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 open_mock.side_effect = get_data
George Burgess IV853d65b2020-02-25 13:13:15 -0800166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 def get_data_stream(file_path):
168 return io.StringIO(get_data(file_path))
George Burgess IV853d65b2020-02-25 13:13:15 -0800169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 open_mock.side_effect = get_data_stream
171 return f(self, *args, **kwargs)
George Burgess IV853d65b2020-02-25 13:13:15 -0800172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 return inner_inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800174
Alex Klein1699fab2022-09-08 08:46:06 -0600175 return inner
George Burgess IV853d65b2020-02-25 13:13:15 -0800176
177
178class TriciumClangTidyTests(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600179 """Various tests for tricium support."""
George Burgess IV853d65b2020-02-25 13:13:15 -0800180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 def test_tidy_diagnostic_path_normalization(self):
182 expanded_from = tricium_clang_tidy.TidyExpandedFrom(
183 file_path=Path("/old2/foo"),
184 line_number=2,
185 )
186 diag = default_tidy_diagnostic(
187 file_path=Path("/old/foo"),
188 expansion_locs=(expanded_from,),
189 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800190
Alex Klein1699fab2022-09-08 08:46:06 -0600191 normalized = diag.normalize_paths_to("/new")
192 self.assertEqual(
193 normalized,
194 diag._replace(
195 file_path=Path("../old/foo"),
196 expansion_locs=(
197 expanded_from._replace(file_path=Path("../old2/foo")),
198 ),
George Burgess IV853d65b2020-02-25 13:13:15 -0800199 ),
Alex Klein1699fab2022-09-08 08:46:06 -0600200 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 def test_line_offest_map_works(self):
203 # (input_char, line_number_of_char, line_offset_of_char)
204 line_offset_pairs = [
205 ("a", 1, 0),
206 ("b", 1, 1),
207 ("\n", 1, 2),
208 ("c", 2, 0),
209 ("\n", 2, 1),
210 ("\n", 3, 0),
211 ("d", 4, 0),
212 ("", 4, 1),
213 ("", 4, 2),
214 ]
215 text = tricium_clang_tidy.LineOffsetMap.for_text(
216 "".join(x for x, _, _ in line_offset_pairs)
217 )
218 for offset, (_, line_number, line_offset) in enumerate(
219 line_offset_pairs
220 ):
221 self.assertEqual(text.get_line_number(offset), line_number)
222 self.assertEqual(text.get_line_offset(offset), line_offset)
223
224 def test_package_ebuild_resolution(self):
225 run_mock = self.StartPatcher(cros_test_lib.RunCommandMock())
226 run_mock.SetDefaultCmdResult(stdout="${package1_ebuild}\n")
227 ebuilds = tricium_clang_tidy.resolve_package_ebuilds(
228 "${board}",
229 [
230 "package1",
231 "package2.ebuild",
George Burgess IV853d65b2020-02-25 13:13:15 -0800232 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600233 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 run_mock.assertCommandContains(
236 ["equery-${board}", "w", "package1"],
237 check=True,
238 stdout=subprocess.PIPE,
239 encoding="utf-8",
240 )
241 self.assertEqual(ebuilds, ["${package1_ebuild}", "package2.ebuild"])
242
243 @mocked_readonly_open(default="")
244 def test_parse_tidy_invocation_returns_exception_on_error(
245 self, read_file_mock
246 ):
247 oh_no = ValueError("${oh_no}!")
248 read_file_mock.side_effect = oh_no
249 result = tricium_clang_tidy.parse_tidy_invocation(
250 Path("/some/file/that/doesnt/exist.json")
251 )
252 self.assertIn(str(oh_no), str(result))
253
254 @mocked_readonly_open(
255 {
256 "/file/path.json": json.dumps(
257 {
258 "exit_code": 1,
259 "executable": "${clang_tidy}",
260 "args": ["foo", "bar"],
261 "lint_target": "${target}",
262 "stdstreams": "brrrrrrr",
263 "wd": "/path/to/wd",
264 }
265 ),
266 # |yaml.dumps| doesn't exist, but json parses cleanly as yaml, so...
267 "/file/path.yaml": json.dumps(
268 {
269 "Diagnostics": [
270 yaml_diagnostic(
271 name="some-diag",
272 message="${message}",
273 file_path="",
274 ),
275 ]
276 }
277 ),
278 }
279 )
280 def test_parse_tidy_invocation_functions_on_success(self):
281 result = tricium_clang_tidy.parse_tidy_invocation("/file/path.json")
282 # If we got an |Exception|, print it out.
283 self.assertNotIsInstance(result, tricium_clang_tidy.Error)
284 meta, info = result
285 self.assertEqual(
286 meta,
287 tricium_clang_tidy.InvocationMetadata(
288 exit_code=1,
289 invocation=["${clang_tidy}", "foo", "bar"],
290 lint_target="${target}",
291 stdstreams="brrrrrrr",
292 wd="/path/to/wd",
293 ),
294 )
295
296 self.assertEqual(
297 info,
298 [
299 default_tidy_diagnostic(
300 diag_name="some-diag",
301 message="${message}",
302 file_path="",
303 ),
George Burgess IV853d65b2020-02-25 13:13:15 -0800304 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600305 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 @mocked_nop_realpath
308 @mocked_readonly_open(default="")
309 def test_parse_fixes_file_absolutizes_paths(self):
310 results = tricium_clang_tidy.parse_tidy_fixes_file(
311 "/tidy",
312 {
313 "Diagnostics": [
314 yaml_diagnostic(file_path="foo.c"),
315 yaml_diagnostic(file_path="/tidy/bar.c"),
316 yaml_diagnostic(file_path=""),
George Burgess IV853d65b2020-02-25 13:13:15 -0800317 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600318 },
319 )
320 file_paths = [x.file_path for x in results]
321 self.assertEqual(file_paths, ["/tidy/foo.c", "/tidy/bar.c", ""])
George Burgess IV853d65b2020-02-25 13:13:15 -0800322
Alex Klein1699fab2022-09-08 08:46:06 -0600323 @mocked_nop_realpath
324 @mocked_readonly_open(
325 {
326 "/tidy/foo.c": "",
327 "/tidy/foo.h": "a\n\n",
328 }
329 )
330 def test_parse_fixes_file_interprets_offsets_correctly(self):
331 results = tricium_clang_tidy.parse_tidy_fixes_file(
332 "/tidy",
333 {
334 "Diagnostics": [
335 yaml_diagnostic(file_path="/tidy/foo.c", file_offset=1),
336 yaml_diagnostic(file_path="/tidy/foo.c", file_offset=2),
337 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=1),
338 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=2),
339 yaml_diagnostic(file_path="/tidy/foo.h", file_offset=3),
George Burgess IV853d65b2020-02-25 13:13:15 -0800340 ],
Alex Klein1699fab2022-09-08 08:46:06 -0600341 },
342 )
343 file_locations = [(x.file_path, x.line_number) for x in results]
344 self.assertEqual(
345 file_locations,
346 [
347 ("/tidy/foo.c", 1),
348 ("/tidy/foo.c", 1),
349 ("/tidy/foo.h", 1),
350 ("/tidy/foo.h", 2),
351 ("/tidy/foo.h", 3),
352 ],
353 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800354
Alex Klein1699fab2022-09-08 08:46:06 -0600355 @mocked_nop_realpath
356 @mocked_readonly_open({"/tidy/foo.c": "a \n\n"})
357 def test_parse_fixes_file_handles_replacements(self):
358 results = list(
359 tricium_clang_tidy.parse_tidy_fixes_file(
360 "/tidy",
361 {
362 "Diagnostics": [
363 yaml_diagnostic(
364 file_path="/tidy/foo.c",
365 file_offset=1,
366 replacements=[
367 Replacement(
368 file_path="foo.c",
369 text="whee",
370 offset=2,
371 length=2,
372 ),
373 ],
374 ),
375 ],
376 },
377 )
378 )
379 self.assertEqual(len(results), 1, results)
380 self.assertEqual(
381 results[0].replacements,
382 (
383 tricium_clang_tidy.TidyReplacement(
384 new_text="whee",
385 start_line=1,
386 end_line=3,
387 start_char=2,
388 end_char=0,
389 ),
390 ),
391 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800392
Alex Klein1699fab2022-09-08 08:46:06 -0600393 @mocked_nop_realpath
394 @mocked_readonly_open({"/whee.c": "", "/whee.h": "\n\n"})
395 def test_parse_fixes_file_handles_macro_expansions(self):
396 results = list(
397 tricium_clang_tidy.parse_tidy_fixes_file(
398 "/tidy",
399 {
400 "Diagnostics": [
401 yaml_diagnostic(
402 file_path="/whee.c",
403 file_offset=1,
404 notes=[
405 Note(
406 message="not relevant",
407 file_path="/whee.c",
408 file_offset=1,
409 ),
410 Note(
411 message='expanded from macro "Foo"',
412 file_path="/whee.h",
413 file_offset=9,
414 ),
415 ],
416 ),
417 ],
418 },
419 )
420 )
421 self.assertEqual(len(results), 1, results)
422 self.assertEqual(
423 results[0].expansion_locs,
424 (
425 tricium_clang_tidy.TidyExpandedFrom(
426 file_path="/whee.h",
427 line_number=3,
428 ),
429 ),
430 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800431
Alex Klein1699fab2022-09-08 08:46:06 -0600432 @mock.patch.object(Path, "glob")
433 @mock.patch.object(tricium_clang_tidy, "parse_tidy_invocation")
434 def test_collect_lints_functions(self, parse_invocation_mock, glob_mock):
435 glob_mock.return_value = ("/lint/foo.json", "/lint/bar.json")
George Burgess IV853d65b2020-02-25 13:13:15 -0800436
Alex Klein1699fab2022-09-08 08:46:06 -0600437 diag_1 = default_tidy_diagnostic()
438 diag_2 = diag_1._replace(line_number=diag_1.line_number + 1)
439 diag_3 = diag_2._replace(line_number=diag_2.line_number + 1)
George Burgess IV853d65b2020-02-25 13:13:15 -0800440
Alex Klein1699fab2022-09-08 08:46:06 -0600441 # Because we want to test unique'ing, ensure these aren't equal.
442 all_diags = [diag_1, diag_2, diag_3]
443 self.assertEqual(sorted(all_diags), sorted(set(all_diags)))
George Burgess IV853d65b2020-02-25 13:13:15 -0800444
Alex Klein1699fab2022-09-08 08:46:06 -0600445 per_file_lints = {
446 "/lint/foo.json": {diag_1, diag_2},
447 "/lint/bar.json": {diag_2, diag_3},
448 }
George Burgess IV853d65b2020-02-25 13:13:15 -0800449
Alex Klein1699fab2022-09-08 08:46:06 -0600450 def parse_invocation_side_effect(json_file):
451 self.assertIn(json_file, per_file_lints)
452 meta = mock.Mock()
453 meta.exit_code = 0
454 return meta, per_file_lints[json_file]
George Burgess IV853d65b2020-02-25 13:13:15 -0800455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 parse_invocation_mock.side_effect = parse_invocation_side_effect
George Burgess IV853d65b2020-02-25 13:13:15 -0800457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 with multiprocessing.pool.ThreadPool(1) as yaml_pool:
459 lints = tricium_clang_tidy.collect_lints(Path("/lint"), yaml_pool)
George Burgess IV853d65b2020-02-25 13:13:15 -0800460
Alex Klein1699fab2022-09-08 08:46:06 -0600461 self.assertEqual(set(all_diags), lints)
George Burgess IV853d65b2020-02-25 13:13:15 -0800462
Alex Klein1699fab2022-09-08 08:46:06 -0600463 def test_filter_tidy_lints_filters_nothing_by_default(self):
464 basis = default_tidy_diagnostic()
465 diag2 = default_tidy_diagnostic(line_number=basis.line_number + 1)
466 diags = [basis, diag2]
467 diags.sort()
George Burgess IV853d65b2020-02-25 13:13:15 -0800468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 self.assertEqual(
470 diags,
471 tricium_clang_tidy.filter_tidy_lints(
472 only_files=None,
473 git_repo_base=None,
474 diags=diags,
475 ),
476 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800477
Alex Klein1699fab2022-09-08 08:46:06 -0600478 def test_filter_tidy_lints_filters_paths_outside_of_only_files(self):
479 in_only_files = default_tidy_diagnostic(file_path="foo.c")
480 out_of_only_files = default_tidy_diagnostic(file_path="bar.c")
481 self.assertEqual(
482 [in_only_files],
483 tricium_clang_tidy.filter_tidy_lints(
484 only_files={Path("foo.c")},
485 git_repo_base=None,
486 diags=[in_only_files, out_of_only_files],
487 ),
488 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800489
Alex Klein1699fab2022-09-08 08:46:06 -0600490 def test_filter_tidy_lints_normalizes_to_git_repo_baes(self):
491 git = default_tidy_diagnostic(file_path="/git/foo.c")
492 nogit = default_tidy_diagnostic(file_path="/nogit/bar.c")
493 self.assertEqual(
494 [git.normalize_paths_to("/git")],
495 tricium_clang_tidy.filter_tidy_lints(
496 only_files=None,
497 git_repo_base=Path("/git"),
498 diags=[git, nogit],
499 ),
500 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800501
Alex Klein1699fab2022-09-08 08:46:06 -0600502 def test_filter_tidy_lints_normalizes_and_restricts_properly(self):
503 git_and_only = default_tidy_diagnostic(file_path="/git/foo.c")
504 git_and_noonly = default_tidy_diagnostic(file_path="/git/bar.c")
505 self.assertEqual(
506 [git_and_only.normalize_paths_to("/git")],
507 tricium_clang_tidy.filter_tidy_lints(
508 only_files={Path("/git/foo.c")},
509 git_repo_base=Path("/git"),
510 diags=[git_and_only, git_and_noonly],
511 ),
512 )
George Burgess IV853d65b2020-02-25 13:13:15 -0800513
Alex Klein1699fab2022-09-08 08:46:06 -0600514 @mock.patch.object(osutils, "CopyDirContents")
515 @mock.patch.object(osutils, "SafeMakedirs")
516 def test_lint_generation_functions(
517 self, safe_makedirs_mock, copy_dir_contents_mock
518 ):
519 run_mock = self.StartPatcher(cros_test_lib.PopenMock())
520 run_mock.SetDefaultCmdResult()
George Burgess IV853d65b2020-02-25 13:13:15 -0800521
Alex Klein1699fab2022-09-08 08:46:06 -0600522 # Mock mkdtemp last, since PopenMock() makes a tempdir.
523 mkdtemp_mock = self.PatchObject(tempfile, "mkdtemp")
524 mkdtemp_path = "/path/to/temp/dir"
525 mkdtemp_mock.return_value = mkdtemp_path
526 with mock.patch.object(osutils, "RmDir") as rmdir_mock:
527 dir_name = str(
528 tricium_clang_tidy.generate_lints(
529 "${board}", "/path/to/the.ebuild"
530 )
531 )
532 self.assertEqual(mkdtemp_path, dir_name)
George Burgess IV853d65b2020-02-25 13:13:15 -0800533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 rmdir_mock.assert_called_with(
535 tricium_clang_tidy.LINT_BASE, ignore_missing=True, sudo=True
536 )
537 safe_makedirs_mock.assert_called_with(
538 tricium_clang_tidy.LINT_BASE, 0o777, sudo=True
539 )
540
541 desired_env = dict(os.environ)
542 desired_env["WITH_TIDY"] = "tricium"
543 run_mock.assertCommandContains(
544 ["ebuild-${board}", "/path/to/the.ebuild", "clean", "compile"],
545 env=desired_env,
546 )
547
548 copy_dir_contents_mock.assert_called_with(
549 tricium_clang_tidy.LINT_BASE, dir_name
550 )