orionis 0.434.0__py3-none-any.whl → 0.435.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.434.0"
8
+ VERSION = "0.435.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -201,6 +201,7 @@ class UnitTest(IUnitTest):
201
201
  OrionisTestValueError
202
202
  If any parameter is invalid.
203
203
  """
204
+
204
205
  # Validate and assign parameters using specialized validators
205
206
  self.__verbosity = ValidVerbosity(verbosity)
206
207
  self.__execution_mode = ValidExecutionMode(execution_mode)
@@ -284,15 +285,17 @@ class UnitTest(IUnitTest):
284
285
  # Check for failed test imports (unittest.loader._FailedTest)
285
286
  for test in self.__flattenTestSuite(tests):
286
287
  if test.__class__.__name__ == "_FailedTest":
288
+
287
289
  # Extract the error message from the test's traceback
288
290
  error_message = ""
289
291
  if hasattr(test, "_exception"):
290
292
  error_message = str(test._exception)
291
293
  elif hasattr(test, "_outcome") and hasattr(test._outcome, "errors"):
292
294
  error_message = str(test._outcome.errors)
295
+ # Try to get error from test id or str(test)
293
296
  else:
294
- # Try to get error from test id or str(test)
295
297
  error_message = str(test)
298
+
296
299
  raise OrionisTestValueError(
297
300
  f"Failed to import test module: {test.id()}.\n"
298
301
  f"Error details: {error_message}\n"
@@ -380,6 +383,7 @@ class UnitTest(IUnitTest):
380
383
  OrionisTestValueError
381
384
  If module_name is invalid, test_name_pattern is not a valid regex, the module cannot be imported, or no tests are found.
382
385
  """
386
+
383
387
  # Validate input parameters
384
388
  self.__module_name = ValidModuleName(module_name)
385
389
  self.__test_name_pattern = ValidNamePattern(test_name_pattern)
@@ -503,27 +507,37 @@ class UnitTest(IUnitTest):
503
507
  suite: unittest.TestSuite
504
508
  ) -> List[unittest.TestCase]:
505
509
  """
506
- Recursively flatten a unittest.TestSuite into a list of unique unittest.TestCase instances.
510
+ Recursively flattens a unittest.TestSuite into a list of unique unittest.TestCase instances.
507
511
 
508
512
  Parameters
509
513
  ----------
510
514
  suite : unittest.TestSuite
511
- The test suite to flatten.
515
+ The test suite to be flattened.
512
516
 
513
517
  Returns
514
518
  -------
515
- list of unittest.TestCase
516
- Flat list of unique unittest.TestCase instances.
519
+ List[unittest.TestCase]
520
+ A flat list containing unique unittest.TestCase instances extracted from the suite.
521
+
522
+ Notes
523
+ -----
524
+ Test uniqueness is determined by a shortened test identifier (the last two components of the test id).
525
+ This helps avoid duplicate test cases in the returned list.
517
526
  """
527
+
528
+ # Initialize an empty list to hold unique test cases and a set to track seen test IDs
518
529
  tests = []
519
530
  seen_ids = set()
520
531
 
532
+ # Recursive function to flatten the test suite
521
533
  def _flatten(item):
522
534
  if isinstance(item, unittest.TestSuite):
523
535
  for sub_item in item:
524
536
  _flatten(sub_item)
525
537
  elif hasattr(item, "id"):
526
538
  test_id = item.id()
539
+
540
+ # Use the last two components of the test id for uniqueness
527
541
  parts = test_id.split('.')
528
542
  if len(parts) >= 2:
529
543
  short_id = '.'.join(parts[-2:])
@@ -533,6 +547,7 @@ class UnitTest(IUnitTest):
533
547
  seen_ids.add(short_id)
534
548
  tests.append(item)
535
549
 
550
+ # Start the flattening process
536
551
  _flatten(suite)
537
552
  return tests
538
553
 
@@ -540,68 +555,86 @@ class UnitTest(IUnitTest):
540
555
  self
541
556
  ) -> Tuple[unittest.TestResult, io.StringIO, io.StringIO]:
542
557
  """
543
- Execute the test suite using the configured execution mode, capturing output and error streams.
558
+ Executes the test suite according to the configured execution mode, capturing both standard output and error streams.
544
559
 
545
560
  Returns
546
561
  -------
547
562
  tuple
548
- (result, output_buffer, error_buffer)
549
563
  result : unittest.TestResult
550
- Test result object with test outcomes.
564
+ The result object containing the outcomes of the executed tests.
551
565
  output_buffer : io.StringIO
552
- Captured standard output during test execution.
566
+ Buffer capturing the standard output generated during test execution.
553
567
  error_buffer : io.StringIO
554
- Captured standard error during test execution.
568
+ Buffer capturing the standard error generated during test execution.
555
569
  """
570
+
571
+ # Initialize output and error buffers to capture test execution output
556
572
  output_buffer = io.StringIO()
557
573
  error_buffer = io.StringIO()
558
574
 
575
+ # Run tests in parallel mode using multiple workers
559
576
  if self.__execution_mode == ExecutionMode.PARALLEL.value:
