2020"""
2121from __future__ import annotations
2222
23+ import logging
24+ import unittest
25+ from datetime import datetime
2326from inspect import signature
24- from typing import Any , List , Optional , TextIO , Union
25- from unittest import TestResult
27+ from pathlib import Path
28+ from typing import Any , List , Literal , Optional , Union
29+ from unittest import TestResult , util
2630from unittest .case import _SubTest
2731
2832from pykiso .types import ExcInfoType
2933
34+ from ..logging_initializer import get_internal_level , get_logging_options
3035from ..test_coordinator .test_case import BasicTest
3136from ..test_coordinator .test_suite import BaseTestSuite
3237from .text_result import BannerTestResult
3338from .xml_result import XmlTestResult
3439
40+ test_runner_instance : Optional [unittest .TextTestRunner ] = None
41+
3542
3643class MultiTestResult :
3744 """Class that can take multiple test result classes and ran the
3845 test for all the classes.
3946 """
4047
41- def __init__ (self , * result_classes : TestResult ):
48+ def __init__ (
49+ self ,
50+ * result_classes : TestResult ,
51+ log_file_strategy : Literal ["testRun,testCase" ] | None = None ,
52+ ):
4253 """Initialize parameter
4354
4455 :param result_classes: test result classes
56+ :param log_file_strategy: strategy to use for the log file
57+ - testRun: log file will be created for each test run
58+ - testCase: log file will be created for each test case
4559 """
46- self .result_classes = result_classes
60+ self .result_classes : list [TestResult ] = result_classes
61+ self ._log_file_strategy = log_file_strategy
62+ self ._list_test_results : list [BasicTest ] = []
63+ self .current_log_file : Path | None = None
4764
4865 def __call__ (self , * args , ** kwargs ) -> MultiTestResult :
4966 """Initialize the result classes with the parameters passed in arguments."""
@@ -83,7 +100,7 @@ def __setattr__(self, name: str, value: Any) -> Any:
83100 """
84101 super ().__setattr__ (name , value )
85102 # condition to avoid infinite loop with the __getattr__
86- if name != "result_classes" :
103+ if name not in [ "result_classes" , "_log_file_strategy" , "_list_test_results" , "current_log_file" ] :
87104 for result in self .result_classes :
88105 setattr (result , name , value )
89106
@@ -99,9 +116,43 @@ def startTest(self, test: Union[BasicTest, BaseTestSuite]) -> None:
99116
100117 :param test: running testcase
101118 """
119+ self .handle_log_file_strategy (test )
120+
102121 for result in self .result_classes :
103122 result .startTest (test )
104123
124+ def handle_log_file_strategy (self , test : Union [BasicTest , BaseTestSuite ]) -> None :
125+ """Handle the log file strategy for the given test.
126+
127+ :param test: The test case or test suite being executed.
128+ """
129+ if self ._log_file_strategy is None :
130+ return
131+
132+ log_options = get_logging_options ()
133+
134+ if test .__class__ not in self ._list_test_results or self ._log_file_strategy == "testRun" :
135+ log_file_name = util .strclass (test .__class__ ).replace ("." , "_" ).replace ("-" , "_" )
136+ if self ._log_file_strategy == "testRun" :
137+ log_file_name += "_" + test ._testMethodName
138+
139+ log_file_name += f"_{ datetime .today ().strftime ('%Y%d%m%H%M%S' )} .log"
140+ self .current_log_file = log_options .log_path .parent / log_file_name
141+
142+ # Setup the file handler for logging
143+ log_format = logging .Formatter ("%(asctime)s [%(levelname)s] %(module)s:%(lineno)d: %(message)s" )
144+ root_logger = logging .getLogger ()
145+ file_handler = logging .FileHandler (self .current_log_file , "a+" )
146+ file_handler .setFormatter (log_format )
147+ file_handler .name = "strategy_log_file_handler"
148+ # always include internal logs in log files
149+ file_handler .setLevel (get_internal_level (log_options .log_level ))
150+ root_logger .addHandler (file_handler )
151+
152+ # Add the log file to the test runner instance to get the banner information
153+ test_runner_instance .stream .stream .multifile_handler .add_file (self .current_log_file )
154+ self ._list_test_results .append (test .__class__ )
155+
105156 def startTestRun (self ) -> None :
106157 """Call the startTestRun function for all result classes."""
107158 for result in self .result_classes :
@@ -125,6 +176,16 @@ def stopTest(self, test: Union[BasicTest, BaseTestSuite]) -> None:
125176 for result in self .result_classes :
126177 result .stopTest (test )
127178
179+ if self ._log_file_strategy is None :
180+ return
181+ # Remove the log file
182+ test_runner_instance .stream .stream .multifile_handler .remove_file (self .current_log_file )
183+ root_logger = logging .getLogger ()
184+ for handler in root_logger .handlers :
185+ if handler .name == "strategy_log_file_handler" :
186+ root_logger .removeHandler (handler )
187+ handler .close ()
188+
128189 def addSuccess (self , test : Union [BasicTest , BaseTestSuite ]) -> None :
129190 """Call the addSuccess function for all result classes.
130191
0 commit comments