scripts: Support applying fixes to lint_package

This CL adds support for applying Clang Tidy fixes to files in
platform2.

Screenshot of output: https://screenshot.googleplex.com/6iwDbBkzB2mJt25
Screenshot of generated diff: https://screenshot.googleplex.com/4X5BVVrqFGtdAMs

BUG=b:300533086
TEST=lint_package -b=atlas --apply-fixes --fetch-only

Change-Id: I87c9ba4942af4b7e1e8016746e535e53609893b2
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4866834
Tested-by: Ryan Beltran <ryanbeltran@chromium.org>
Auto-Submit: Ryan Beltran <ryanbeltran@chromium.org>
Commit-Queue: George Burgess <gbiv@chromium.org>
Reviewed-by: George Burgess <gbiv@chromium.org>
diff --git a/scripts/lint_package_unitest.py b/scripts/lint_package_unitest.py
new file mode 100644
index 0000000..9da2f4b
--- /dev/null
+++ b/scripts/lint_package_unitest.py
@@ -0,0 +1,85 @@
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for lint_package script."""
+
+from chromite.lib import cros_test_lib
+from chromite.scripts import lint_package
+from chromite.service import toolchain
+
+
+class TestApplyFixes(cros_test_lib.MockTempDirTestCase):
+    """Unit tests for --apply-fixes."""
+
+    def fix_from_offsets(self, start, end, path="", edit=""):
+        return toolchain.SuggestedFix(
+            edit, toolchain.CodeLocation(path, "", 0, 0, None, None, start, end)
+        )
+
+    def fix_to_offsets(self, fix):
+        return (fix.location.start_offset, fix.location.end_offset)
+
+    def testHasOverlap(self):
+        prior_fixes = [
+            self.fix_from_offsets(0, 5),
+            self.fix_from_offsets(10, 15),
+            self.fix_from_offsets(20, 25),
+            self.fix_from_offsets(30, 30),
+        ]
+        no_overlaps = [
+            self.fix_from_offsets(40, 45),
+            self.fix_from_offsets(50, 55),
+            self.fix_from_offsets(60, 65),
+            self.fix_from_offsets(70, 70),
+        ]
+        overlaps = [
+            self.fix_from_offsets(0, 2),
+            self.fix_from_offsets(10, 10),
+            self.fix_from_offsets(25, 25),
+            self.fix_from_offsets(22, 902),
+            self.fix_from_offsets(30, 30),
+            self.fix_from_offsets(5, 5),
+        ]
+        prior_fixes_str = str([self.fix_to_offsets(f) for f in prior_fixes])
+        for fix in no_overlaps:
+            self.assertFalse(
+                lint_package.has_overlap(prior_fixes, [fix]),
+                "has_overlap returned true unepectedly for "
+                f"{self.fix_to_offsets(fix)} in {prior_fixes_str}",
+            )
+        for fix in overlaps:
+            self.assertTrue(
+                lint_package.has_overlap(prior_fixes, [fix]),
+                "has_overlap returned false unexpectedly for "
+                f"{self.fix_to_offsets(fix)} in {prior_fixes_str}",
+            )
+
+        self.assertTrue(
+            lint_package.has_overlap(prior_fixes, overlaps + no_overlaps)
+        )
+
+        self.assertTrue(
+            lint_package.has_overlap(prior_fixes, no_overlaps + overlaps)
+        )
+
+    def testApplyEdits(self):
+        prior_contents = "0123456789" * 5
+        edits = [
+            self.fix_from_offsets(0, 5, edit="abc"),
+            self.fix_from_offsets(8, 9, edit=""),
+            self.fix_from_offsets(10, 15, edit="hello world"),
+            self.fix_from_offsets(20, 25, edit=""),
+            self.fix_from_offsets(30, 30, edit="foo"),
+            self.fix_from_offsets(43, 48, edit="spam spam"),
+        ]
+        expected = (
+            "abc5679"
+            + "hello world56789"
+            + "56789"
+            + "foo0123456789"
+            + "012spam spam89"
+        )
+        self.assertEqual(
+            lint_package.apply_edits(prior_contents, edits), expected
+        )