560
577
  result = self.__runTestsInParallel(
561
578
  output_buffer,
562
579
  error_buffer
563
580
  )
581
+
582
+ # Run tests sequentially
564
583
  else:
565
584
  result = self.__runTestsSequentially(
566
585
  output_buffer,
567
586
  error_buffer
568
587
  )
569
588
 
589
+ # Return the result, output, and error buffers
570
590
  return result, output_buffer, error_buffer
571
591
 
572
592
  def __resolveFlattenedTestSuite(
573
593
  self
574
594
  ) -> unittest.TestSuite:
575
595
  """
576
- Resolve and inject dependencies for all test cases in the suite, returning a flattened TestSuite.
596
+ Resolves and injects dependencies for all test cases in the current suite, returning a flattened TestSuite.
597
+
598
+ This method iterates through all test cases in the suite, checks for failed imports, decorated methods, and unresolved dependencies.
599
+ For each test case, it uses reflection to determine the test method and its dependencies. If dependencies are required and can be resolved,
600
+ it injects them using the application's resolver. If a test method has unresolved dependencies, an exception is raised.
601
+ Decorated methods and failed imports are added as-is. The resulting TestSuite contains all test cases with dependencies injected where needed.
577
602
 
578
603
  Returns
579
604
  -------
580
605
  unittest.TestSuite
581
- New TestSuite with dependencies injected where required.
606
+ A new TestSuite containing all test cases with dependencies injected as required.
582
607
 
583
608
  Raises
584
609
  ------
585
610
  OrionisTestValueError
586
- If any test method has unresolved dependencies.
611
+ If any test method has unresolved dependencies that cannot be resolved by the resolver.
587
612
  """
613
+
614
+ # Create a new TestSuite to hold the resolved test cases
588
615
  flattened_suite = unittest.TestSuite()
589
616
 
617
+ # Iterate through all test cases in the flattened suite
590
618
  for test_case in self.__flattenTestSuite(self.__suite):
591
619
 
620
+ # If the test case is a failed import, add it directly
592
621
  if test_case.__class__.__name__ == "_FailedTest":
593
622
  flattened_suite.addTest(test_case)
594
623
  continue
595
624
 
625
+ # Use reflection to get the test method name
596
626
  rf_instance = ReflectionInstance(test_case)
597
627
  method_name = rf_instance.getAttribute("_testMethodName")
598
628
 
629
+ # If no method name is found, add the test case as-is
599
630
  if not method_name:
600
631
  flattened_suite.addTest(test_case)
601
632
  continue
602
633
 
634
+ # Retrieve the test method from the class
603
635
  test_method = getattr(test_case.__class__, method_name, None)
604
636
 
637
+ # Check if the method is decorated (wrapped)
605
638
  decorators = []
606
639
  if hasattr(test_method, '__wrapped__'):
607
640
  original = test_method
@@ -612,32 +645,40 @@ class UnitTest(IUnitTest):
612
645
  decorators.append(original.__name__)
613
646
  original = original.__wrapped__
614
647
 
648
+ # If decorators are present, add the test case as-is
615
649
  if decorators:
616
650
  flattened_suite.addTest(test_case)
617
651
  continue
618
652
 
653
+ # Get the method's dependency signature
619
654
  signature = rf_instance.getMethodDependencies(method_name)
620
655
 
656
+ # If no dependencies are required or unresolved, add the test case as-is
621
657
  if ((not signature.resolved and not signature.unresolved) or (not signature.resolved and len(signature.unresolved) > 0)):
622
658
  flattened_suite.addTest(test_case)
623
659
  continue
624
660
 
661
+ # If there are unresolved dependencies, raise an error
625
662
  if (len(signature.unresolved) > 0):
626
663
  raise OrionisTestValueError(
627
664
  f"Test method '{method_name}' in class '{test_case.__class__.__name__}' has unresolved dependencies: {signature.unresolved}. "
628
665
  "Please ensure all dependencies are correctly defined and available."
629
666
  )
630
667
 
668
+ # Get the original test class and method
631
669
  test_class = ReflectionInstance(test_case).getClass()
632
670
  original_method = getattr(test_class, method_name)
633
671
 
672
+ # Resolve dependencies using the application's resolver
634
673
  params = Resolver(self.__app).resolveSignature(signature)
635
674
 
675
+ # Create a wrapper to inject resolved dependencies into the test method
636
676
  def create_test_wrapper(original_test, resolved_args: dict):
637
677
  def wrapper(self_instance):
638
678
  return original_test(self_instance, **resolved_args)
639
679
  return wrapper
640
680
 
681
+ # Bind the wrapped method to the test case instance
641
682
  wrapped_method = create_test_wrapper(original_method, params)
642
683
  bound_method = wrapped_method.__get__(test_case, test_case.__class__)
643
684
  setattr(test_case, method_name, bound_method)
@@ -651,28 +692,45 @@ class UnitTest(IUnitTest):
651
692
  error_buffer: io.StringIO
652
693
  ) -> unittest.TestResult:
