/
githubmirror
/
oppia
Обзор
Документация
Войти
/
githubmirror
/
oppia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
scripts/linters/other_files_linter_test.py
608 строк
22 KB
Mohak51234
[GSoC 2026] M2.14 - Fix #22539: Prevent duplicate functions in playwright acceptance tests (#27058)
15 часов назад
Не верифицирован
15 часов назад
85ef030
Код
Авторство
О чём код?
# coding: utf-8 # # Copyright 2020 The Oppia Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for app_dev_linter.py.""" from __future__ import annotations import builtins import io import multiprocessing import os from core.tests import test_utils from typing import Final, List, Tuple from . import other_files_linter, run_lint_checks NAME_SPACE: Final = multiprocessing.Manager().Namespace() NAME_SPACE.files = run_lint_checks.FileCache() FILE_CACHE: Final = NAME_SPACE.files LINTER_TESTS_DIR: Final = os.path.join( os.getcwd(), 'scripts', 'linters', 'test_files' ) class CustomLintChecksManagerTests(test_utils.LinterTestBase): """Tests for CustomLintChecksManager.""" def setUp(self) -> None: super().setUp() self.verbose_mode_enabled = False self.package_file = io.StringIO( '{"dependencies":{"nerdamer":"^0.6","skulpt-dist":"0.2",' '"guppy-dev":"git+https://github.com/oppia/guppy#f509e",' '"midi": "git+https://github.com/oppia/miDI.js#c26eb"}}' ) self.files_in_typings_dir = [ 'guppy-defs-f509e.d.ts', 'skulpt-defs-0.2.d.ts', 'midi-defs-c26eb.d.ts', 'nerdamer-defs-0.6.d.ts', ] def mock_open( path: str, _: List[str], encoding: str = 'utf-8', # pylint: disable=unused-argument ) -> io.StringIO: if path == other_files_linter.PACKAGE_JSON_FILE_PATH: return self.package_file raise ValueError('Unexpected file path: %s' % path) def mock_listdir(unused_path: str) -> List[str]: return self.files_in_typings_dir self.open_swap = self.swap(builtins, 'open', mock_open) self.listdir_swap = self.swap(os, 'listdir', mock_listdir) def test_check_valid_pattern_in_app_dev_yaml(self) -> None: def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Just a comment', '# Third party files:', '- third_party/static/bootstrap-5.3.3/', ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() expected_error_messages = ['SUCCESS App dev file check passed'] self.assertEqual( error_messages.get_report(), expected_error_messages ) self.assertEqual('App dev file', error_messages.name) self.assertFalse(error_messages.failed) def test_check_skip_files_in_app_dev_yaml_without_section(self) -> None: """Passes when no '# Third party files:' section exists.""" def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Some unrelated config', '', 'random_setting: true', ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() expected = ['SUCCESS App dev file check passed'] self.assertEqual(error_messages.get_report(), expected) self.assertFalse(error_messages.failed) def test_check_skip_files_in_app_dev_yaml_ignores_non_entries(self) -> None: """Tests that blank lines, comments and non '- ' lines are ignored.""" def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Third party files:', '', # Blank line should be ignored. '# Some explanation', # Comment should be ignored. 'random_text', # Not a '- ' entry, should be ignored. '- third_party/static/bootstrap-5.3.3/', # Valid entry. ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() expected_error_messages = ['SUCCESS App dev file check passed'] self.assertEqual( error_messages.get_report(), expected_error_messages ) self.assertEqual('App dev file', error_messages.name) self.assertFalse(error_messages.failed) def test_check_skip_files_in_app_dev_yaml_with_no_entries(self) -> None: """Tests that file passes when no skip entries are present.""" def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Third party files:', '# Only comments present', '', 'some_random_text', ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() expected_error_messages = ['SUCCESS App dev file check passed'] self.assertEqual( error_messages.get_report(), expected_error_messages ) self.assertEqual('App dev file', error_messages.name) self.assertFalse(error_messages.failed) def test_check_invalid_pattern_in_app_dev_yaml(self) -> None: def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Third party files:', '- third_party/static/bootstrap-5.3/', ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() self.assertEqual(len(error_messages.get_report()), 2) self.assertTrue( 'Pattern on line 2 doesn\'t match any file or directory' in error_messages.get_report()[0] ) self.assertEqual('App dev file', error_messages.name) self.assertTrue(error_messages.failed) def test_check_multiple_invalid_patterns_in_app_dev_yaml(self) -> None: def mock_readlines( unused_self: str, unused_filepath: str ) -> Tuple[str, ...]: return ( '# Third party files:', '- third_party/static/bootstrap-5.3/', '- third_party/static/jquery-3/', ) readlines_swap = self.swap( run_lint_checks.FileCache, 'readlines', mock_readlines ) with readlines_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_skip_files_in_app_dev_yaml() self.assertEqual(len(error_messages.get_report()), 3) self.assertTrue( 'Pattern on line 2 doesn\'t match any file or directory' in error_messages.get_report()[0] ) self.assertTrue( 'Pattern on line 3 doesn\'t match any file or directory' in error_messages.get_report()[1] ) self.assertTrue(error_messages.failed) def test_check_third_party_libs_type_defs(self) -> None: expected_error_messages = [ 'SUCCESS Third party type defs check passed' ] with self.open_swap, self.listdir_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_third_party_libs_type_defs() self.assertEqual( error_messages.get_report(), expected_error_messages ) self.assertEqual('Third party type defs', error_messages.name) self.assertFalse(error_messages.failed) def test_check_third_party_libs_type_defs_verbose(self) -> None: self.verbose_mode_enabled = True expected_error_messages = [ 'SUCCESS Third party type defs check passed' ] with self.open_swap, self.listdir_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_third_party_libs_type_defs() self.assertEqual( error_messages.get_report(), expected_error_messages ) self.assertEqual('Third party type defs', error_messages.name) self.assertFalse(error_messages.failed) def test_check_third_party_libs_type_defs_multiple(self) -> None: self.files_in_typings_dir.append('guppy-defs-0.2.d.ts') expected_error_messages = 'FAILED Third party type defs check failed' with self.open_swap, self.listdir_swap, self.print_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_third_party_libs_type_defs() self.assertEqual( error_messages.get_report()[1], expected_error_messages ) self.assert_same_list_elements( [ 'There are multiple type definitions for Guppy in the ' 'typings dir.' ], error_messages.get_report(), ) self.assertEqual('Third party type defs', error_messages.name) self.assertTrue(error_messages.failed) def test_check_third_party_libs_type_defs_no_type_defs(self) -> None: self.files_in_typings_dir = [ 'skulpt-defs-0.2.d.ts', 'math-expressions-defs-0.3.d.ts', 'midi-defs-c26eb.d.ts', 'nerdamer-defs-0.6.d.ts', ] expected_error_messages = 'FAILED Third party type defs check failed' with self.open_swap, self.listdir_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_third_party_libs_type_defs() self.assertEqual( error_messages.get_report()[1], expected_error_messages ) self.assert_same_list_elements( [ 'There are no type definitions for Guppy in the ' 'typings dir.' ], error_messages.get_report(), ) self.assertEqual('Third party type defs', error_messages.name) self.assertTrue(error_messages.failed) def test_check_third_party_libs_type_defs_wrong_version(self) -> None: self.files_in_typings_dir = [ 'guppy-defs-0.2.d.ts', 'skulpt-defs-0.2.d.ts', 'math-expressions-defs-0.3.d.ts', 'midi-defs-c26eb.d.ts', 'nerdamer-defs-0.6.d.ts', ] expected_error_messages = 'FAILED Third party type defs check failed' with self.open_swap, self.listdir_swap, self.print_swap: error_messages = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_third_party_libs_type_defs() self.assertEqual( error_messages.get_report()[1], expected_error_messages ) self.assert_same_list_elements( [ 'Type definitions for Guppy are not up to date. The ' 'current version of Guppy is f509e and the type definitions ' 'are for version 0.2. Please refer typings/README.md ' 'for more details.' ], error_messages.get_report(), ) self.assertEqual('Third party type defs', error_messages.name) self.assertTrue(error_messages.failed) def test_check_github_workflows_have_name_checks(self) -> None: def mock_listdir(unused_path: str) -> List[str]: return ['pass.yml', 'fail.yml', 'README'] def mock_read(path: str) -> str: if path.endswith('pass.yml'): return '\n'.join( [ 'name: Passing workflow file', 'on:', ' push:', ' branches:', ' - develop', '', 'jobs:', ' run:', ' steps:', ' - name: Print', ' run: echo "oppia"', ] ) elif path.endswith('fail.yml'): return '\n'.join( [ 'name: Failing workflow file', 'on:', ' push:', ' branches:', ' - develop', '', 'jobs:', ' run:', ' steps:', ' - run: echo "oppia"', ] ) raise AssertionError( 'mock_read called with unexpected path %s' % path ) listdir_swap = self.swap_with_checks( os, 'listdir', mock_listdir, expected_args=[(other_files_linter.WORKFLOWS_DIR,)], ) read_swap = self.swap(FILE_CACHE, 'read', mock_read) expected = [ '%s --> Job run has an unnamed step' % os.path.join(other_files_linter.WORKFLOWS_DIR, 'fail.yml'), 'FAILED Github workflow steps have a name check failed', ] with listdir_swap, read_swap: task_results = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_github_workflows_have_name() self.assertEqual(task_results.get_report(), expected) def test_check_duplicate_method_names_in_user_utilities_no_duplicates( self, ) -> None: def mock_listdir(unused_path: str) -> List[str]: return ['exploration-editor.ts', 'logged-in-user.ts'] def mock_read(path: str) -> str: if path.endswith('exploration-editor.ts'): return '\n'.join( [ 'export class ExplorationEditor extends BaseUser {', ' async addHint(): Promise<void> {}', '}', ] ) elif path.endswith('logged-in-user.ts'): return '\n'.join( [ 'export class LoggedInUser extends BaseUser {', ' async expectToBeOnPage(): Promise<void> {}', '}', ] ) raise AssertionError( 'mock_read called with unexpected path %s' % path ) listdir_swap = self.swap_with_checks( os, 'listdir', mock_listdir, expected_args=[(other_files_linter.PLAYWRIGHT_USER_UTILITIES_DIR,)], ) read_swap = self.swap(FILE_CACHE, 'read', mock_read) expected = [ 'SUCCESS Duplicate method names in user utilities check passed' ] with listdir_swap, read_swap: task_results = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_duplicate_method_names_in_user_utilities() self.assertEqual(task_results.get_report(), expected) self.assertFalse(task_results.failed) def test_check_duplicate_method_names_in_user_utilities_with_duplicates( self, ) -> None: def mock_listdir(unused_path: str) -> List[str]: return ['exploration-editor.ts', 'logged-out-user.ts'] def mock_read(path: str) -> str: if path.endswith('exploration-editor.ts'): return '\n'.join( [ 'export class ExplorationEditor extends BaseUser {', ' async continueToNextCard(): Promise<void> {}', '}', ] ) elif path.endswith('logged-out-user.ts'): return '\n'.join( [ 'export class LoggedOutUser extends BaseUser {', ' async continueToNextCard(): Promise<void> {}', '}', ] ) raise AssertionError( 'mock_read called with unexpected path %s' % path ) listdir_swap = self.swap_with_checks( os, 'listdir', mock_listdir, expected_args=[(other_files_linter.PLAYWRIGHT_USER_UTILITIES_DIR,)], ) read_swap = self.swap(FILE_CACHE, 'read', mock_read) expected = [ 'Method "continueToNextCard" is defined in multiple user ' 'utility files: exploration-editor.ts, logged-out-user.ts. ' 'Rename to disambiguate, following the convention ' '{action}In{PageContext}Page.', 'FAILED Duplicate method names in user utilities check failed', ] with listdir_swap, read_swap: task_results = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_duplicate_method_names_in_user_utilities() self.assertEqual(task_results.get_report(), expected) self.assertTrue(task_results.failed) def test_check_duplicate_method_names_in_user_utilities_ignores_non_ts( self, ) -> None: """Non-.ts files in the directory (e.g. the duplicate-functions report itself) must not be scanned for method names. """ def mock_listdir(unused_path: str) -> List[str]: return ['exploration-editor.ts', 'duplicate-functions.md'] def mock_read(path: str) -> str: if path.endswith('exploration-editor.ts'): return '\n'.join( [ 'export class ExplorationEditor extends BaseUser {', ' async addHint(): Promise<void> {}', '}', ] ) raise AssertionError( 'mock_read called with unexpected path %s' % path ) listdir_swap = self.swap_with_checks( os, 'listdir', mock_listdir, expected_args=[(other_files_linter.PLAYWRIGHT_USER_UTILITIES_DIR,)], ) read_swap = self.swap(FILE_CACHE, 'read', mock_read) expected = [ 'SUCCESS Duplicate method names in user utilities check passed' ] with listdir_swap, read_swap: task_results = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_duplicate_method_names_in_user_utilities() self.assertEqual(task_results.get_report(), expected) self.assertFalse(task_results.failed) def test_check_duplicate_method_names_flags_private_methods_too( self, ) -> None: """Edge case: two different classes with a same-named private method should still be flagged, since the check is name-based across files rather than access-modifier-aware. """ def mock_listdir(unused_path: str) -> List[str]: return ['curriculum-admin.ts', 'topic-manager.ts'] def mock_read(path: str) -> str: if path.endswith('curriculum-admin.ts'): return '\n'.join( [ 'export class CurriculumAdmin extends BaseUser {', ' private async waitForSave(): Promise<void> {}', '}', ] ) elif path.endswith('topic-manager.ts'): return '\n'.join( [ 'export class TopicManager extends BaseUser {', ' private async waitForSave(): Promise<void> {}', '}', ] ) raise AssertionError( 'mock_read called with unexpected path %s' % path ) listdir_swap = self.swap_with_checks( os, 'listdir', mock_listdir, expected_args=[(other_files_linter.PLAYWRIGHT_USER_UTILITIES_DIR,)], ) read_swap = self.swap(FILE_CACHE, 'read', mock_read) with listdir_swap, read_swap: task_results = other_files_linter.CustomLintChecksManager( FILE_CACHE ).check_duplicate_method_names_in_user_utilities() self.assertTrue(task_results.failed) self.assertTrue( any( 'waitForSave' in message for message in task_results.get_report() ) ) def test_perform_all_lint_checks(self) -> None: lint_task_report = other_files_linter.CustomLintChecksManager( FILE_CACHE ).perform_all_lint_checks() self.assertTrue(isinstance(lint_task_report, list)) def test_get_linters_with_success(self) -> None: custom_linter, third_party_linter = other_files_linter.get_linters( FILE_CACHE ) self.assertTrue( isinstance( custom_linter, other_files_linter.CustomLintChecksManager ) ) self.assertEqual(third_party_linter, None)