910e62b5创建于 1月15日历史提交
#!/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 unittest
import os
import tempfile
import shutil

import bedrock_metrics


class TestBedrockMetrics(unittest.TestCase):

  def setUp(self):
    # Create a temporary directory for test files.
    self.test_dir = tempfile.mkdtemp(prefix="bedrock_test_")
    self.sub_dir = os.path.join(self.test_dir, "sub")
    os.makedirs(self.sub_dir)

    # --- Files for count_matching_files ---
    # Structure:
    # self.test_dir/
    #   - file_alpha.h (Content: "apple banana\ncount_me_once")
    #   - file_beta.cc (Content: "banana cherry\ncount_me_once\ncount_me_once")
    #   - file_gamma.mm (Content: "apple cherry\nno_count_string")
    #   - sub/
    #     - file_delta.h (Content: "apple\ncount_me_once")
    #   - file_exclude_me.cc (Content: "apple banana\ncontent for exclude")
    #   - other_extension.txt (Content: "apple banana\ntext file content")
    #   - empty_file.h (Content: "")
    files_to_create_for_matching = [
        ("file_alpha.h", "apple banana\ncount_me_once"),
        ("file_beta.cc", "banana cherry\ncount_me_once\ncount_me_once"),
        ("file_gamma.mm", "apple cherry\nno_count_string"),
        (os.path.join("sub", "file_delta.h"), "apple\ncount_me_once"),
        ("file_exclude_me.cc", "apple banana\ncontent for exclude"),
        ("other_extension.txt",
         "apple banana\ntext file content"),  # Invalid extension
        ("empty_file.h", "")
    ]

    for rel_path, content in files_to_create_for_matching:
      full_path = os.path.join(self.test_dir, rel_path)
      os.makedirs(os.path.dirname(full_path), exist_ok=True)
      with open(full_path, 'w', encoding='utf-8') as f:
        f.write(content)

    # --- Files for count_lines ---
    self.lines_file1_path = os.path.join(self.test_dir, "lines_file1.data")
    with open(self.lines_file1_path, 'w', encoding='utf-8') as f:
      f.write("line1\nline2\nline3")  # 3 lines

    self.lines_file2_path = os.path.join(self.test_dir, "lines_file2.data")
    with open(self.lines_file2_path, 'w', encoding='utf-8') as f:
      f.write("single line")  # 1 line

    self.lines_empty_file_path = os.path.join(self.test_dir, "lines_empty.data")
    with open(self.lines_empty_file_path, 'w', encoding='utf-8') as f:
      f.write("")  # 0 lines

  def tearDown(self):
    # Remove the temporary directory and its contents after tests.
    shutil.rmtree(self.test_dir)

  # --- Tests for count_matching_files ---

  def test_cmf_no_filters_counts_valid_extensions(self):
    # Expected: file_alpha.h, file_beta.cc, file_gamma.mm, sub/file_delta.h,
    # file_exclude_me.cc, empty_file.h (6 files with .h, .cc, .mm)
    # other_extension.txt is ignored due to extension.
    # No content_match_strings, so string_matches_count is 0.
    count, str_count = bedrock_metrics.count_matching_files(self.test_dir)
    self.assertEqual(count, 6, "File count mismatch with no filters")
    self.assertEqual(str_count, 0,
                     "String count should be 0 with no content search strings")

  def test_cmf_include_filename_single(self):
    # include_filename_strings=["alpha"] -> matches file_alpha.h
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_filename_strings=["alpha"])
    self.assertEqual(count, 1)
    self.assertEqual(str_count, 0)

  def test_cmf_include_filename_multiple_all_match(self):
    # include_filename_strings=["file", "alpha"] -> matches file_alpha.h
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_filename_strings=["file", "alpha"])
    self.assertEqual(count, 1)
    self.assertEqual(str_count, 0)

  def test_cmf_include_filename_multiple_one_no_match(self):
    # include_filename_strings=["alpha", "beta"] -> No file has BOTH "alpha"
    # AND"beta".
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_filename_strings=["alpha", "nomatch"])
    self.assertEqual(count, 0)
    self.assertEqual(str_count, 0)

  def test_cmf_exclude_filename_single(self):
    # exclude_filename_strings=["exclude"] -> Excludes file_exclude_me.cc
    # Original 6 valid files - 1 = 5 files.
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, exclude_filename_strings=["exclude"])
    self.assertEqual(count, 5)
    self.assertEqual(str_count, 0)

  # These tests verify the current code behavior (ANY).
  def test_cmf_include_file_content_strings_any_behavior_one_present(self):
    # include_file_content_strings=["apple"]
    # Matches: file_alpha.h, file_gamma.mm, sub/file_delta.h, file_exclude_me.cc
    # (4 files)
    # file_beta.cc (banana cherry) does not contain "apple". empty_file.h is
    # empty.
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_file_content_strings=["apple"])
    self.assertEqual(count, 4)
    self.assertEqual(str_count, 0)

  def test_cmf_include_file_content_strings_any_behavior_multiple_options(self):
    # include_file_content_strings=["apple", "cherry"] (any of these in content)
    # file_alpha.h ("apple") - Yes
    # file_beta.cc ("cherry") - Yes
    # file_gamma.mm ("apple", "cherry") - Yes
    # sub/file_delta.h ("apple") - Yes
    # file_exclude_me.cc ("apple") - Yes
    # empty_file.h - No
    # Expected: 5 files
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_file_content_strings=["apple", "cherry"])
    self.assertEqual(count, 5)
    self.assertEqual(str_count, 0)

  def test_cmf_include_file_content_strings_none_present_in_content(self):
    # include_file_content_strings=["nonexistent_string_pattern"]
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir,
        include_file_content_strings=["nonexistent_string_pattern"])
    self.assertEqual(count, 0)
    self.assertEqual(str_count, 0)

  def test_cmf_content_match_strings_single_term_count(self):
    # content_match_strings=["count_me_once"]
    # All 6 valid files are checked for content counts.
    # file_alpha.h: 1 | file_beta.cc: 2 | file_gamma.mm: 0
    # sub/file_delta.h: 1 | file_exclude_me.cc: 0 | empty_file.h: 0
    # Total string matches: 1 + 2 + 0 + 1 + 0 + 0 = 4
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, content_match_strings=["count_me_once"])
    self.assertEqual(
        count, 6,
        "File count should include all valid files for content counting")
    self.assertEqual(str_count, 4, "String match count error")

  def test_cmf_content_match_strings_multiple_terms_count(self):
    # content_match_strings=["apple", "banana"]
    # All 6 valid files are checked.
    # file_alpha.h ("apple banana"): apple(1)+banana(1)=2
    # file_beta.cc ("banana cherry"): apple(0)+banana(1)=1
    # file_gamma.mm ("apple cherry"): apple(1)+banana(0)=1
    # sub/file_delta.h ("apple"): apple(1)+banana(0)=1
    # file_exclude_me.cc ("apple banana"): apple(1)+banana(1)=2
    # empty_file.h: apple(0)+banana(0)=0
    # Total string matches: 2+1+1+1+2+0 = 7
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, content_match_strings=["apple", "banana"])
    self.assertEqual(count, 6)
    self.assertEqual(str_count, 7)

  def test_cmf_all_filters_combined(self):
    # include_filename_strings=["file"], exclude_filename_strings=["exclude"]
    # include_file_content_strings=["apple"] (content filter, using 'any' logic
    # of code)
    # content_match_strings=["count_me_once"]

    # 1. Include "file": file_alpha.h, file_beta.cc, file_gamma.mm,
    #    file_exclude_me.cc, empty_file.h
    # 2. Exclude "exclude": Removes file_exclude_me.cc.
    #    Remaining: file_alpha.h, file_beta.cc, file_gamma.mm, empty_file.h
    # 3. include_file_content_strings=["apple"]: (file must contain "apple")
    #    file_alpha.h (has "apple") - Keep
    #    file_beta.cc (no "apple") - Remove
    #    file_gamma.mm (has "apple") - Keep
    #    sub/file_delta.h (has "apple"): - Keep
    #    empty_file.h (no "apple") - Remove
    #    Matched files: file_alpha.h, file_gamma.mm. So, count = 2.
    # 4. content_match_strings=["count_me_once"] on these 2 files:
    #    file_alpha.h: 1 occurrence
    #    file_gamma.mm: 0 occurrences
    #    sub/file_delta.h (has "apple"): 1 occurrences
    #    Total str_count = 1.
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir,
        include_file_content_strings=["apple"],
        content_match_strings=["count_me_once"],
        include_filename_strings=["file"],
        exclude_filename_strings=["exclude"])
    self.assertEqual(count, 3)
    self.assertEqual(str_count, 2)

  def test_cmf_empty_directory(self):
    empty_subdir_path = os.path.join(self.test_dir, "empty_subdir_for_test")
    os.makedirs(empty_subdir_path)
    count, str_count = bedrock_metrics.count_matching_files(empty_subdir_path)
    self.assertEqual(count, 0)
    self.assertEqual(str_count, 0)

  def test_cmf_no_matching_extension_files_present(self):
    # Directory with only a .txt file, which is not in [.h, .cc, .mm]
    other_ext_dir = os.path.join(self.test_dir, "other_ext_dir")
    os.makedirs(other_ext_dir)
    with open(os.path.join(other_ext_dir, "somefile.txt"), 'w') as f:
      f.write("content")
    count, str_count = bedrock_metrics.count_matching_files(other_ext_dir)
    self.assertEqual(count, 0)
    self.assertEqual(str_count, 0)

  def test_cmf_include_file_content_strings_is_empty_list(self):
    # An empty list for include_file_content_strings means the content filter
    # (Step 4) is skipped.
    # Same as include_file_content_strings=None.
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, include_file_content_strings=[])
    self.assertEqual(count, 6)  # All 6 valid extension files
    self.assertEqual(str_count, 0)

  def test_cmf_content_match_strings_is_empty_list(self):
    # No strings to count, so str_count should be 0.
    # File count reflects all files matching other criteria.
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, content_match_strings=[])
    self.assertEqual(count, 6)  # All 6 valid extension files
    self.assertEqual(str_count, 0)

  def test_cmf_content_match_strings_includes_empty_string(self):
    # The code `if s_str:` skips empty strings in content_match_strings.
    # So, ["count_me_once", ""] should behave like ["count_me_once"].
    count, str_count = bedrock_metrics.count_matching_files(
        self.test_dir, content_match_strings=["count_me_once", ""])
    self.assertEqual(count, 6)
    self.assertEqual(str_count, 4)  # Same as just ["count_me_once"]

  # --- Tests for count_lines ---

  def test_cl_single_file_multiple_lines(self):
    self.assertEqual(bedrock_metrics.count_lines([self.lines_file1_path]), 3)

  def test_cl_single_file_one_line_no_trailing_newline(self):
    self.assertEqual(bedrock_metrics.count_lines([self.lines_file2_path]), 1)

  def test_cl_multiple_files(self):
    total_lines = bedrock_metrics.count_lines(
        [self.lines_file1_path, self.lines_file2_path])
    self.assertEqual(total_lines, 3 + 1)  # 4

  def test_cl_empty_file(self):
    # An empty file has 0 lines according to file.readlines().
    self.assertEqual(bedrock_metrics.count_lines([self.lines_empty_file_path]),
                     0)

  def test_cl_mixed_files_including_empty(self):
    total_lines = bedrock_metrics.count_lines([
        self.lines_file1_path, self.lines_empty_file_path, self.lines_file2_path
    ])
    self.assertEqual(total_lines, 3 + 0 + 1)  # 4

  def test_cl_empty_file_list(self):
    self.assertEqual(bedrock_metrics.count_lines([]), 0)


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