| #!/usr/bin/env vpython3 |
| # Copyright (c) 2023 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import sys |
| from typing import List |
| import unittest |
| |
| _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) |
| # The repo's root directory. |
| _ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..", "..")) |
| |
| # Add the repo's root directory for clearer imports. |
| sys.path.insert(0, _ROOT_DIR) |
| |
| import metadata.fields.known as known_fields |
| import metadata.fields.field_types as field_types |
| import metadata.validation_result as vr |
| |
| |
| class FieldValidationTest(unittest.TestCase): |
| def _run_field_validation(self, |
| field: field_types.MetadataField, |
| valid_values: List[str], |
| error_values: List[str], |
| warning_values: List[str] = []): |
| """Helper to run a field's validation for different values.""" |
| for value in valid_values: |
| self.assertIsNone(field.validate(value), value) |
| |
| for value in error_values: |
| self.assertIsInstance(field.validate(value), vr.ValidationError, |
| value) |
| |
| for value in warning_values: |
| self.assertIsInstance(field.validate(value), vr.ValidationWarning, |
| value) |
| |
| def test_freeform_text_validation(self): |
| # Check validation of a freeform text field that should be on |
| # one line. |
| self._run_field_validation( |
| field=field_types.SingleLineTextField("Text single line"), |
| valid_values=["Text on single line", "a", "1"], |
| error_values=["", "\n", " "], |
| ) |
| |
| # Check validation of a freeform text field that can span |
| # multiple lines. |
| self._run_field_validation( |
| field=field_types.FreeformTextField("Freeform multi"), |
| valid_values=[ |
| "This is text spanning multiple lines:\n" |
| " * with this point\n" |
| " * and this other point", |
| "Text on single line", |
| "a", |
| "1", |
| ], |
| error_values=["", "\n", " "], |
| ) |
| |
| def test_yes_no_field_validation(self): |
| self._run_field_validation( |
| field=field_types.YesNoField("Yes/No test"), |
| valid_values=["yes", "no", "No", "YES"], |
| error_values=["", "\n", "Probably yes"], |
| warning_values=["Yes?", "not"], |
| ) |
| |
| def test_cpe_prefix_validation(self): |
| self._run_field_validation( |
| field=known_fields.CPE_PREFIX, |
| valid_values=[ |
| "unknown", |
| "cpe:2.3:a:sqlite:sqlite:3.0.0:*:*:*:*:*:*:*", |
| "cpe:2.3:a:sqlite:sqlite:*:*:*:*:*:*:*:*", |
| "cpe:/a:vendor:product:version:update:edition:lang", |
| "cpe:/a::product:", |
| "cpe:/:vendor::::edition", |
| "cpe:/:vendor", |
| ], |
| error_values=[ |
| "", |
| "\n", |
| "cpe:2.3:a:sqlite:sqlite:3.0.0", |
| "cpe:2.3:a:sqlite:sqlite::::::::", |
| "cpe:/", |
| "cpe:/a:vendor:product:version:update:edition:lang:", |
| ], |
| ) |
| |
| def test_date_validation(self): |
| self._run_field_validation( |
| field=known_fields.DATE, |
| valid_values=["2012-03-04"], |
| error_values=[ |
| "", |
| "\n", |
| "N/A", |
| "03-04-12", # Ambiguous month and day. |
| "04/03/2012", # Ambiguous month and day. |
| ], |
| warning_values=[ |
| "2012-03-04 UTC", "2012-03-04 UTC+10:00", |
| "2012/03/04 UTC+10:00", "20120304", "April 3, 2012", |
| "3 Apr 2012", "30/12/2000", "20-03-2020", |
| "Tue Apr 3 05:06:07 2012 +0800" |
| ], |
| ) |
| |
| def test_license_validation(self): |
| self._run_field_validation( |
| field=known_fields.LICENSE, |
| valid_values=[ |
| "Apache, 2.0 / MIT / MPL 2", |
| "LGPL 2.1", |
| "GPL v2 or later", |
| "LGPL2 with the classpath exception", |
| "Apache, Version 2 and Public domain", |
| ], |
| error_values=["", "\n", ",", "Apache 2.0 / MIT / "], |
| warning_values=[ |
| "Custom license", |
| "Custom / MIT", |
| "Public domain or MPL 2", |
| "APSL 2 and the MIT license", |
| ], |
| ) |
| |
| def test_license_file_validation(self): |
| self._run_field_validation( |
| field=known_fields.LICENSE_FILE, |
| valid_values=[ |
| "LICENSE", "src/LICENSE.txt", |
| "LICENSE, //third_party_test/LICENSE-TEST", |
| "src/MISSING_LICENSE" |
| ], |
| error_values=["", "\n", ","], |
| warning_values=["NOT_SHIPPED"], |
| ) |
| |
| # Check relative path from README directory, and multiple |
| # license files. |
| result = known_fields.LICENSE_FILE.validate_on_disk( |
| value="LICENSE, src/LICENSE.txt", |
| source_file_dir=os.path.join(_THIS_DIR, "data"), |
| repo_root_dir=_THIS_DIR, |
| ) |
| self.assertIsNone(result) |
| |
| # Check relative path from Chromium src directory. |
| result = known_fields.LICENSE_FILE.validate_on_disk( |
| value="//data/LICENSE", |
| source_file_dir=os.path.join(_THIS_DIR, "data"), |
| repo_root_dir=_THIS_DIR, |
| ) |
| self.assertIsNone(result) |
| |
| # Check missing file. |
| result = known_fields.LICENSE_FILE.validate_on_disk( |
| value="MISSING_LICENSE", |
| source_file_dir=os.path.join(_THIS_DIR, "data"), |
| repo_root_dir=_THIS_DIR, |
| ) |
| self.assertIsInstance(result, vr.ValidationError) |
| |
| # Check deprecated NOT_SHIPPED. |
| result = known_fields.LICENSE_FILE.validate_on_disk( |
| value="NOT_SHIPPED", |
| source_file_dir=os.path.join(_THIS_DIR, "data"), |
| repo_root_dir=_THIS_DIR, |
| ) |
| self.assertIsInstance(result, vr.ValidationWarning) |
| |
| def test_url_validation(self): |
| self._run_field_validation( |
| field=known_fields.URL, |
| valid_values=[ |
| "https://www.example.com/a", |
| "http://www.example.com/b", |
| "ftp://www.example.com/c,git://www.example.com/d", |
| "https://www.example.com/a\n https://example.com/b", |
| "This is the canonical public repository", |
| ], |
| warning_values=[ |
| # Scheme is case-insensitive, but should be lower case. |
| "Https://www.example.com/g", |
| ], |
| error_values=[ |
| "", |
| "\n", |
| "ghttps://www.example.com/e", |
| "https://www.example.com/ f", |
| "This is an unrecognized message for the URL", |
| ], |
| ) |
| |
| def test_version_validation(self): |
| self._run_field_validation( |
| field=known_fields.VERSION, |
| valid_values=["n / a", "123abc", "unknown forked version"], |
| error_values=["", "\n"], |
| warning_values=["0", "unknown"], |
| ) |
| |
| def test_local_modifications(self): |
| # Checks local modifications field early terminates when we can reasonably infer there's no modification. |
| _NO_MODIFICATION_VALUES = [ |
| "None", "None.", "N/A.", "(none).", "No modification", "\nNone." |
| ] |
| for value in _NO_MODIFICATION_VALUES: |
| self.assertTrue( |
| known_fields.LOCAL_MODIFICATIONS.should_terminate_field(value)) |
| |
| # Checks ambiguous values won't early terminate the field. |
| _MAY_CONTAIN_MODIFICATION_VALUES = [ |
| "None. Except doing something.", |
| "Modify file X to include ....", |
| ] |
| for value in _MAY_CONTAIN_MODIFICATION_VALUES: |
| self.assertFalse( |
| known_fields.LOCAL_MODIFICATIONS.should_terminate_field(value)) |
| |
| if __name__ == "__main__": |
| unittest.main() |