653
694
  """
654
- Execute the test suite sequentially, capturing output and error streams.
695
+ Executes all test cases in the test suite sequentially, capturing standard output and error streams.
655
696
 
656
697
  Parameters
657
698
  ----------
658
699
  output_buffer : io.StringIO
659
- Buffer to capture standard output.
700
+ Buffer to capture the standard output generated during test execution.
660
701
  error_buffer : io.StringIO
661
- Buffer to capture standard error.
702
+ Buffer to capture the standard error generated during test execution.
662
703
 
663
704
  Returns
664
705
  -------
665
706
  unittest.TestResult
666
- Result of the test suite execution.
707
+ The aggregated result object containing the outcomes of all executed test cases.
708
+
709
+ Raises
710
+ ------
711
+ OrionisTestValueError
712
+ If an item in the suite is not a valid unittest.TestCase instance.
713
+
714
+ Notes
715
+ -----
716
+ Each test case is executed individually, and results are merged into a single result object.
717
+ Output and error streams are redirected for each test case to ensure complete capture.
718
+ The printer is used to display the result of each test immediately after execution.
667
719
  """
720
+
721
+ # Initialize output and error buffers to capture test execution output
668
722
  result = None
723
+
724
+ # Iterate through all resolved test cases in the suite
669
725
  for case in self.__resolveFlattenedTestSuite():
670
726
 
727
+ # Ensure the test case is a valid unittest.TestCase instance
671
728
  if not isinstance(case, unittest.TestCase):
672
729
  raise OrionisTestValueError(
673
730
  f"Invalid test case type: Expected unittest.TestCase, got {type(case).__name__}."
674
731
  )
675
732
 
733
+ # Redirect output and error streams for the current test case
676
734
  with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
677
735
  runner = unittest.TextTestRunner(
678
736
  stream=output_buffer,
@@ -680,15 +738,19 @@ class UnitTest(IUnitTest):
680
738
  failfast=self.__fail_fast,
681
739
  resultclass=self.__customResultClass()
682
740
  )
741
+ # Run the current test case and obtain the result
683
742
  single_result: IOrionisTestResult = runner.run(unittest.TestSuite([case]))
684
743
 
744
+ # Print the result of the current test case using the printer
685
745
  self.__printer.unittestResult(single_result.test_results[0])
686
746
 
747
+ # Merge the result of the current test case into the aggregated result
687
748
  if result is None:
688
749
  result = single_result
689
750
  else:
690
751
  self.__mergeTestResults(result, single_result)
691
752
 
753
+ # Return the aggregated result containing all test outcomes
692
754
  return result
693
755
 
694
756
  def __runTestsInParallel(
@@ -697,47 +759,72 @@ class UnitTest(IUnitTest):
697
759
  error_buffer: io.StringIO
698
760
  ) -> unittest.TestResult:
699
761
  """
700
- Execute all test cases in the test suite concurrently using a thread pool, aggregating results.
762
+ Executes all test cases in the test suite concurrently using a thread pool and aggregates their results.
701
763
 
702
764
  Parameters
703
765
  ----------
704
766
  output_buffer : io.StringIO
705
- Buffer to capture standard output.
767
+ Buffer to capture the standard output generated during test execution.
706
768
  error_buffer : io.StringIO
707
- Buffer to capture standard error.
769
+ Buffer to capture the standard error generated during test execution.
708
770
 
709
771
  Returns
710
772
  -------
711
773
  unittest.TestResult
712
- Combined result object containing all test outcomes.
774
+ Combined result object containing the outcomes of all executed test cases.
775
+
776
+ Notes
777
+ -----
778
+ Each test case is executed in a separate thread using a ThreadPoolExecutor.
779
+ Results from all threads are merged into a single result object.
780
+ Output and error streams are redirected for the entire parallel execution.
781
+ If fail-fast is enabled, execution stops as soon as a failure is detected.
713
782
  """
783
+
784
+ # Resolve and flatten all test cases in the suite, injecting dependencies if needed
714
785
  test_cases = list(self.__resolveFlattenedTestSuite())
786
+
787
+ # Get the custom result class for enhanced test tracking
715
788
  result_class = self.__customResultClass()
789
+
790
+ # Create a combined result object to aggregate all individual test results
716
791
  combined_result = result_class(io.StringIO(), descriptions=True, verbosity=self.__verbosity)
717
792
 
793
+ # Define a function to run a single test case and return its result
718
794
  def run_single_test(test):
719
795
  runner = unittest.TextTestRunner(
720
- stream=io.StringIO(),
796
+ stream=io.StringIO(), # Use a separate buffer for each test
721
797
  verbosity=0,
722
798
  failfast=False,
723
799
  resultclass=result_class
724
800
  )
725
801
  return runner.run(unittest.TestSuite([test]))
726
802
 
803
+ # Redirect output and error streams for the entire parallel execution
727
804
  with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
805
+
806
+ # Create a thread pool with the configured number of workers
728
807
  with ThreadPoolExecutor(max_workers=self.__max_workers) as executor:
