#!/usr/bin/env python3

# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os

import unittest
from unittest import mock
import collect_warnings

################################################################################
# This file contains tests for the collect_warnings.py script.
# These are primarily regression tests, comparing the result of running on
# sample build logs to known outputs. The sample logs were generated by building
# chromium with some extra warnings enabled, then arbitrarily choosing some
# examples of each warning and splitting them into multiple test files.
################################################################################


class TestCollectWarnings(unittest.TestCase):
    @property
    def _input_dir(self):
        base = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(base, "test_files")

    @property
    def _output_dir(self):
        base = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(base, "expected_output")

    def RunTestForWarning(self, warning_str, expected_file, other_args):
        """
        Run the script on the files in test_files/, searching for the designated
        warning. Ensure the output is identical to the contents of
        `expected_file`.
        """
        # Always write to stdout so we can capture the output easily via mock
        args = ["-w", warning_str, "-o", "stdout", "-l", self._input_dir]
        args += other_args
        with mock.patch("sys.stdout", write=mock.Mock()) as mock_stdout:
            collect_warnings.main(args)
            output = mock_stdout.write.call_args_list

        # Reconstruct the written output from the call_args

        # Calling with more than one arg should be impossible
        self.assertFalse(any(len(call.args) > 1 for call in output))
        call_strs = [call.args[0] for call in output]

        written_text = "".join(call_strs)

        with open(os.path.join(self._output_dir, expected_file)) as expected:
            self.assertEqual(written_text, expected.read())

    ## Tests for specific warning, to ensure we filter out warnings we don't
    ## want, and don't generate duplicates
    def testMissingInitializers(self):
        self.RunTestForWarning(
            "[-Wmissing-field-initializers]", "missing_initializers.txt", ["-s"]
        )

    def testUnusedParameter(self):
        self.RunTestForWarning(
            "[-Wunused-parameter]", "unused_parameter.txt", ["-s"]
        )

    # This warning only appears in the Windows output
    def testNontrivialMemcall(self):
        self.RunTestForWarning(
            "[-Wnontrivial-memcall]", "nontrivial_memcall.txt", ["-s"]
        )

    ## Tests for the various output formats (json/text, links/no links)
    def testEverything(self):
        # Passing "]" as the warning string will grab all clang warnings
        self.RunTestForWarning("]", "everything.json", [])
        self.RunTestForWarning("]", "everything_links.json", ["-k"])
        self.RunTestForWarning("]", "everything.txt", ["-s"])
        self.RunTestForWarning("]", "everything_links.txt", ["-s", "-k"])

    ## Tests for Rust warning parsing
    def testRustWarnings(self):
        rust_input = os.path.join(self._input_dir, "sample_rust.txt")
        args = ["-l", rust_input, "-o", "stdout", "-w", "used"]
        with mock.patch("sys.stdout", write=mock.Mock()) as mock_stdout:
            collect_warnings.main(args)
            output = mock_stdout.write.call_args_list

        call_strs = [call.args[0] for call in output]
        written_text = "".join(call_strs)
        self.assertIn("mojo/public/rust/system/message.rs", written_text)
        self.assertIn("fn foo()", written_text)
        self.assertIn("let x", written_text)
        self.assertNotIn("struct bar;", written_text)


if __name__ == '__main__':
    unittest.main()
