From bce53e6416e88f0652af85fb16f76ae708d5e657 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Mon, 8 Apr 2024 19:41:51 +0300 Subject: [PATCH 01/18] test: add table information about coverage of each classes at library with python-script and update yml-script for it --- .github/workflows/build-test.yml | 3 + lib/build.gradle.kts | 23 ++-- scripts/csv-reports-printer.py | 183 +++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 scripts/csv-reports-printer.py diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 35a2c6a..c98aaf8 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,3 +20,6 @@ jobs: - name: Run tests with jacoco run: ./gradlew test + + - name: Display info about coverage + run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 48a49ca..47511d0 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -4,9 +4,6 @@ plugins { // Code coverage plugin jacoco - - // Output project coverage - id("org.barfuin.gradle.jacocolog") version "3.1.0" } repositories { @@ -17,6 +14,8 @@ repositories { dependencies { // This dependency is exported to consumers, that is to say found on their compile classpath. api(libs.commons.math3) + // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2") // This dependency is used internally, and not exposed to consumers on their own compile classpath. @@ -30,16 +29,6 @@ java { } } -testing { - suites { - // Configure the built-in test suite - val test by getting(JvmTestSuite::class) { - // Use Kotlin Test framework - useKotlinTest("1.9.20") - } - } -} - tasks.named("test") { useJUnitPlatform() finalizedBy(tasks.jacocoTestReport) @@ -53,9 +42,13 @@ tasks.withType { tasks.named("jacocoTestReport") { dependsOn(tasks.test) + val sep = File.separator + val jacocoReportsDirName = "reports${sep}jacoco" reports { - csv.required = false + csv.required = true xml.required = false - html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + html.required = true + csv.outputLocation = layout.buildDirectory.file("${jacocoReportsDirName}${sep}info.csv") + html.outputLocation = layout.buildDirectory.dir("${jacocoReportsDirName}${sep}html") } } diff --git a/scripts/csv-reports-printer.py b/scripts/csv-reports-printer.py new file mode 100644 index 0000000..4ed3c55 --- /dev/null +++ b/scripts/csv-reports-printer.py @@ -0,0 +1,183 @@ +import argparse +import csv +import sys +import typing + +COLUMNS_TYPES = [ + '_MISSED', + '_COVERED', +] + +CSV_COLUMNS = [ + 'PACKAGES', + 'CLASS', + 'INSTRUCTION_MISSED', + 'INSTRUCTION_COVERED', + 'BRANCH_MISSED', + 'BRANCH_COVERED', + 'LINE_MISSED', + 'LINE_COVERED', + 'COMPLEXITY_MISSED', + 'COMPLEXITY_COVERED', + 'METHOD_MISSED', + 'METHOD_COVERED', +] +DISPLAY_COLUMNS = [ + 'PACKAGES', + 'CLASS', + 'INSTRUCTION', + 'BRANCH', + 'LINE', + 'COMPLEXITY', + 'METHOD', +] +DEFAULT_LABEL_SIZE = 15 + + +def create_row_info() -> dict: + return { + key: 0 for key in CSV_COLUMNS + } + + +def is_valid_lib(group: str, lib_name: str) -> bool: + if len(lib_name) == 0: + return True + return group == lib_name + + +def parse_args(args: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="CSV Jacoco Test Reports Printer", + description="Program read csv file with jacoco report and print it at terminal at stdin", + ) + + parser.add_argument( + "-i", "--input", required=True, + help="setup path to CSV file with jacoco report information", + metavar="" + ) + parser.add_argument( + "-l", "--lib", + help="setup library name to remove from package path", + default="", + metavar="" + ) + parser.add_argument( + "-p", "--package-print", + help="setup flag to 'ON' to print packages of files at report (default 'OFF')", + action='store_true', + default=False + ) + + return parser.parse_args(args) + + +def read_csv(namespace: argparse.Namespace) -> typing.Optional[dict]: + table = [] + max_packages_name_length = 20 + max_classes_name_length = 20 + + with open(getattr(namespace, "input"), 'r') as file: + reader = csv.reader(file) + for row in reader: + if len(row) == 0: + break + if (row[0] == "GROUP") or not is_valid_lib(row[0], getattr(namespace, "lib", "")): + continue + + row_info = create_row_info() + is_skipped = False + for key in row_info.keys(): + if key not in CSV_COLUMNS: + row_info.pop(key) + + index = CSV_COLUMNS.index(key) + 1 + row_info[key] = row[index] + + if key == "PACKAGES": + max_packages_name_length = max(max_packages_name_length, len(row_info[key])) + elif key == "CLASS": + if '(' in row_info[key] or ')' in row_info[key] or ' ' in row_info[key]: + is_skipped = True + break + max_classes_name_length = max(max_classes_name_length, len(row_info[key])) + + if not is_skipped: + table.append(row_info) + return { + "table": table, + "max_packages_name_length": max_packages_name_length, + "max_classes_name_length": max_classes_name_length + } + + +def create_label(text: str, lbl_size: int): + if len(text) >= lbl_size: + return '| ' + text[:lbl_size] + ' |' + + return f'| {{:^{lbl_size}}} |'.format(text) + + +def display_columns(max_packages_name_length: int, max_classes_name_length: int) -> int: + global DISPLAY_COLUMNS, DEFAULT_LABEL_SIZE + result_length = 0 + for column in DISPLAY_COLUMNS: + lbl_size = DEFAULT_LABEL_SIZE + if column == "CLASS": + lbl_size = max_classes_name_length + elif column == "PACKAGES": + lbl_size = max_packages_name_length + + lbl = create_label(column, lbl_size) + result_length += lbl_size + print(lbl, end="") + print() + return result_length + + +def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None: + global DISPLAY_COLUMNS, COLUMNS_TYPES + if not getattr(namespace, "package_print", False): + DISPLAY_COLUMNS.remove("PACKAGES") + + if getattr(namespace, 'lib'): + print(f"Jacoco Covered Report Info for module named '{getattr(namespace, 'lib')}':") + + max_packages_name_length = csv_data_dict.get("max_packages_name_length", 20) + max_classes_name_length = csv_data_dict.get("max_classes_name_length", 20) + table: list[dict] = csv_data_dict.get("table", []) + result_length: int = display_columns(max_packages_name_length, max_classes_name_length) + + for row in table: + for column in DISPLAY_COLUMNS: + lbl = "" + if column in ["PACKAGES", "CLASS"]: + lbl = create_label( + row[column], + max_packages_name_length if column == "PACKAGES" else max_classes_name_length + ) + else: + vals = [int(row[column + _type]) for _type in COLUMNS_TYPES] + lbl = create_label( + f"{int(round((vals[1] - vals[0]) / vals[1], 2) * 100)}%" if vals[1] != 0 else "100%", + DEFAULT_LABEL_SIZE + ) + + print(lbl, end="") + print() + + +if __name__ == "__main__": + ns = parse_args(sys.argv[1:]) + + try: + csv_data = read_csv(ns) + except Exception as e: + print( + f"Can't read csv file: '{getattr(ns, 'input')}', get exception: '{e}'", + file=sys.stderr + ) + sys.exit(-1) + + display_csv_data(ns, csv_data) From 052e2bca289a61491ea39cfac202415df565d7ba Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Mon, 8 Apr 2024 20:04:39 +0300 Subject: [PATCH 02/18] refactor: update format of coverage information --- scripts/csv-reports-printer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/csv-reports-printer.py b/scripts/csv-reports-printer.py index 4ed3c55..9898919 100644 --- a/scripts/csv-reports-printer.py +++ b/scripts/csv-reports-printer.py @@ -11,27 +11,21 @@ CSV_COLUMNS = [ 'PACKAGES', 'CLASS', - 'INSTRUCTION_MISSED', - 'INSTRUCTION_COVERED', 'BRANCH_MISSED', 'BRANCH_COVERED', 'LINE_MISSED', 'LINE_COVERED', - 'COMPLEXITY_MISSED', - 'COMPLEXITY_COVERED', 'METHOD_MISSED', 'METHOD_COVERED', ] DISPLAY_COLUMNS = [ 'PACKAGES', 'CLASS', - 'INSTRUCTION', 'BRANCH', 'LINE', - 'COMPLEXITY', 'METHOD', ] -DEFAULT_LABEL_SIZE = 15 +DEFAULT_LABEL_SIZE = 8 def create_row_info() -> dict: @@ -81,7 +75,7 @@ def read_csv(namespace: argparse.Namespace) -> typing.Optional[dict]: with open(getattr(namespace, "input"), 'r') as file: reader = csv.reader(file) for row in reader: - if len(row) == 0: + if len(row) == 0: break if (row[0] == "GROUP") or not is_valid_lib(row[0], getattr(namespace, "lib", "")): continue @@ -101,6 +95,9 @@ def read_csv(namespace: argparse.Namespace) -> typing.Optional[dict]: if '(' in row_info[key] or ')' in row_info[key] or ' ' in row_info[key]: is_skipped = True break + elif '.' in row_info[key]: + row_info[key] = row_info[key].split('.')[-1] + max_classes_name_length = max(max_classes_name_length, len(row_info[key])) if not is_skipped: @@ -116,6 +113,9 @@ def create_label(text: str, lbl_size: int): if len(text) >= lbl_size: return '| ' + text[:lbl_size] + ' |' + if len(text) % 2 != 0: + text = ' ' + text + return f'| {{:^{lbl_size}}} |'.format(text) From 68ece3f4b09be12c44f92cc77bd255a03b86d80e Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 01:10:39 +0300 Subject: [PATCH 03/18] refactor: update test gradle task and change logging of it. Update information of ParametrizedTests by changing display name --- lib/build.gradle.kts | 10 ++++------ .../tree_tripper/binary_trees/AVLTreeTest.kt | 12 ++++++------ .../kotlin/tree_tripper/binary_trees/BSTreeTest.kt | 14 ++++++++++---- .../iterators/BinarySearchTreeIteratorTest.kt | 8 ++++---- .../test/kotlin/tree_tripper/nodes/UtilsTest.kt | 4 ++-- .../nodes/binary_nodes/AVLTreeNodeTest.kt | 2 +- .../nodes/binary_nodes/RBTreeNodeTest.kt | 8 ++++---- 7 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 47511d0..d8e9d90 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -31,12 +31,10 @@ java { tasks.named("test") { useJUnitPlatform() - finalizedBy(tasks.jacocoTestReport) -} - -tasks.withType { testLogging { - events("PASSED", "SKIPPED", "FAILED") + showCauses = false + showStackTraces = false + showExceptions = false } } @@ -51,4 +49,4 @@ tasks.named("jacocoTestReport") { csv.outputLocation = layout.buildDirectory.file("${jacocoReportsDirName}${sep}info.csv") html.outputLocation = layout.buildDirectory.dir("${jacocoReportsDirName}${sep}html") } -} +} \ No newline at end of file diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt index 124e62e..5296d7c 100644 --- a/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt +++ b/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt @@ -26,42 +26,42 @@ class AVLTreeTest { Assertions.assertEquals(0, tree.size) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeCreationCases") @DisplayName("node creation") public fun testNodeCreation(key: Int, value: Int) { tree.assertNodeCreation(key, value) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testBalanceTreeCases") @DisplayName("check balance tree") public fun testBalanceTree(expected: AVLTreeNode, node: AVLTreeNode) { tree.assertBalanceTree(expected, node) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("checkBalanceFactor") @DisplayName("balance factor") public fun checkBalanceFactor(expected: Int, node: AVLTreeNode?) { tree.assertBalanceFactor(expected, node) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testBalanceCases") @DisplayName("balance case") public fun testBalanceCase(expected: AVLTreeNode, node: AVLTreeNode) { tree.assertBalance(expected, node) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeRotateLeftCases") @DisplayName("node rotate left case") public fun testNodeRotateLeftCases(expected: AVLTreeNode, node: AVLTreeNode) { tree.assertNodeLeftRotation(expected, node) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeRotateRightCases") @DisplayName("node rotate right case") public fun testNodeRotateRightCase(expected: AVLTreeNode, node: AVLTreeNode) { diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt index 13da642..f3a0a24 100644 --- a/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt +++ b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt @@ -12,6 +12,7 @@ import tree_tripper.iterators.IterationOrders import java.time.Duration import kotlin.random.Random import kotlin.test.Test +import kotlin.test.assertEquals public class BSTreeTest { @@ -22,6 +23,11 @@ public class BSTreeTest { tree = BSTreeTestAssistant() } + @Test + public fun maximkaTop() { + assertEquals("a", "asdf") + } + @Test @DisplayName("tree initialization") public fun testTreeInitialization() { @@ -59,7 +65,7 @@ public class BSTreeTest { Assertions.assertEquals(tree.getRoot(), Pair(1, 0), "Incorrect change root") } - @Test + @Test() @DisplayName("insert children root") public fun testInsertChildrenRoot() { tree.insert(2, -2) @@ -69,7 +75,7 @@ public class BSTreeTest { Assertions.assertEquals(tree.size, 3, "Incorrect resizing tree size.") } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("getSizeAndTimeArguments") @DisplayName("insert with size and time") public fun testInsertWithSizeAndTime(size: Int, seconds: Long) { @@ -115,7 +121,7 @@ public class BSTreeTest { Assertions.assertEquals(tree.search(0), null, "Incorrect search a non-existent child root.") } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("getSizeAndTimeArguments") @DisplayName("search with size and time") public fun testSearchWithSizeAndTime(size: Int, seconds: Long) { @@ -285,7 +291,7 @@ public class BSTreeTest { Assertions.assertEquals(tree.search(3), -3, "Incorrect remove a root and lose the right child.") } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("getSizeAndTimeArguments") @DisplayName("remove with size and time") public fun testRemoveWithSizeAndTime(size: Int, seconds: Long) { diff --git a/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt b/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt index 0cbf597..3ac0388 100644 --- a/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt +++ b/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt @@ -11,7 +11,7 @@ import tree_tripper.nodes.binary_nodes.BSTreeNode class BinarySearchTreeIteratorTest { lateinit var iterator: BinarySearchTreeIterator> - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testIteratorCases") @DisplayName("test iterator at width order") public fun testWidthOrderIterator(expected: List, root: BSTreeNode) { @@ -24,7 +24,7 @@ class BinarySearchTreeIteratorTest { } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testGetALotOfElementsCases") @DisplayName("try get more elements than iterator has") public fun testGetALotOfElements(order: IterationOrders) { @@ -33,7 +33,7 @@ class BinarySearchTreeIteratorTest { Assertions.assertThrows(NoSuchElementException::class.java) { iterator.next() } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testIteratorCases") @DisplayName("test iterator at increase order") public fun testIncreasingOrderIterator(expected: List, root: BSTreeNode) { @@ -47,7 +47,7 @@ class BinarySearchTreeIteratorTest { } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testIteratorCases") @DisplayName("test iterator at decrease order") public fun testDecreasingOrderIterator(expected: List, root: BSTreeNode) { diff --git a/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt index cc2fa1a..c21815d 100644 --- a/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt +++ b/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt @@ -10,7 +10,7 @@ import tree_tripper.nodes.binary_nodes.BSTreeNode public class UtilsTest { - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeUpdateCases") @DisplayName("util of node update") public fun testNodeUpdate(expected: Boolean, node: BSTreeNode?) { @@ -19,7 +19,7 @@ public class UtilsTest { Assertions.assertEquals(expected, isActivateAction) } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeActionCases") @DisplayName("util of action on node") public fun testNodeAction(expected: Boolean, node: BSTreeNode?) { diff --git a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt index cad70a1..ca1a2b2 100644 --- a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt +++ b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt @@ -17,7 +17,7 @@ class AVLTreeNodeTest { Assertions.assertEquals(1, node.height) {"The height is not 1 by standard initialize."} } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testUpdateHeight") @DisplayName("update height") public fun testUpdateHeight(expected: Int, node: AVLTreeNode) { diff --git a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt index 5a71782..ac95991 100644 --- a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt +++ b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt @@ -10,7 +10,7 @@ import org.junit.jupiter.params.provider.MethodSource public class RBTreeNodeTest { - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeSimpleInitializeCases") @DisplayName("node simple initialization") public fun testNodeSimpleInitialize(key: Int, value: Int?) { @@ -22,7 +22,7 @@ public class RBTreeNodeTest { Assertions.assertNull(node.rightChild) { "Right child of node is not null." } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeColorTypeInitializeCases") @DisplayName("node initialization with color") public fun testNodeColorTypeInitialize(isRed: Boolean) { @@ -32,7 +32,7 @@ public class RBTreeNodeTest { Assertions.assertNull(node.rightChild) { "Right child of node is not null." } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testNodeFullInitializeCases") @DisplayName("node initialization with color and children") public fun testNodeFullInitialize(leftChild: RBTreeNode?, rightChild: RBTreeNode?) { @@ -41,7 +41,7 @@ public class RBTreeNodeTest { assertBinaryNodeDeepEquals(rightChild, node.rightChild) { n1, n2 -> n1.isRed == n2.isRed } } - @ParameterizedTest + @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}") @MethodSource("testToStringSimpleViewCases") @DisplayName("to string simple view") public fun testToStringSimpleView(expected: String, node: RBTreeNode) { From 1a493ec49801e32ac7e0117413cb523d7fb77879 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 13:28:22 +0300 Subject: [PATCH 04/18] test: add test result printer to terminal and add run cmd at action workflow yml-script --- .github/workflows/build-test.yml | 8 ++- scripts/test-result-printer.py | 105 +++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 scripts/test-result-printer.py diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c98aaf8..f66d304 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -19,7 +19,13 @@ jobs: run: ./gradlew build -x test - name: Run tests with jacoco - run: ./gradlew test + run: ./gradlew test >./test-res-out.log 2>./test-res-err.log + + - name: Display test results + run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test - name: Display info about coverage run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib + + - name: Clearing tmpfiles of test runnig + run: rm ./test-res-out.log && rm ./test-res-err.log diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py new file mode 100644 index 0000000..cf7db31 --- /dev/null +++ b/scripts/test-result-printer.py @@ -0,0 +1,105 @@ +import argparse +import sys, os, pprint +import typing +import xml.etree.ElementTree as ET + + +class TestCase: + + def __init__(self, name: str, is_passed: bool) -> None: + self.name = str(name) + self.is_passed = bool(is_passed) + + def toString(self, indent: int = 0): + return "\t" * indent + f"{self.name} -> {'PASSED' if self.is_passed else 'FAILURE'}" + + def __bool__(self): + return self.is_passed + + +class ParametrisedTestCase(TestCase): + + def __init__(self, name: str, cases: list[TestCase]) -> None: + super().__init__(name, all(cases)) + self.cases = cases + + def add_case(self, case: TestCase): + self.is_passed = self.is_passed and case.is_passed + self.cases.append(case) + + def toString(self, indent: int = 0): + inline_cases = [] + for case in self.cases: + inline_cases.append(case.toString(indent + 1)) + + inline_cases = '\n'.join(inline_cases).rstrip() + return "\t" * indent + f"{self.name} -> {'PASSED' if self.is_passed else 'FAILURE'}\n{inline_cases}" + + +def parse_args(args: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="Test Result Printer", + description="Program read xml files with tests results and print it at terminal at stdin", + ) + parser.add_argument( + "-d", "--dir", required=True, + help="setup path to directory with xml files with tests information", + metavar="" + ) + return parser.parse_args(args) + + +def display_test(test_path: str): + tree_root = ET.parse(test_path).getroot() + print( + "Tests of ", + tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), + sep=" " + ) + cases = dict() + for child in tree_root: + if child.tag != "testcase": + continue + + name = child.get("name", "uncknown test cases") + is_passed = child.find("failure") is None + if '[' in name: + primary_name = name.split('[')[0] + args = '[' + name.split('[')[1] + + case: ParametrisedTestCase = cases.get(primary_name, ParametrisedTestCase(primary_name, [])) + case.add_case(TestCase(args, is_passed)) + cases[primary_name] = case + else: + cases[name] = TestCase(name, is_passed) + + for name in sorted(cases.keys()): + print(cases[name].toString(indent=1)) + + print( + f"Passed: {int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0))}", + f"Failures: {tree_root.attrib.get("failures", 0)}", + f"Time: {tree_root.attrib.get("time", 0.0)}", + sep=" ", + end=os.linesep * 2 + ) + return [] + + +if __name__ == "__main__": + ns = parse_args(sys.argv[1:]) + + tests_result_dir = getattr(ns, "dir") + childs = os.listdir(tests_result_dir) + for child in sorted(childs): + child_path = os.path.join(tests_result_dir, child) + if not os.path.isfile(child_path): + continue + + if not (child.startswith("TEST") and child.endswith(".xml")): + continue + + try: + display_test(child_path) + except Exception as e: + print(f"Can't display ttest information at file '{child}': {e}", file=sys.stderr) From 1a1a6c42e64eefaf09faf04c242e49609a4c98e4 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 13:37:23 +0300 Subject: [PATCH 05/18] fix: update yml-script to continue execution on error at execution tests --- .github/workflows/build-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f66d304..87ed2f3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -23,6 +23,7 @@ jobs: - name: Display test results run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test + continue-on-error: true - name: Display info about coverage run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib From a541b276eb95268d51a0b02dceb19920d54e0577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D1=83=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=B2?= <90147482+IliaSuponeff@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:50:57 +0300 Subject: [PATCH 06/18] Update build-test.yml by change tasks line init --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 87ed2f3..1abc77a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,8 +22,8 @@ jobs: run: ./gradlew test >./test-res-out.log 2>./test-res-err.log - name: Display test results - run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test continue-on-error: true + run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test - name: Display info about coverage run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib From e299d152379b3b8f07eeb139669360678bfd80de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D1=83=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=B2?= <90147482+IliaSuponeff@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:54:24 +0300 Subject: [PATCH 07/18] fix: update build-test.yml to contiue on error at tests runtime(test fail)) --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1abc77a..e73c003 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,9 +20,9 @@ jobs: - name: Run tests with jacoco run: ./gradlew test >./test-res-out.log 2>./test-res-err.log + continue-on-error: true - name: Display test results - continue-on-error: true run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test - name: Display info about coverage From 3c5420b27ae6f144e2cd04cc44eb0055ab0117fb Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 14:02:55 +0300 Subject: [PATCH 08/18] refactor: update test result printer test --- scripts/test-result-printer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py index cf7db31..6aa84c7 100644 --- a/scripts/test-result-printer.py +++ b/scripts/test-result-printer.py @@ -65,7 +65,7 @@ def display_test(test_path: str): is_passed = child.find("failure") is None if '[' in name: primary_name = name.split('[')[0] - args = '[' + name.split('[')[1] + args = '[' + "[".join(name.split('[')[1:]) case: ParametrisedTestCase = cases.get(primary_name, ParametrisedTestCase(primary_name, [])) case.add_case(TestCase(args, is_passed)) @@ -76,10 +76,11 @@ def display_test(test_path: str): for name in sorted(cases.keys()): print(cases[name].toString(indent=1)) + passed_test_count = int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0)) print( - f"Passed: {int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0))}", - f"Failures: {tree_root.attrib.get("failures", 0)}", - f"Time: {tree_root.attrib.get("time", 0.0)}", + f"Passed: {passed_test_count}", + f"Failures: {tree_root.attrib.get('failures', 0)}", + f"Time: {tree_root.attrib.get('time', 0.0)}", sep=" ", end=os.linesep * 2 ) From 87b88fa08e53c2367f02e6218b598753772e35b0 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 14:07:03 +0300 Subject: [PATCH 09/18] refactor: update yml-script to more-continue on error at same steps of displaying test result information --- .github/workflows/build-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e73c003..a0a4ffd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -24,9 +24,11 @@ jobs: - name: Display test results run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test + continue-on-error: true - name: Display info about coverage run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib + continue-on-error: true - name: Clearing tmpfiles of test runnig run: rm ./test-res-out.log && rm ./test-res-err.log From 410fdc355945c26aab7ba069a7bc8d6af28cb04b Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 14:45:34 +0300 Subject: [PATCH 10/18] test: add colorize output of tests result printer --- scripts/test-result-printer.py | 23 +++++++++++++++-------- scripts/text_colorize.py | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 scripts/text_colorize.py diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py index 6aa84c7..c2c4f96 100644 --- a/scripts/test-result-printer.py +++ b/scripts/test-result-printer.py @@ -1,7 +1,7 @@ import argparse -import sys, os, pprint -import typing +import sys, os import xml.etree.ElementTree as ET +from text_colorize import ANSIColors, TextStyle, colorize class TestCase: @@ -11,8 +11,15 @@ def __init__(self, name: str, is_passed: bool) -> None: self.is_passed = bool(is_passed) def toString(self, indent: int = 0): - return "\t" * indent + f"{self.name} -> {'PASSED' if self.is_passed else 'FAILURE'}" - + return "\t" * indent + f"{self.name} -> {self.result_type()}" + + def result_type(self) -> str: + return colorize( + 'PASSED' if self.is_passed else 'FAILURE', + ANSIColors.GREEN if self.is_passed else ANSIColors.RED, + TextStyle.ITALIC + ) + def __bool__(self): return self.is_passed @@ -33,7 +40,7 @@ def toString(self, indent: int = 0): inline_cases.append(case.toString(indent + 1)) inline_cases = '\n'.join(inline_cases).rstrip() - return "\t" * indent + f"{self.name} -> {'PASSED' if self.is_passed else 'FAILURE'}\n{inline_cases}" + return super().toString(indent) + f"\n{inline_cases}" def parse_args(args: list[str]) -> argparse.Namespace: @@ -52,7 +59,7 @@ def parse_args(args: list[str]) -> argparse.Namespace: def display_test(test_path: str): tree_root = ET.parse(test_path).getroot() print( - "Tests of ", + "Tests of", tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), sep=" " ) @@ -78,8 +85,8 @@ def display_test(test_path: str): passed_test_count = int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0)) print( - f"Passed: {passed_test_count}", - f"Failures: {tree_root.attrib.get('failures', 0)}", + colorize(f"Passed: {passed_test_count}", ANSIColors.GREEN, TextStyle.BOLD), + colorize(f"Failures: {tree_root.attrib.get('failures', 0)}", ANSIColors.RED, TextStyle.BOLD), f"Time: {tree_root.attrib.get('time', 0.0)}", sep=" ", end=os.linesep * 2 diff --git a/scripts/text_colorize.py b/scripts/text_colorize.py new file mode 100644 index 0000000..a25a857 --- /dev/null +++ b/scripts/text_colorize.py @@ -0,0 +1,20 @@ +import enum + +class ANSIColors(enum.IntEnum): + PURPLE = 35 + BLUE = 34 + GREEN = 32 + YELLOW = 33 + RED = 31 + BLACK = 30 + WHITE = 37 + + +class TextStyle(enum.IntEnum): + SIMPLE = 0 + BOLD = 1 + ITALIC = 3 + + +def colorize(text: str, color: ANSIColors, style: TextStyle = TextStyle.SIMPLE) -> str: + return f"\033[{style};{color}m{text}\033[0m" \ No newline at end of file From cc7f891e65d0e841db41a630081ce9138bb1ce2f Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 15:45:53 +0300 Subject: [PATCH 11/18] test: add colorize output of tests coverage printer --- scripts/csv-reports-printer.py | 44 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/scripts/csv-reports-printer.py b/scripts/csv-reports-printer.py index 9898919..5a71cf8 100644 --- a/scripts/csv-reports-printer.py +++ b/scripts/csv-reports-printer.py @@ -2,6 +2,8 @@ import csv import sys import typing +from text_colorize import ANSIColors, TextStyle, colorize + COLUMNS_TYPES = [ '_MISSED', @@ -109,19 +111,38 @@ def read_csv(namespace: argparse.Namespace) -> typing.Optional[dict]: } -def create_label(text: str, lbl_size: int): +def create_label(text: str, lbl_size: int, color: ANSIColors = ANSIColors.BLACK): if len(text) >= lbl_size: - return '| ' + text[:lbl_size] + ' |' + text = text[:lbl_size] if len(text) % 2 != 0: text = ' ' + text - return f'| {{:^{lbl_size}}} |'.format(text) + color_text = colorize( + f"{{:^{lbl_size}}}".format(text), + color, + TextStyle.BOLD + ) + return f'| {color_text} |' + + +def colorize_percent_label(percent: int) -> str: + color = ANSIColors.RED + if 50 <= percent < 75: + color = ANSIColors.YELLOW + elif 75 <= percent <= 100: + color = ANSIColors.GREEN + + color_text = colorize( + f"{{:^{DEFAULT_LABEL_SIZE}}}".format(f"{percent}%"), + color, + TextStyle.BOLD + ) + return create_label(f"{percent}%", DEFAULT_LABEL_SIZE, color) def display_columns(max_packages_name_length: int, max_classes_name_length: int) -> int: global DISPLAY_COLUMNS, DEFAULT_LABEL_SIZE - result_length = 0 for column in DISPLAY_COLUMNS: lbl_size = DEFAULT_LABEL_SIZE if column == "CLASS": @@ -129,11 +150,9 @@ def display_columns(max_packages_name_length: int, max_classes_name_length: int) elif column == "PACKAGES": lbl_size = max_packages_name_length - lbl = create_label(column, lbl_size) - result_length += lbl_size + lbl = create_label(column, lbl_size, ANSIColors.PURPLE) print(lbl, end="") print() - return result_length def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None: @@ -147,7 +166,7 @@ def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None max_packages_name_length = csv_data_dict.get("max_packages_name_length", 20) max_classes_name_length = csv_data_dict.get("max_classes_name_length", 20) table: list[dict] = csv_data_dict.get("table", []) - result_length: int = display_columns(max_packages_name_length, max_classes_name_length) + display_columns(max_packages_name_length, max_classes_name_length) for row in table: for column in DISPLAY_COLUMNS: @@ -155,13 +174,14 @@ def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None if column in ["PACKAGES", "CLASS"]: lbl = create_label( row[column], - max_packages_name_length if column == "PACKAGES" else max_classes_name_length + max_packages_name_length if column == "PACKAGES" else max_classes_name_length, + ANSIColors.RED ) else: vals = [int(row[column + _type]) for _type in COLUMNS_TYPES] - lbl = create_label( - f"{int(round((vals[1] - vals[0]) / vals[1], 2) * 100)}%" if vals[1] != 0 else "100%", - DEFAULT_LABEL_SIZE + percent = int(round((vals[1] - vals[0]) / vals[1], 2) * 100) if vals[1] != 0 else 100 + lbl = colorize_percent_label( + percent, ) print(lbl, end="") From 2b6c419dc1b71c71b55e527375f6c252621914e7 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 15:47:01 +0300 Subject: [PATCH 12/18] refactor: set yml-script result checking of execution test task --- .github/workflows/build-test.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a0a4ffd..d4a4a00 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,15 +18,22 @@ jobs: - name: Build source code run: ./gradlew build -x test - - name: Run tests with jacoco + - name: Run tests + id: run-tests run: ./gradlew test >./test-res-out.log 2>./test-res-err.log continue-on-error: true - name: Display test results run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test continue-on-error: true + + - name: Run jacoco tests coverage report creating + if: ${{ steps.run-tests.outcome == 'success' }} + run: ./gradlew jacocoTestReport >./test-res-out.log 2>./test-res-err.log + continue-on-error: true - name: Display info about coverage + if: ${{ steps.run-tests.outcome == 'success' }} run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib continue-on-error: true From 9f52cf356d41a1dfbab292a62b898a9ddc969b55 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 15:51:11 +0300 Subject: [PATCH 13/18] refactor: delete test that always crashes, used to check the output of test results --- lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt index f3a0a24..226b109 100644 --- a/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt +++ b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt @@ -23,11 +23,6 @@ public class BSTreeTest { tree = BSTreeTestAssistant() } - @Test - public fun maximkaTop() { - assertEquals("a", "asdf") - } - @Test @DisplayName("tree initialization") public fun testTreeInitialization() { From e1956c7413e7f8c5e1c39d27514ef701ca6c0a1d Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 18:16:46 +0300 Subject: [PATCH 14/18] refactor: update yml-script to minimaize checking of tests execution. Disable depentancy task jacocoTestReport on task test. Change colorize of test coverage information and add endline at file text_colorize.py --- .github/workflows/build-test.yml | 8 +------- lib/build.gradle.kts | 1 - scripts/csv-reports-printer.py | 9 ++------- scripts/text_colorize.py | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d4a4a00..b67c263 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -19,23 +19,17 @@ jobs: run: ./gradlew build -x test - name: Run tests - id: run-tests run: ./gradlew test >./test-res-out.log 2>./test-res-err.log continue-on-error: true - name: Display test results - run: python ./scripts/test-result-printer.py -d ./lib/build/test-results/test - continue-on-error: true + run: python3 ./scripts/test-result-printer.py -d ./lib/build/test-results/test - name: Run jacoco tests coverage report creating - if: ${{ steps.run-tests.outcome == 'success' }} run: ./gradlew jacocoTestReport >./test-res-out.log 2>./test-res-err.log - continue-on-error: true - name: Display info about coverage - if: ${{ steps.run-tests.outcome == 'success' }} run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib - continue-on-error: true - name: Clearing tmpfiles of test runnig run: rm ./test-res-out.log && rm ./test-res-err.log diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index d8e9d90..62c75da 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -39,7 +39,6 @@ tasks.named("test") { } tasks.named("jacocoTestReport") { - dependsOn(tasks.test) val sep = File.separator val jacocoReportsDirName = "reports${sep}jacoco" reports { diff --git a/scripts/csv-reports-printer.py b/scripts/csv-reports-printer.py index 5a71cf8..4ee0bbe 100644 --- a/scripts/csv-reports-printer.py +++ b/scripts/csv-reports-printer.py @@ -132,12 +132,7 @@ def colorize_percent_label(percent: int) -> str: color = ANSIColors.YELLOW elif 75 <= percent <= 100: color = ANSIColors.GREEN - - color_text = colorize( - f"{{:^{DEFAULT_LABEL_SIZE}}}".format(f"{percent}%"), - color, - TextStyle.BOLD - ) + return create_label(f"{percent}%", DEFAULT_LABEL_SIZE, color) @@ -175,7 +170,7 @@ def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None lbl = create_label( row[column], max_packages_name_length if column == "PACKAGES" else max_classes_name_length, - ANSIColors.RED + ANSIColors.YELLOW ) else: vals = [int(row[column + _type]) for _type in COLUMNS_TYPES] diff --git a/scripts/text_colorize.py b/scripts/text_colorize.py index a25a857..ce38225 100644 --- a/scripts/text_colorize.py +++ b/scripts/text_colorize.py @@ -17,4 +17,4 @@ class TextStyle(enum.IntEnum): def colorize(text: str, color: ANSIColors, style: TextStyle = TextStyle.SIMPLE) -> str: - return f"\033[{style};{color}m{text}\033[0m" \ No newline at end of file + return f"\033[{style};{color}m{text}\033[0m" From 092b89d4a62991125867c7fc90abd8daf6084050 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 18:52:29 +0300 Subject: [PATCH 15/18] refactor: split display_test function into two parse_test_result and display_test_result --- scripts/test-result-printer.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py index c2c4f96..b46c3ec 100644 --- a/scripts/test-result-printer.py +++ b/scripts/test-result-printer.py @@ -56,13 +56,8 @@ def parse_args(args: list[str]) -> argparse.Namespace: return parser.parse_args(args) -def display_test(test_path: str): +def parse_test_result(test_path: str) -> tuple[ET.Element, dict[str, TestCase]]: tree_root = ET.parse(test_path).getroot() - print( - "Tests of", - tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), - sep=" " - ) cases = dict() for child in tree_root: if child.tag != "testcase": @@ -79,7 +74,16 @@ def display_test(test_path: str): cases[primary_name] = case else: cases[name] = TestCase(name, is_passed) + + return (tree_root, cases,) + +def display_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): + print( + "Tests of", + tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), + sep=" " + ) for name in sorted(cases.keys()): print(cases[name].toString(indent=1)) @@ -91,7 +95,6 @@ def display_test(test_path: str): sep=" ", end=os.linesep * 2 ) - return [] if __name__ == "__main__": @@ -99,6 +102,7 @@ def display_test(test_path: str): tests_result_dir = getattr(ns, "dir") childs = os.listdir(tests_result_dir) + tests_results: list[tuple[ET.Element, dict[str, TestCase]]] = [] for child in sorted(childs): child_path = os.path.join(tests_result_dir, child) if not os.path.isfile(child_path): @@ -108,6 +112,9 @@ def display_test(test_path: str): continue try: - display_test(child_path) + tests_results += parse_test_result(child_path) except Exception as e: print(f"Can't display ttest information at file '{child}': {e}", file=sys.stderr) + + for test_result in tests_results: + display_test_result(tests_results[0], tests_results[1]) From 2185545f8d61ee1717ba97082e4c246177411057 Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 18:53:13 +0300 Subject: [PATCH 16/18] refactor: update names of steps at yml-script --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b67c263..6e1952d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -25,11 +25,11 @@ jobs: - name: Display test results run: python3 ./scripts/test-result-printer.py -d ./lib/build/test-results/test - - name: Run jacoco tests coverage report creating + - name: Run jacoco coverage report run: ./gradlew jacocoTestReport >./test-res-out.log 2>./test-res-err.log - name: Display info about coverage run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib - - name: Clearing tmpfiles of test runnig + - name: Clear tmpfiles of runnig tests run: rm ./test-res-out.log && rm ./test-res-err.log From 054d6f85d0913b15255ee4c170f607d84866e44d Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 19:40:49 +0300 Subject: [PATCH 17/18] test: update python-script for displaying test results and update it accordingly yml-script to run updated python-script correctly --- .github/workflows/build-test.yml | 4 +- scripts/test-result-printer.py | 73 ++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 6e1952d..44b952f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -23,13 +23,13 @@ jobs: continue-on-error: true - name: Display test results - run: python3 ./scripts/test-result-printer.py -d ./lib/build/test-results/test + run: python3 ./scripts/test-result-printer.py --dir ./lib/build/test-results/test --all-failures - name: Run jacoco coverage report run: ./gradlew jacocoTestReport >./test-res-out.log 2>./test-res-err.log - name: Display info about coverage - run: python3 ./scripts/csv-reports-printer.py -i ./lib/build/reports/jacoco/info.csv -l lib + run: python3 ./scripts/csv-reports-printer.py --input ./lib/build/reports/jacoco/info.csv --lib lib - name: Clear tmpfiles of runnig tests run: rm ./test-res-out.log && rm ./test-res-err.log diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py index b46c3ec..5f9e987 100644 --- a/scripts/test-result-printer.py +++ b/scripts/test-result-printer.py @@ -34,9 +34,12 @@ def add_case(self, case: TestCase): self.is_passed = self.is_passed and case.is_passed self.cases.append(case) - def toString(self, indent: int = 0): + def toString(self, indent: int = 0, also_failed: bool = False): inline_cases = [] for case in self.cases: + if (also_failed and case.is_passed): + continue + inline_cases.append(case.toString(indent + 1)) inline_cases = '\n'.join(inline_cases).rstrip() @@ -53,6 +56,16 @@ def parse_args(args: list[str]) -> argparse.Namespace: help="setup path to directory with xml files with tests information", metavar="" ) + parser.add_argument( + "-a", "--all", + action="store_true", + help="setup mode of diplay type to print all test information to ON (default OFF)", + ) + parser.add_argument( + "-f", "--all-failures", + action="store_true", + help="setup mode of diplay type to print also test information which failed to ON (default OFF)", + ) return parser.parse_args(args) @@ -78,7 +91,7 @@ def parse_test_result(test_path: str) -> tuple[ET.Element, dict[str, TestCase]]: return (tree_root, cases,) -def display_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): +def display_all_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): print( "Tests of", tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), @@ -96,6 +109,36 @@ def display_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): end=os.linesep * 2 ) + +def display_failures_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): + failed_tests = [] + for name in sorted(cases.keys()): + if not cases[name].is_passed: + failed_tests.append(cases[name]) + + if len(failed_tests) == 0: + return + + print( + "Failed tests of", + tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"), + sep=" " + ) + for case in failed_tests: + if isinstance(case, ParametrisedTestCase): + print(case.toString(indent=1, also_failed=True)) + elif isinstance(case, TestCase): + print(case.toString(indent=1)) + + passed_test_count = int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0)) + print( + colorize(f"Passed: {passed_test_count}", ANSIColors.GREEN, TextStyle.BOLD), + colorize(f"Failures: {tree_root.attrib.get('failures', 0)}", ANSIColors.RED, TextStyle.BOLD), + f"Time: {tree_root.attrib.get('time', 0.0)}", + sep=" ", + end=os.linesep * 2 + ) + if __name__ == "__main__": ns = parse_args(sys.argv[1:]) @@ -112,9 +155,31 @@ def display_test_result(tree_root: ET.Element, cases: dict[str, TestCase]): continue try: - tests_results += parse_test_result(child_path) + tests_results.append(parse_test_result(child_path)) except Exception as e: print(f"Can't display ttest information at file '{child}': {e}", file=sys.stderr) + tests_count = 0 + tests_failed_count = 0 + time_of_all_tests = 0 + for test_result in tests_results: + tree_root: ET.Element = test_result[0] + tests_count += int(tree_root.attrib.get("tests", 0)) + tests_failed_count += int(tree_root.attrib.get("failures", 0)) + time_of_all_tests += float(tree_root.attrib.get('time', 0.0)) + + + print( + colorize(f"Count of tests: {tests_count}", ANSIColors.YELLOW, TextStyle.BOLD), + colorize(f"Count of passed tests: {tests_count - tests_failed_count}", ANSIColors.GREEN, TextStyle.BOLD), + colorize(f"Count of failured tests: {tests_failed_count}", ANSIColors.RED, TextStyle.BOLD), + colorize(f"Time: {time_of_all_tests}", ANSIColors.BLUE, TextStyle.BOLD), + sep=os.linesep, + end=os.linesep * 2 + ) + for test_result in tests_results: - display_test_result(tests_results[0], tests_results[1]) + if getattr(ns, "all", False): + display_all_test_result(test_result[0], test_result[1]) + elif getattr(ns, "all_failures", False): + display_failures_test_result(test_result[0], test_result[1]) From 8a0b4edf582c5bc9d98fa2b5e8ee34745f064f4e Mon Sep 17 00:00:00 2001 From: IliaSuponeff Date: Tue, 9 Apr 2024 19:46:33 +0300 Subject: [PATCH 18/18] refactor: remove unnecessary variable type declarations --- .../tree_tripper/binary_trees/RBTree.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt b/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt index 6cf81ca..12aa1ac 100644 --- a/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt +++ b/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt @@ -41,7 +41,7 @@ public open class RBTree, V>: AbstractBSTree?, V?> var resultCompare: Int = key.compareTo(node.key) - var nodeCurrent: RBTreeNode = node + var nodeCurrent = node if (resultCompare < 0) { if (!isRedColor(nodeCurrent.leftChild) && !isRedLeftChild(nodeCurrent.leftChild)) nodeCurrent = moveRedLeft(nodeCurrent) @@ -105,13 +105,13 @@ public open class RBTree, V>: AbstractBSTree): RBTreeNode { - val rightChild: RBTreeNode = node.rightChild ?: return node - node.rightChild = rightChild.leftChild - rightChild.leftChild = node + val nodeSwapped: RBTreeNode = node.rightChild ?: return node + node.rightChild = nodeSwapped.leftChild + nodeSwapped.leftChild = node - rightChild.isRed = node.isRed + nodeSwapped.isRed = node.isRed node.isRed = true - return rightChild + return nodeSwapped } /** @@ -122,13 +122,13 @@ public open class RBTree, V>: AbstractBSTree): RBTreeNode { - val leftChild: RBTreeNode = node.leftChild ?: return node - node.leftChild = leftChild.rightChild - leftChild.rightChild = node + val nodeSwapped: RBTreeNode = node.leftChild ?: return node + node.leftChild = nodeSwapped.rightChild + nodeSwapped.rightChild = node - leftChild.isRed = node.isRed + nodeSwapped.isRed = node.isRed node.isRed = true - return leftChild + return nodeSwapped } /** @@ -151,7 +151,7 @@ public open class RBTree, V>: AbstractBSTree): RBTreeNode { if (node.rightChild == null) return node - var nodeCurrent: RBTreeNode = node + var nodeCurrent = node flipColors(nodeCurrent) if (isRedLeftChild(nodeCurrent.leftChild)) { @@ -170,13 +170,13 @@ public open class RBTree, V>: AbstractBSTree): RBTreeNode { if (node.leftChild == null) return node - var nodeCurrent: RBTreeNode = node + var nodeCurrent = node flipColors(nodeCurrent) if (isRedLeftChild(nodeCurrent.rightChild)) { nodeCurrent.rightChild = notNullNodeAction( node.rightChild, null - ) {rightChild -> rotateRight(rightChild)} + ) { rightChild -> rotateRight(rightChild) } nodeCurrent = rotateLeft(nodeCurrent) flipColors(nodeCurrent) } @@ -193,7 +193,7 @@ public open class RBTree, V>: AbstractBSTree = node + var nodeCurrent = node if (!isRedColor(leftChild) && !isRedLeftChild(leftChild)) nodeCurrent = moveRedLeft(nodeCurrent)