808
+
809
+ # Submit all test cases to the thread pool for execution
729
810
  futures = [executor.submit(run_single_test, test) for test in test_cases]
811
+
812
+ # As each test completes, merge its result into the combined result
730
813
  for future in as_completed(futures):
731
814
  test_result = future.result()
732
815
  self.__mergeTestResults(combined_result, test_result)
816
+
817
+ # If fail-fast is enabled and a failure occurs, cancel remaining tests
733
818
  if self.__fail_fast and not combined_result.wasSuccessful():
734
819
  for f in futures:
735
820
  f.cancel()
736
821
  break
737
822
 
823
+ # Print the result of each individual test using the printer
738
824
  for test_result in combined_result.test_results:
739
825
  self.__printer.unittestResult(test_result)
740
826
 
827
+ # Return the aggregated result containing all test outcomes
741
828
  return combined_result
742
829
 
743
830
  def __mergeTestResults(
@@ -751,20 +838,42 @@ class UnitTest(IUnitTest):
751
838
  Parameters
752
839
  ----------
753
840
  combined_result : unittest.TestResult
754
- The TestResult object to update.
841
+ The TestResult object that will be updated with the merged results.
755
842
  individual_result : unittest.TestResult
756
- The TestResult object to merge in.
843
+ The TestResult object whose results will be merged into the combined_result.
757
844
 
758
845
  Returns
759
846
  -------
760
847
  None
848
+ This method does not return a value. It updates combined_result in place.
849
+
850
+ Notes
851
+ -----
852
+ This method aggregates the test statistics and detailed results from individual_result into combined_result.
853
+ It updates the total number of tests run, and extends the lists of failures, errors, skipped tests,
854
+ expected failures, and unexpected successes. If the result objects contain a 'test_results' attribute,
855
+ this method also merges the detailed test result entries.
761
856
  """
857
+
858
+ # Increment the total number of tests run
762
859
  combined_result.testsRun += individual_result.testsRun
860
+
861
+ # Extend the list of failures with those from the individual result
763
862
  combined_result.failures.extend(individual_result.failures)
863
+
864
+ # Extend the list of errors with those from the individual result
764
865
  combined_result.errors.extend(individual_result.errors)
866
+
867
+ # Extend the list of skipped tests with those from the individual result
765
868
  combined_result.skipped.extend(individual_result.skipped)
869
+
870
+ # Extend the list of expected failures with those from the individual result
766
871
  combined_result.expectedFailures.extend(individual_result.expectedFailures)
872
+
873
+ # Extend the list of unexpected successes with those from the individual result
767
874
  combined_result.unexpectedSuccesses.extend(individual_result.unexpectedSuccesses)
875
+
876
+ # If the individual result contains detailed test results, merge them as well
768
877
  if hasattr(individual_result, 'test_results'):
769
878
  if not hasattr(combined_result, 'test_results'):
770
879
  combined_result.test_results = []
@@ -774,31 +883,45 @@ class UnitTest(IUnitTest):
774
883
  self
775
884
  ) -> type:
776
885
  """
777
- Create a custom test result class for enhanced test tracking.
886
+ Create and return a custom test result class for enhanced test tracking.
778
887
 
779
888
  Returns
780
889
  -------
781
890
  type
782
- Dynamically created OrionisTestResult class extending unittest.TextTestResult.
891
+ A dynamically created subclass of unittest.TextTestResult that collects
892
+ detailed information about each test execution, including timing, status,
893
+ error messages, tracebacks, and metadata.
894
+
895
+ Notes
896
+ -----
897
+ The returned class, OrionisTestResult, extends unittest.TextTestResult and
898
+ overrides key methods to capture additional data for each test case. This
899
+ includes execution time, error details, and test metadata, which are stored
900
+ in a list of TestResult objects for later reporting and analysis.
783
901
  """
784
902
  this = self
785
903
 
786
904
  class OrionisTestResult(unittest.TextTestResult):
905
+
906
+ # Initialize the parent class and custom attributes for tracking results and timings
787
907
  def __init__(self, *args, **kwargs):
788
908
  super().__init__(*args, **kwargs)
789
- self.test_results = []
790
- self._test_timings = {}
791
- self._current_test_start = None
909
+ self.test_results = [] # Stores detailed results for each test
910
+ self._test_timings = {} # Maps test instances to their execution time
911
+ self._current_test_start = None # Tracks the start time of the current test
792
912
 
913
+ # Record the start time of the test
793
914
  def startTest(self, test):
794
915
  self._current_test_start = time.time()
795
916
  super().startTest(test)
796
917
 
918
+ # Calculate and store the elapsed time for the test
797
919
  def stopTest(self, test):
798
920
  elapsed = time.time() - self._current_test_start
799
921
  self._test_timings[test] = elapsed
800
922
  super().stopTest(test)
801
923
 
924
+ # Handle a successful test case and record its result
802
925
  def addSuccess(self, test):
803
926
  super().addSuccess(test)
804
927
  elapsed = self._test_timings.get(test, 0.0)
@@ -816,6 +939,7 @@ class UnitTest(IUnitTest):
816
939
  )
817
940
  )
818
941
 
942
+ # Handle a failed test case, extract error info, and record its result
819
943
  def addFailure(self, test, err):
820
944
  super().addFailure(test, err)
821
945
  elapsed = self._test_timings.get(test, 0.0)
@@ -837,6 +961,7 @@ class UnitTest(IUnitTest):
837
961
  )
838
962
  )
839
963
 
964
+ # Handle a test case that raised an error, extract error info, and record its result
840
965
  def addError(self, test, err):
841
966
  super().addError(test, err)
842
967
  elapsed = self._test_timings.get(test, 0.0)
@@ -858,6 +983,7 @@ class UnitTest(IUnitTest):
858
983
  )
859
984
  )
860
985
 
986
+ # Handle a skipped test case and record its result
861
987
  def addSkip(self, test, reason):
862
988
  super().addSkip(test, reason)
863
989
  elapsed = self._test_timings.get(test, 0.0)
@@ -876,6 +1002,7 @@ class UnitTest(IUnitTest):
876
1002
  )
877
1003
  )
878
1004
 
1005
+ # Return the dynamically created OrionisTestResult class
879
1006
  return OrionisTestResult
880
1007
 
881
1008
  def _extractErrorInfo(
@@ -883,33 +1010,53 @@ class UnitTest(IUnitTest):
883
1010
  traceback_str: str
884
1011
  ) -> Tuple[Optional[str], Optional[str]]:
885
1012
  """
886
- Extract the file path and a cleaned traceback from a given traceback string.
1013
+ Extracts the file path and a cleaned traceback from a given traceback string.
887
1014
 
888
1015
  Parameters
889
1016
  ----------
890
1017
  traceback_str : str
891
- Full traceback string to process.
1018
+ The full traceback string to process.
892
1019
 
893
1020
  Returns
894
1021
  -------
895
1022
  tuple
896
1023
  file_path : str or None
897
- Path to the Python file where the error occurred.
1024
+ The path to the Python file where the error occurred, or None if not found.
898
1025
  clean_tb : str or None
899
- Cleaned traceback string with framework internals removed.
1026
+ The cleaned traceback string with framework internals removed, or the original traceback if no cleaning was possible.
1027
+
1028
+ Notes
1029
+ -----
1030
+ This method parses the traceback string to identify the most relevant file path (typically the last Python file in the traceback).
1031
+ It then filters out lines related to framework internals (such as 'unittest/', 'lib/python', or 'site-packages') to produce a more concise and relevant traceback.
1032
+ The cleaned traceback starts from the first occurrence of the relevant file path.
900
1033
  """
1034
+
1035
+ # Find all Python file paths in the traceback
901
1036
  file_matches = re.findall(r'File ["\'](.*?.py)["\']', traceback_str)
1037
+
1038
+ # Select the last file path as the most relevant one
902
1039
  file_path = file_matches[-1] if file_matches else None
1040
+
1041
+ # Split the traceback into individual lines for processing
903
1042
  tb_lines = traceback_str.split('\n')
904
1043
  clean_lines = []
905
1044
  relevant_lines_started = False
1045
+
1046
+ # Iterate through each line to filter out framework internals
906
1047
  for line in tb_lines:
1048
+
1049
+ # Skip lines that are part of unittest, Python standard library, or site-packages
907
1050
  if any(s in line for s in ['unittest/', 'lib/python', 'site-packages']):
908
1051
  continue
1052
+
1053
+ # Start collecting lines from the first occurrence of the relevant file path
909
1054
  if file_path and file_path in line and not relevant_lines_started:
910
1055
  relevant_lines_started = True
911
1056
  if relevant_lines_started:
912
1057
  clean_lines.append(line)
1058
+
1059
+ # Join the filtered lines to form the cleaned traceback
913
1060
  clean_tb = str('\n').join(clean_lines) if clean_lines else traceback_str
914
1061
  return file_path, clean_tb
915
1062
 
@@ -919,25 +1066,30 @@ class UnitTest(IUnitTest):
919
1066
  execution_time: float
920
1067
  ) -> Dict[str, Any]:
921
1068
  """
922
- Generate a summary of the test suite execution.
1069
+ Generates a summary dictionary of the test suite execution, including statistics,
1070
+ timing, and detailed results for each test. Optionally persists the summary and/or
1071
+ generates a web report if configured.
923
1072
 
924
1073
  Parameters
925
1074
  ----------
926
1075
  result : unittest.TestResult
927
- Result object containing details of the test execution.
1076
+ The result object containing details of the test execution.
928
1077
  execution_time : float
929
- Total execution time in milliseconds.
1078
+ The total execution time of the test suite in seconds.
930
1079
 
931
1080
  Returns
932
1081
  -------
933
1082
  dict
934
- Dictionary containing test statistics and details.
1083
+ A dictionary containing test statistics, details, and metadata.
935
1084
 
936
- Side Effects
937
- ------------
938
- If persistence is enabled, the summary is persisted to storage.
939
- If web reporting is enabled, a web report is generated.
1085
+ Notes
1086
+ -----
1087
+ - If persistence is enabled, the summary is saved to storage.
1088
+ - If web reporting is enabled, a web report is generated.
1089
+ - The summary includes per-test details, overall statistics, and a timestamp.
940
1090
  """
1091
+
1092
+ # Collect detailed information for each test result
941
1093
  test_details = []
942
1094
  for test_result in result.test_results:
943
1095
  rst: TestResult = test_result
@@ -952,8 +1104,14 @@ class UnitTest(IUnitTest):
952
1104
  'file_path': rst.file_path,
953
1105
  'doc_string': rst.doc_string
954
1106
  })
1107
+
1108
+ # Calculate the number of passed tests
955
1109
  passed = result.testsRun - len(result.failures) - len(result.errors) - len(result.skipped)
1110
+
1111
+ # Calculate the success rate as a percentage
956
1112
  success_rate = (passed / result.testsRun * 100) if result.testsRun > 0 else 100.0
1113
+
1114
+ # Build the summary dictionary with all relevant statistics and details
957
1115
  self.__result = {
958
1116
  "total_tests": result.testsRun,
959
1117
  "passed": passed,
@@ -965,10 +1123,16 @@ class UnitTest(IUnitTest):
965
1123
  "test_details": test_details,
966
1124
  "timestamp": datetime.now().isoformat()
967
1125
  }
1126
+
1127
+ # Persist the summary if persistence is enabled
968
1128
  if self.__persistent:
969
1129
  self.__handlePersistResults(self.__result)
1130
+
1131
+ # Generate a web report if web reporting is enabled
970
1132
  if self.__web_report:
971
1133
  self.__handleWebReport(self.__result)
1134
+
1135
+ # Return the summary dictionary
972
1136
  return self.__result
973
1137
 
974
1138
  def __handleWebReport(
@@ -986,12 +1150,25 @@ class UnitTest(IUnitTest):
986
1150
  Returns
987
1151
  -------
988
1152
  None
1153
+
1154
+ Notes
1155
+ -----
1156
+ This method creates a web-based report for the given test results summary.
1157
+ It uses the TestingResultRender class to generate the report, passing the storage path,
1158
+ the summary result, and a flag indicating whether to persist the report based on the
1159
+ persistence configuration and driver. After rendering, it prints a link to the generated
1160
+ web report using the printer.
989
1161
  """
1162
+
1163
+ # Create a TestingResultRender instance with the storage path, result summary,
1164
+ # and persistence flag (True if persistent and using sqlite driver)
990
1165
  render = TestingResultRender(
991
1166
  storage_path=self.__storage,
992
1167
  result=summary,
993
1168
  persist=self.__persistent and self.__persistent_driver == 'sqlite'
994
1169
  )
1170
+
1171
+ # Print the link to the generated web report
995
1172
  self.__printer.linkWebReport(render.render())
996
1173
 
997
1174
  def __handlePersistResults(
@@ -1004,11 +1181,7 @@ class UnitTest(IUnitTest):
1004
1181
  Parameters
1005
1182
  ----------
1006
1183
  summary : dict
1007
- Test results summary to persist.
1008
-
1009
- Returns
1010
- -------
1011
- None
1184
+ The summary dictionary containing test results and metadata to be persisted.
1012
1185
 
1013
1186
  Raises
1014
1187
  ------
@@ -1016,20 +1189,40 @@ class UnitTest(IUnitTest):
1016
1189
  If there is an error creating directories or writing files.
1017
1190
  OrionisTestPersistenceError
1018
1191
  If database operations fail or any other error occurs during persistence.
1192
+
1193
+ Notes
1194
+ -----
1195
+ This method persists the test results summary according to the configured persistence driver.
1196
+ If the driver is set to 'sqlite', the summary is stored in a SQLite database using the TestLogs class.
1197
+ If the driver is set to 'json', the summary is saved as a JSON file in the specified storage directory,
1198
+ with a filename based on the current timestamp. The method ensures that the target directory exists,
1199
+ and handles any errors that may occur during file or database operations.
1019
1200
  """
1020
1201
  try:
1202
+
1203
+ # If the persistence driver is SQLite, store the summary in the database
1021
1204
  if self.__persistent_driver == PersistentDrivers.SQLITE.value:
1022
1205
  history = TestLogs(self.__storage)
1023
1206
  history.create(summary)
1207
+
1208
+ # If the persistence driver is JSON, write the summary to a JSON file
1024
1209
  elif self.__persistent_driver == PersistentDrivers.JSON.value:
1025
1210
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1026
1211
  log_path = Path(self.__storage) / f"{timestamp}_test_results.json"
1212
+
1213
+ # Ensure the parent directory exists
1027
1214
  log_path.parent.mkdir(parents=True, exist_ok=True)
1215
+
1216
+ # Write the summary to the JSON file
1028
1217
  with open(log_path, 'w', encoding='utf-8') as log:
1029
1218
  json.dump(summary, log, indent=4)
1030
1219
  except OSError as e:
1220
+
1221
+ # Raise an error if directory creation or file writing fails
1031
1222
  raise OSError(f"Error creating directories or writing files: {str(e)}")
1032
1223
  except Exception as e:
1224
+
1225
+ # Raise a persistence error for any other exceptions
1033
1226
  raise OrionisTestPersistenceError(f"Error persisting test results: {str(e)}")
1034
1227
 
1035
1228
  def __filterTestsByName(
@@ -1038,37 +1231,57 @@ class UnitTest(IUnitTest):
1038
1231
  pattern: str
1039
1232
  ) -> unittest.TestSuite:
1040
1233
  """
1041
- Filter tests in a test suite based on a specified name pattern.
1234
+ Filter tests in a test suite by a regular expression pattern applied to test names.
1042
1235
 
1043
1236
  Parameters
1044
1237
  ----------
1045
1238
  suite : unittest.TestSuite
1046
- Test suite containing the tests to filter.
1239
+ The test suite containing the tests to be filtered.
1047
1240
  pattern : str
1048
- Regular expression pattern to match test names.
1241
+ Regular expression pattern to match against test names (test IDs).
1049
1242
 
1050
1243
  Returns
1051
1244
  -------
1052
1245
  unittest.TestSuite
1053
- New test suite containing only tests that match the pattern.
1246
+ A new TestSuite containing only the tests whose names match the given pattern.
1054
1247
 
1055
1248
  Raises
1056
1249
  ------
1057
1250
  OrionisTestValueError
1058
1251
  If the provided pattern is not a valid regular expression.
1252
+
1253
+ Notes
1254
+ -----
1255
+ This method compiles the provided regular expression and applies it to the IDs of all test cases
1256
+ in the flattened suite. Only tests whose IDs match the pattern are included in the returned suite.
1257
+ If the pattern is invalid, an OrionisTestValueError is raised with details about the regex error.
1059
1258
  """
1259
+
1260
+ # Create a new TestSuite to hold the filtered tests
1060
1261
  filtered_suite = unittest.TestSuite()
1262
+
1061
1263
  try:
1264
+
1265
+ # Compile the provided regular expression pattern
1062
1266
  regex = re.compile(pattern)
1267
+
1063
1268
  except re.error as e:
1269
+
1270
+ # Raise a value error if the regex is invalid
1064
1271
  raise OrionisTestValueError(
1065
1272
  f"The provided test name pattern is invalid: '{pattern}'. "
1066
1273
  f"Regular expression compilation error: {str(e)}. "
1067
1274
  "Please check the pattern syntax and try again."
1068
1275
  )
1276
+
1277
+ # Iterate through all test cases in the flattened suite
1069
1278
  for test in self.__flattenTestSuite(suite):
1279
+
1280
+ # Add the test to the filtered suite if its ID matches the regex
1070
1281
  if regex.search(test.id()):
1071
1282
  filtered_suite.addTest(test)
1283
+
1284
+ # Return the suite containing only the filtered tests
1072
1285
  return filtered_suite
1073
1286
 
1074
1287
  def __filterTestsByTags(
@@ -1077,32 +1290,56 @@ class UnitTest(IUnitTest):
1077
1290
  tags: List[str]
1078
1291
  ) -> unittest.TestSuite:
1079
1292
  """
1080
- Filter tests in a unittest TestSuite by specified tags.
1293
+ Filters tests in a unittest TestSuite by matching specified tags.
1081
1294
 
1082
1295
  Parameters
1083
1296
  ----------
1084
1297
  suite : unittest.TestSuite
1085
- Original TestSuite containing all tests.
1298
+ The original TestSuite containing all test cases to be filtered.
1086
1299
  tags : list of str
1087
- Tags to filter the tests by.
1300
+ List of tags to filter the tests by.
1088
1301
 
1089
1302
  Returns
1090
1303
  -------
1091
1304
  unittest.TestSuite
1092
- New TestSuite containing only tests with matching tags.
1305
+ A new TestSuite containing only the tests that have at least one matching tag.
1306
+
1307
+ Notes
1308
+ -----
1309
+ This method inspects each test case in the provided suite and checks for the presence of tags
1310
+ either on the test method (via a `__tags__` attribute) or on the test class instance itself.
1311
+ If any of the specified tags are found in the test's tags, the test is included in the returned suite.
1093
1312
  """
1313
+
1314
+ # Create a new TestSuite to hold the filtered tests
1094
1315
  filtered_suite = unittest.TestSuite()
1316
+
1317
+ # Convert the list of tags to a set for efficient intersection checks
1095
1318
  tag_set = set(tags)
1319
+
1320
+ # Iterate through all test cases in the flattened suite
1096
1321
  for test in self.__flattenTestSuite(suite):
1322
+
1323
+ # Attempt to retrieve the test method from the test case
1097
1324
  test_method = getattr(test, test._testMethodName, None)
1325
+
1326
+ # Check if the test method has a __tags__ attribute
1098
1327
  if hasattr(test_method, '__tags__'):
1099
1328
  method_tags = set(getattr(test_method, '__tags__'))
1329
+
1330
+ # If there is any intersection between the method's tags and the filter tags, add the test
1100
1331
  if tag_set.intersection(method_tags):
1101
1332
  filtered_suite.addTest(test)
1333
+
1334
+ # If the method does not have tags, check if the test case itself has a __tags__ attribute
1102
1335
  elif hasattr(test, '__tags__'):
1103
1336
  class_tags = set(getattr(test, '__tags__'))
1337
+
1338
+ # If there is any intersection between the class's tags and the filter tags, add the test
1104
1339
  if tag_set.intersection(class_tags):
1105
1340
  filtered_suite.addTest(test)
1341
+
1342
+ # Return the suite containing only the filtered tests
1106
1343
  return filtered_suite
1107
1344
 
1108
1345
  def getTestNames(
@@ -1204,4 +1441,4 @@ class UnitTest(IUnitTest):
1204
1441
  -------
1205
1442
  None
1206
1443
  """
1207
- self.__printer.print(self.__error_buffer)
1444
+ self.__printer.print(self.__error_buffer)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.434.0
3
+ Version: 0.435.0
4
4
  Summary: Orionis Framework – Elegant, Fast, and Powerful.
5
5
  Home-page: https://github.com/orionis-framework/framework
6
6
  Author: Raul Mauricio Uñate Castro
@@ -173,7 +173,7 @@ orionis/foundation/providers/progress_bar_provider.py,sha256=WW3grNgH-yV2meSSTeO
173
173
  orionis/foundation/providers/testing_provider.py,sha256=iJSN2RIChbYIL-1ue6vmPmDMCSrvERDkti4Er9MPiLA,1102
174
174
  orionis/foundation/providers/workers_provider.py,sha256=kiQjQRyUEyiBX2zcbF_KmqRgvc7Bvxsvg5oMtIvYniM,1075
175
175
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
- orionis/metadata/framework.py,sha256=1M3mvFtAf9Oy_DQRsl17tgyk_z8D92EU7Z_AZQ-CxQo,4088
176
+ orionis/metadata/framework.py,sha256=xZaOkjKk17-9lod4InCFpqe5TDF3tAggkcTKJ1Bx5Nk,4088
177
177
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
178
178
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
179
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -307,7 +307,7 @@ orionis/test/contracts/render.py,sha256=wpDQzUtT0r8KFZ7zPcxWHXQ1EVNKxzA_rZ6ZKUcZ
307
307
  orionis/test/contracts/test_result.py,sha256=SNXJ2UerkweYn7uCT0i0HmMGP0XBrL_9KJs-0ZvIYU4,4002
308
308
  orionis/test/contracts/unit_test.py,sha256=PSnjEyM-QGQ3Pm0ZOqaa8QdPOtilGBVO4R87JYdVa-8,5386
309
309
  orionis/test/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
310
- orionis/test/core/unit_test.py,sha256=Am3o3xc91CZkVYd-Kn9JypSig3ZYbC8eHcEK2YLncKs,44598
310
+ orionis/test/core/unit_test.py,sha256=NiFk1u_a69JjQXBkXIzvlswoOniNm4YV4_dDGajRqQk,57400
311
311
  orionis/test/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
312
  orionis/test/entities/result.py,sha256=IMAd1AiwOf2z8krTDBFMpQe_1PG4YJ5Z0qpbr9xZwjg,4507
313
313
  orionis/test/enums/__init__.py,sha256=M3imAgMvKFTKg55FbtVoY3zxj7QRY9AfaUWxiSZVvn4,66
@@ -341,7 +341,7 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
341
341
  orionis/test/validators/workers.py,sha256=HcZ3cnrk6u7cvM1xZpn_lsglHAq69_jx9RcTSvLrdb0,1204
342
342
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
343
343
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
344
- orionis-0.434.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
344
+ orionis-0.435.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
345
345
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
346
346
  tests/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
347
347
  tests/container/test_container.py,sha256=asv8TkkupVoex6SWod74NBl4dSs7wb9mLmu_glNdNy8,14815
@@ -486,8 +486,8 @@ tests/testing/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
486
486
  tests/testing/validators/test_testing_validators.py,sha256=WPo5GxTP6xE-Dw3X1vZoqOMpb6HhokjNSbgDsDRDvy4,16588
487
487
  tests/testing/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
488
488
  tests/testing/view/test_render.py,sha256=tnnMBwS0iKUIbogLvu-7Rii50G6Koddp3XT4wgdFEYM,1050
489
- orionis-0.434.0.dist-info/METADATA,sha256=OhYJIu6SlGHskK8ZSYOhErAlvWr6O3qkrWHYRyG7Gbw,4772
490
- orionis-0.434.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
491
- orionis-0.434.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
492
- orionis-0.434.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
493
- orionis-0.434.0.dist-info/RECORD,,
489
+ orionis-0.435.0.dist-info/METADATA,sha256=ZRvGT2JiamyywWKMCGIXaN8J2j445-wpiBz7cRrRYe4,4772
490
+ orionis-0.435.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
491
+ orionis-0.435.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
492
+ orionis-0.435.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
493
+ orionis-0.435.0.dist-info/RECORD,,