import json import os import unittest from TodoParser import TodoParser def count_issues_for_file_type(raw_issues, file_type): num_issues = 0 for issue in raw_issues: if issue.markdown_language == file_type: num_issues += 1 return num_issues def get_issues_for_fields(raw_issues, fields): matching_issues = [] for issue in raw_issues: for key in fields.keys(): if getattr(issue, key) != fields.get(key): break else: matching_issues.append(issue) return matching_issues def print_unexpected_issues(unexpected_issues): return '\n'.join([ '', 'Unexpected issues:', '\n=========================\n'.join(map(str, unexpected_issues))]) class NewIssueTest(unittest.TestCase): # Check for newly added TODOs across the files specified. def setUp(self): parser = TodoParser() self.raw_issues = [] with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) with open('tests/test_new.diff', 'r') as diff_file: self.raw_issues.extend(parser.parse(diff_file)) with open('tests/test_new2.diff', 'r') as diff_file: self.raw_issues.extend(parser.parse(diff_file)) with open('tests/test_edit.diff', 'r') as diff_file: self.raw_issues.extend(parser.parse(diff_file)) def test_python_issues(self): # Includes 4 tests for Starlark. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 7) def test_yaml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'yaml'), 2) def test_toml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'toml'), 2) def test_php_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'php'), 4) def test_java_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'java'), 2) def test_javascript_issues(self): # Includes 1 test for JSON with Comments, 1 test for JSON5, 3 tests for TSX. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'javascript'), 5) def test_ruby_issues(self): # Includes 2 tests for Crystal. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'ruby'), 5) def test_abap_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'abap'), 2) def test_sql_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'sql'), 1) def test_tex_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'tex'), 2) def test_julia_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'julia'), 2) def test_starlark_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 7) def test_autohotkey_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'autohotkey'), 1) def test_handlebars_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'handlebars'), 2) def test_text_issues(self): # Includes 2 tests for Org, 2 tests for GAP, 2 tests for Visual Basic, 2 tests for Agda, 4 tests for Sol, 4 tests for Move self.assertEqual(count_issues_for_file_type(self.raw_issues, 'text'), 16) def test_scss_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'scss'), 2) def test_twig_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'twig'), 2) def test_makefile_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'makefile'), 3) def test_md_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'markdown'), 8) def test_r_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'r'), 2) def test_haskell_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'haskell'), 4) def test_clojure_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'clojure'), 2) def test_nix_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'nix'), 2) def test_xaml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'xml'), 2) def test_c_cpp_like_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'c_cpp'), 2) def test_liquid_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'liquid'), 3) def test_lua_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'lua'), 2) def test_dockerfile_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'dockerfile'), 1) class CustomOptionsTest(unittest.TestCase): def setUp(self): parser = TodoParser(options={"identifiers": [{"name": "FIX", "labels": []}, {"name": "[TODO]", "labels": []}, {"name": "TODO", "labels": []} ]}) self.raw_issues = [] with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) with open('tests/test_new.diff', 'r') as diff_file: self.raw_issues.extend(parser.parse(diff_file)) def test_exact_identifier_match(self): """ Verify that issues are only created when there's an exact identifier match Other than case-insensitivity, an issue should only be matched if the identifier is exactly within the list of identifiers. For instances, if "FIX" is an identifier, it should NOT accidentaly match comments with the words "suffix" or "prefix". """ matching_issues = get_issues_for_fields(self.raw_issues, { "file_name": "example_file.py", "identifier": "FIX" }) self.assertEqual(len(matching_issues), 0, msg=print_unexpected_issues(matching_issues)) # See GitHub issue #242 def test_regex_identifier_chars(self): """ Verify that the presence of regex characters in the identifier doesn't confuse the parser An identifier such as "[TODO]" should be matched literally, not treating the "[" and "]" characters as part of a regular expression pattern. """ matching_issues = get_issues_for_fields(self.raw_issues, { "file_name": "example_file.py", "identifier": "[TODO]" }) self.assertEqual(len(matching_issues), 1, msg=print_unexpected_issues(matching_issues)) # See GitHub issue #235 @unittest.expectedFailure def test_multiple_identifiers(self): """ Verify that issues by matching the first identifier on the line Issues should be identified such that the priority is where the identifier is found within the comment line, which is not necessarily the order they're specified in the identifier dictionary. For instance, if the dictionary is [{"name": "FIX", "labels": []}, {"name": "TODO", "labels": []}]}) then a comment line such as # TODO: Fix this should match because of the "TODO", not because of the "Fix". This is not a trivial difference. If it matches for the "TODO", then the title will be "Fix this", but if it matches for the "Fix", then the title will erroneously be just "this". """ matching_issues = get_issues_for_fields(self.raw_issues, { "file_name": "init.lua", "identifier": "FIX" }) self.assertEqual(len(matching_issues), 0, msg=print_unexpected_issues(matching_issues)) matching_issues = get_issues_for_fields(self.raw_issues, { "file_name": "init.lua", "identifier": "TODO" }) self.assertEqual(len(matching_issues), 2, msg=print_unexpected_issues(matching_issues)) class ClosedIssueTest(unittest.TestCase): # Check for removed TODOs across the files specified. def setUp(self): diff_file = open('tests/test_closed.diff', 'r') parser = TodoParser() with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) self.raw_issues = parser.parse(diff_file) def test_python_issues(self): # Includes 1 test for Starlark. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 5) def test_yaml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'yaml'), 2) def test_toml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'toml'), 2) def test_php_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'php'), 4) def test_java_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'java'), 2) def test_ruby_issues(self): # Includes 2 tests for Crystal. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'ruby'), 5) def test_abap_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'abap'), 2) def test_sql_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'sql'), 1) def test_tex_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'tex'), 2) def test_julia_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'julia'), 4) def test_starlark_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 5) def test_javascript_issues(self): # Includes 1 test for JSON with Comments, 1 test for JSON5, 3 tests for TSX. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'javascript'), 5) def test_autohotkey_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'autohotkey'), 1) def test_handlebars_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'handlebars'), 2) def test_text_issues(self): # Includes 2 tests for Org, 2 tests for GAP, 2 tests for Visual Basic, 2 tests for Agda, 4 tests for Sol, 4 tests for Move self.assertEqual(count_issues_for_file_type(self.raw_issues, 'text'), 16) def test_scss_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'scss'), 2) def test_twig_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'twig'), 2) def test_makefile_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'makefile'), 3) def test_md_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'markdown'), 8) def test_r_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'r'), 2) def test_haskell_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'haskell'), 4) def test_clojure_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'clojure'), 2) def test_nix_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'nix'), 2) def test_xaml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'xml'), 2) def test_c_cpp_like_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'c_cpp'), 2) def test_liquid_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'liquid'), 3) def test_lua_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'lua'), 2) def test_dockerfile_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'dockerfile'), 1) class IgnorePatternTest(unittest.TestCase): def test_single_ignore(self): os.environ['INPUT_IGNORE'] = '.*\\.java' parser = TodoParser() with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) diff_file = open('tests/test_closed.diff', 'r') self.raw_issues = parser.parse(diff_file) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 5) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'yaml'), 2) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'php'), 4) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'java'), 0) # Includes 2 tests for Crystal. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'ruby'), 5) os.environ['INPUT_IGNORE'] = '' def test_multiple_ignores(self): os.environ['INPUT_IGNORE'] = '.*\\.java, tests/example-file\\.php' parser = TodoParser() with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) diff_file = open('tests/test_closed.diff', 'r') self.raw_issues = parser.parse(diff_file) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'python'), 5) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'yaml'), 2) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'php'), 0) self.assertEqual(count_issues_for_file_type(self.raw_issues, 'java'), 0) # Includes 2 tests for Crystal. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'ruby'), 5) os.environ['INPUT_IGNORE'] = '' class EscapeMarkdownTest(unittest.TestCase): def test_simple_escape(self): os.environ['INPUT_ESCAPE'] = 'true' parser = TodoParser() with open('syntax.json', 'r') as syntax_json: parser.syntax_dict = json.load(syntax_json) diff_file = open('tests/test_escape.diff', 'r') # I had no other idea to make these checks dynamic. self.raw_issues = parser.parse(diff_file) self.assertEqual(len(self.raw_issues), 2) issue = self.raw_issues[0] self.assertEqual(len(issue.body), 2) self.assertEqual(issue.body[0], '\\# Some title') self.assertEqual(issue.body[1], '\\') issue = self.raw_issues[1] self.assertEqual(len(issue.body), 2) self.assertEqual(issue.body[0], '\\# Another title') self.assertEqual(issue.body[1], '\\') class BaseCustomLanguageTests: class BaseTest(unittest.TestCase): @staticmethod def count_syntax(parser: TodoParser, name: str): counter = 0 for syntax in parser.syntax_dict: if syntax['language'] == name: counter = counter + 1 return counter class CustomLanguageFileTest(BaseCustomLanguageTests.BaseTest): def setUp(self): os.environ['INPUT_LANGUAGES'] = 'tests/custom_languages.json' self.parser = TodoParser() def test_custom_lang_load(self): # Test if the custom language ILS is actually loaded into the system self.assertIsNotNone(self.parser.languages_dict['ILS']) self.assertEqual(self.count_syntax(self.parser, 'ILS'), 1) def test_custom_lang_not_dupplicate(self): # Test if a custom language can overwrite the rules of an existing one self.assertEqual(self.count_syntax(self.parser, 'Java'), 1) for syntax in self.parser.syntax_dict: if syntax['language'] == 'Java': self.assertEqual(len(syntax['markers']), 2) self.assertEqual(syntax['markers'][0]['pattern'], "////") self.assertEqual(syntax['markers'][1]['pattern']['start'], '+=') self.assertEqual(syntax['markers'][1]['pattern']['end'], '=+') break self.assertIsNotNone(self.parser.languages_dict['Java']) self.assertEqual(len(self.parser.languages_dict['Java']['extensions']), 1) self.assertEqual(self.parser.languages_dict['Java']['extensions'][0], ".java2") def tearDown(self): del os.environ['INPUT_LANGUAGES'] class CustomLanguageUrlTest(BaseCustomLanguageTests.BaseTest): def setUp(self): os.environ['INPUT_LANGUAGES'] = 'https://raw.githubusercontent.com/alstr/todo-to-issue-action/master/tests/custom_languages.json' os.environ['INPUT_NO_STANDARD'] = 'true' self.parser = TodoParser() def test_url_load(self): self.assertEqual(len(self.parser.languages_dict), 2) self.assertEqual(len(self.parser.syntax_dict), 2) def tearDown(self): del os.environ['INPUT_LANGUAGES'] del os.environ['INPUT_NO_STANDARD']