import os import re import shutil import tempfile import unittest from xml.etree import ElementTree import build import buildtest import coverage def _project_xml(text): return build.parse_project_xml(text.encode("utf-8")) def _repo_path(*parts): return os.path.join(build.REPO_ROOT, *parts) _SAMPLE_COBERTURA = """ """ class ModuleOfTests(unittest.TestCase): def test_returns_the_module_folder_for_a_deeply_nested_project(self): path = _repo_path("Modules", "Blacklight", "Core", "Core.UnitTests", "Blacklight.Core.UnitTests.csproj") self.assertEqual(_repo_path("Modules", "Blacklight"), coverage.module_of(path)) def test_returns_the_game_folder_for_a_game_project(self): path = _repo_path("Games", "ElectricRootPrototype", "ElectricRootPrototype", "ElectricRootPrototype.csproj") self.assertEqual(_repo_path("Games", "ElectricRootPrototype"), coverage.module_of(path)) class ReadPropertiesTests(unittest.TestCase): def test_keeps_the_first_definition_of_a_property(self): xml = _project_xml("first" "second") self.assertEqual("first", coverage.read_properties(xml)["OutputPath"]) def test_skips_an_empty_definition_in_favor_of_a_later_one(self): xml = _project_xml(" " "second") self.assertEqual("second", coverage.read_properties(xml)["OutputPath"]) class ExpandPropertiesTests(unittest.TestCase): def test_expands_references_inside_other_properties_values(self): properties = {"OutputPath": r"$(Root)\Bin", "Root": "C:"} self.assertEqual(r"C:\Bin\x.exe", coverage.expand_properties(r"$(OutputPath)\x.exe", properties)) def test_raises_naming_an_undefined_property(self): with self.assertRaisesRegex(ValueError, re.escape("$(Missing)")): coverage.expand_properties("$(Missing)", {}) def test_raises_for_a_self_referencing_property(self): with self.assertRaisesRegex(ValueError, "without end"): coverage.expand_properties("$(Loop)", {"Loop": "$(Loop)"}) class FindTestCommandTests(unittest.TestCase): def test_finds_a_quoted_post_build_event_running_the_target(self): xml = _project_xml('"$(TargetPath)"' '') self.assertEqual('"$(TargetPath)"', coverage.find_test_command(xml)) def test_finds_an_unquoted_post_build_event_with_arguments(self): xml = _project_xml('$(TargetPath) "$(ProjectDir)Shaders"' '') self.assertEqual('$(TargetPath) "$(ProjectDir)Shaders"', coverage.find_test_command(xml)) def test_finds_an_exec_running_the_exe_after_build(self): xml = _project_xml('' '') self.assertEqual('"$(TargetDir)$(AssemblyName).exe"', coverage.find_test_command(xml)) def test_ignores_an_exec_in_a_target_not_run_after_build(self): xml = _project_xml('' '') self.assertIsNone(coverage.find_test_command(xml)) def test_ignores_a_post_build_event_running_something_else(self): xml = _project_xml('echo $(TargetPath)>out.txt' '') self.assertIsNone(coverage.find_test_command(xml)) def test_ignores_a_target_path_prefix_of_a_longer_property(self): xml = _project_xml('$(TargetPathWithTargetPlatformMoniker)' '') self.assertIsNone(coverage.find_test_command(xml)) def test_returns_none_for_a_project_with_no_build_events(self): self.assertIsNone(coverage.find_test_command(_project_xml(""))) class MakeSuiteTests(unittest.TestCase): _CSPROJ_PATH = _repo_path("Modules", "Glass", "Glass.UnitTests", "Glass.UnitTests.csproj") def test_locates_the_executable_through_the_output_path(self): xml = _project_xml(r'$(SourceRoot)\Junk\$(AssemblyName)\$(Configuration)\\' r'"$(TargetPath)"') suite = coverage.make_suite("Glass.UnitTests", self._CSPROJ_PATH, xml) expected = _repo_path("Junk", "Glass.UnitTests", "Release", "Glass.UnitTests.exe") self.assertEqual(expected, suite.executable) self.assertEqual([expected], suite.command) def test_resolves_an_output_path_relative_to_the_project(self): xml = _project_xml(r'bin\$(Configuration)' r'"$(TargetPath)"') suite = coverage.make_suite("Glass.UnitTests", self._CSPROJ_PATH, xml) self.assertEqual(_repo_path("Modules", "Glass", "Glass.UnitTests", "bin", "Release", "Glass.UnitTests.exe"), suite.executable) def test_expands_and_unquotes_the_arguments(self): xml = _project_xml(r'bin' r'"$(TargetPath)" "$(ProjectDir)My Shaders"' r'') suite = coverage.make_suite("Glass.UnitTests", self._CSPROJ_PATH, xml) self.assertEqual(_repo_path("Modules", "Glass", "Glass.UnitTests") + os.sep + "My Shaders", suite.command[1]) def test_belongs_to_the_module_its_project_lives_in(self): xml = _project_xml(r'bin' r'"$(TargetPath)"') suite = coverage.make_suite("Glass.UnitTests", self._CSPROJ_PATH, xml) self.assertEqual(_repo_path("Modules", "Glass"), suite.module) def test_returns_none_for_a_project_whose_build_runs_no_tests(self): xml = _project_xml("bin") self.assertIsNone(coverage.make_suite("Glass", self._CSPROJ_PATH, xml)) class ModuleAssemblyNamesTests(unittest.TestCase): def test_returns_the_modules_projects_except_its_suites(self): glass = _repo_path("Modules", "Glass") projects = { "Glass": build.Project("Glass", os.path.join(glass, "Glass"), "Glass.csproj"), "Glass.Blacklight": build.Project("Glass.Blacklight", os.path.join(glass, "Glass.Blacklight"), "Glass.Blacklight.csproj"), "Glass.UnitTests": build.Project("Glass.UnitTests", os.path.join(glass, "Glass.UnitTests"), "Glass.UnitTests.csproj"), "Amphora": build.Project("Amphora", _repo_path("Modules", "Amphora", "Amphora"), "Amphora.csproj"), } self.assertEqual(["Glass", "Glass.Blacklight"], coverage.module_assembly_names(projects, glass, {"Glass.UnitTests"})) class SettingsXmlTests(unittest.TestCase): def _pattern(self, assembly_names): return ElementTree.fromstring(coverage.settings_xml(assembly_names)).findtext( "CodeCoverage/ModulePaths/Include/ModulePath") def test_matches_a_named_dll_or_exe_in_any_directory(self): pattern = self._pattern(["Glass", "ElectricRootPrototype"]) self.assertTrue(re.match(pattern, r"C:\ElectricRoot\Junk\Glass.UnitTests\Release\Glass.dll")) self.assertTrue(re.match(pattern, r"C:\ElectricRoot\Junk\X\Release\ElectricRootPrototype.exe")) def test_rejects_an_assembly_that_merely_starts_with_a_name(self): pattern = self._pattern(["Glass"]) self.assertFalse(re.match(pattern, r"C:\ElectricRoot\Bin\Release\Glass.UnitTests.dll")) def test_escapes_the_dots_in_a_name(self): pattern = self._pattern(["Glass.Blacklight"]) self.assertFalse(re.match(pattern, r"C:\ElectricRoot\Bin\Release\GlassXBlacklight.dll")) class IsPinnedPackageTests(unittest.TestCase): def test_rejects_any_other_bytes(self): self.assertFalse(coverage.is_pinned_package(b"not the package")) _X64_ONLY_COBERTURA = """ """ class UnionTests(unittest.TestCase): def union(self): return coverage.union([ElementTree.fromstring(_SAMPLE_COBERTURA), ElementTree.fromstring(_X64_ONLY_COBERTURA)]) def label_lines(self, root): return {line.get("number"): line for line in root.find( "packages/package[@name='Glass']/classes/class[@name='Glass.Label']").findall("lines/line")} def test_hits_add_up_so_a_line_either_ran_counts(self): self.assertEqual("2", self.label_lines(self.union())["11"].get("hits")) def test_a_line_only_one_has_is_kept(self): self.assertEqual("1", self.label_lines(self.union())["12"].get("hits")) def test_a_lines_branches_taken_are_the_most_either_took(self): line = self.label_lines(self.union())["10"] self.assertEqual("True", line.get("branch")) self.assertEqual((1, 2), coverage.condition_counts(line)) def test_a_line_with_no_branches_says_so(self): line = self.label_lines(self.union())["11"] self.assertEqual("False", line.get("branch")) self.assertIsNone(line.get("condition-coverage")) def test_a_method_that_ran_in_either_is_covered(self): methods = coverage.uncovered_methods(self.union(), ["Glass"]) self.assertEqual(["get_Text"], [method.method_name for method in methods]) def test_keeps_every_package_class_and_method_either_has(self): summaries = coverage.summarize(self.union()) self.assertEqual([coverage.AssemblyCoverage("Glass", lines_covered=4, lines_valid=5, branches_covered=3, branches_valid=4, methods_covered=2, methods_total=4), coverage.AssemblyCoverage("Glass.Blacklight", lines_covered=0, lines_valid=1, branches_covered=0, branches_valid=0, methods_covered=0, methods_total=1)], summaries) def test_one_document_is_unchanged_in_what_it_covers(self): self.assertEqual(coverage.summarize(ElementTree.fromstring(_SAMPLE_COBERTURA)), coverage.summarize(coverage.union([ElementTree.fromstring(_SAMPLE_COBERTURA)]))) def test_keeps_method_signatures(self): method = self.union().find(".//method[@name='Draw']") self.assertEqual("(Glass.Canvas)", method.get("signature")) class OtherBitnessCommandTests(unittest.TestCase): def test_points_every_argument_naming_the_exe_at_the_copy(self): self.assertEqual([r"C:\Out\Suite.x64.exe", r"C:\Shaders", r"C:\Out\Suite.x64.exe"], coverage.other_bitness_command([r"C:\Out\Suite.exe", r"C:\Shaders", r"c:\out\SUITE.EXE"], r"C:\Out\Suite.exe", r"C:\Out\Suite.x64.exe")) class MakeOtherBitnessCopyTests(unittest.TestCase): def setUp(self): self.directory = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.directory) self.executable = os.path.join(self.directory, "Suite.exe") def write_exe(self, flags): with open(self.executable, "wb") as file: file.write(buildtest._make_image(flags)) def test_copies_the_exe_flipped_to_the_other_bitness(self): self.write_exe(buildtest._any_cpu_preferring_32bit()) label, copy_path = coverage.make_other_bitness_copy(self.executable) self.assertEqual("x64", label) self.assertEqual(os.path.join(self.directory, "Suite.x64.exe"), copy_path) with open(copy_path, "rb") as file: self.assertEqual(buildtest._make_image(build.CLR_ILONLY), file.read()) def test_brings_the_config_along(self): self.write_exe(buildtest._any_cpu_preferring_32bit()) with open(self.executable + ".config", "w", encoding="utf-8") as file: file.write("") label, copy_path = coverage.make_other_bitness_copy(self.executable) with open(copy_path + ".config", encoding="utf-8") as file: self.assertEqual("", file.read()) def test_leaves_the_original_alone(self): self.write_exe(buildtest._any_cpu_preferring_32bit()) coverage.make_other_bitness_copy(self.executable) with open(self.executable, "rb") as file: self.assertEqual(buildtest._make_image(buildtest._any_cpu_preferring_32bit()), file.read()) def test_an_exe_pinned_to_one_bitness_gets_no_copy(self): self.write_exe(build.CLR_ILONLY | build.CLR_32BIT_REQUIRED) self.assertIsNone(coverage.make_other_bitness_copy(self.executable)) self.assertEqual(["Suite.exe"], os.listdir(self.directory)) class SummarizeTests(unittest.TestCase): def test_counts_lines_branches_and_methods_per_assembly(self): summaries = coverage.summarize(ElementTree.fromstring(_SAMPLE_COBERTURA)) self.assertEqual([coverage.AssemblyCoverage("Glass", lines_covered=1, lines_valid=3, branches_covered=1, branches_valid=2, methods_covered=1, methods_total=3), coverage.AssemblyCoverage("Glass.Blacklight", lines_covered=0, lines_valid=1, branches_covered=0, branches_valid=0, methods_covered=0, methods_total=1)], summaries) class UncoveredMethodsTests(unittest.TestCase): def test_lists_only_methods_with_lines_none_of_which_ran(self): methods = coverage.uncovered_methods(ElementTree.fromstring(_SAMPLE_COBERTURA), ["Glass"]) self.assertEqual([coverage.UncoveredMethod(r"C:\ElectricRoot\Modules\Glass\Glass\Label.cs", 20, "Glass.Label", "get_Text", "()")], methods) def test_skips_assemblies_not_named(self): methods = coverage.uncovered_methods(ElementTree.fromstring(_SAMPLE_COBERTURA), ["Glass.Blacklight"]) self.assertEqual(["Flush"], [method.method_name for method in methods]) class UnloadedLibrariesTests(unittest.TestCase): def test_returns_the_measured_modules_libraries_missing_from_the_results(self): oculus = _repo_path("Modules", "Oculus") projects = { "Oculus": build.Project("Oculus", os.path.join(oculus, "Oculus"), "Oculus.csproj"), "Oculus.Measured": build.Project("Oculus.Measured", os.path.join(oculus, "Measured"), "Measured.csproj"), "Oculus.Testbed": build.Project("Oculus.Testbed", os.path.join(oculus, "Testbed"), "Testbed.csproj"), "Amphora": build.Project("Amphora", _repo_path("Modules", "Amphora", "Amphora"), "Amphora.csproj"), } for name in ("Oculus", "Oculus.Measured", "Amphora"): projects[name].is_library = True summaries = [coverage.AssemblyCoverage("Oculus.Measured", 1, 1, 0, 0, 1, 1)] self.assertEqual(["Oculus"], coverage.unloaded_libraries(projects, [oculus], summaries)) class FormatRatioTests(unittest.TestCase): def test_shows_the_percentage_and_counts(self): self.assertEqual(" 50.0% (1/2)", coverage.format_ratio(1, 2)) def test_shows_a_dash_when_there_is_nothing_to_cover(self): self.assertEqual("-", coverage.format_ratio(0, 0)) if __name__ == '__main__': unittest.main()