1+ import concurrent .futures
2+ import datetime
3+ import signal
4+ import traceback
5+ from dataclasses import dataclass
16from typing import List , Union
2- from contentctl .objects .config import test , test_servers , Container , Infrastructure
7+
8+ import docker
9+ from pydantic import BaseModel
10+
311from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructure import (
412 DetectionTestingInfrastructure ,
13+ DetectionTestingManagerOutputDto ,
514)
615from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructureContainer import (
716 DetectionTestingInfrastructureContainer ,
817)
918from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructureServer import (
1019 DetectionTestingInfrastructureServer ,
1120)
12- import signal
13- import datetime
14-
15- # from queue import Queue
16- from dataclasses import dataclass
17-
18- # import threading
19- from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructure import (
20- DetectionTestingManagerOutputDto ,
21- )
2221from contentctl .actions .detection_testing .views .DetectionTestingView import (
2322 DetectionTestingView ,
2423)
25- from contentctl .objects .enums import PostTestBehavior
26- from pydantic import BaseModel
24+ from contentctl .objects .config import Container , Infrastructure , test , test_servers
2725from contentctl .objects .detection import Detection
28- import concurrent .futures
29- import docker
26+ from contentctl .objects .enums import PostTestBehavior
3027
3128
3229@dataclass (frozen = False )
@@ -63,12 +60,14 @@ def sigint_handler(signum, frame):
6360 # a newline '\r\n' which will cause that wait to stop
6461 print ("*******************************" )
6562 print (
66- "If testing is paused and you are debugging a detection, you MUST hit CTRL-D at the prompt to complete shutdown."
63+ "If testing is paused and you are debugging a detection, you MUST hit CTRL-D "
64+ "at the prompt to complete shutdown."
6765 )
6866 print ("*******************************" )
6967
7068 signal .signal (signal .SIGINT , sigint_handler )
7169
70+ # TODO (#337): futures can be hard to maintain/debug; let's consider alternatives
7271 with (
7372 concurrent .futures .ThreadPoolExecutor (
7473 max_workers = len (self .input_dto .config .test_instances ),
@@ -80,10 +79,19 @@ def sigint_handler(signum, frame):
8079 max_workers = len (self .input_dto .config .test_instances ),
8180 ) as view_shutdowner ,
8281 ):
82+ # Capture any errors for reporting at the end after all threads have been gathered
83+ errors : dict [str , list [Exception ]] = {
84+ "INSTANCE SETUP ERRORS" : [],
85+ "TESTING ERRORS" : [],
86+ "ERRORS DURING VIEW SHUTDOWN" : [],
87+ "ERRORS DURING VIEW EXECUTION" : [],
88+ }
89+
8390 # Start all the views
8491 future_views = {
8592 view_runner .submit (view .setup ): view for view in self .input_dto .views
8693 }
94+
8795 # Configure all the instances
8896 future_instances_setup = {
8997 instance_pool .submit (instance .setup ): instance
@@ -96,7 +104,11 @@ def sigint_handler(signum, frame):
96104 future .result ()
97105 except Exception as e :
98106 self .output_dto .terminate = True
99- print (f"Error setting up container: { str (e )} " )
107+ # Output the traceback if we encounter errors in verbose mode
108+ if self .input_dto .config .verbose :
109+ tb = traceback .format_exc ()
110+ print (tb )
111+ errors ["INSTANCE SETUP ERRORS" ].append (e )
100112
101113 # Start and wait for all tests to run
102114 if not self .output_dto .terminate :
@@ -111,7 +123,11 @@ def sigint_handler(signum, frame):
111123 future .result ()
112124 except Exception as e :
113125 self .output_dto .terminate = True
114- print (f"Error running in container: { str (e )} " )
126+ # Output the traceback if we encounter errors in verbose mode
127+ if self .input_dto .config .verbose :
128+ tb = traceback .format_exc ()
129+ print (tb )
130+ errors ["TESTING ERRORS" ].append (e )
115131
116132 self .output_dto .terminate = True
117133
@@ -123,14 +139,34 @@ def sigint_handler(signum, frame):
123139 try :
124140 future .result ()
125141 except Exception as e :
126- print (f"Error stopping view: { str (e )} " )
142+ # Output the traceback if we encounter errors in verbose mode
143+ if self .input_dto .config .verbose :
144+ tb = traceback .format_exc ()
145+ print (tb )
146+ errors ["ERRORS DURING VIEW SHUTDOWN" ].append (e )
127147
128148 # Wait for original view-related threads to complete
129149 for future in concurrent .futures .as_completed (future_views ):
130150 try :
131151 future .result ()
132152 except Exception as e :
133- print (f"Error running container: { str (e )} " )
153+ # Output the traceback if we encounter errors in verbose mode
154+ if self .input_dto .config .verbose :
155+ tb = traceback .format_exc ()
156+ print (tb )
157+ errors ["ERRORS DURING VIEW EXECUTION" ].append (e )
158+
159+ # Log any errors
160+ for error_type in errors :
161+ if len (errors [error_type ]) > 0 :
162+ print ()
163+ print (f"[{ error_type } ]:" )
164+ for error in errors [error_type ]:
165+ print (f"\t ❌ { str (error )} " )
166+ if isinstance (error , ExceptionGroup ):
167+ for suberror in error .exceptions : # type: ignore
168+ print (f"\t \t ❌ { str (suberror )} " ) # type: ignore
169+ print ()
134170
135171 return self .output_dto
136172
@@ -154,12 +190,15 @@ def create_DetectionTestingInfrastructureObjects(self):
154190 )
155191 if len (parts ) != 2 :
156192 raise Exception (
157- f"Expected to find a name:tag in { self .input_dto .config .container_settings .full_image_path } , "
158- f"but instead found { parts } . Note that this path MUST include the tag, which is separated by ':'"
193+ "Expected to find a name:tag in "
194+ f"{ self .input_dto .config .container_settings .full_image_path } , "
195+ f"but instead found { parts } . Note that this path MUST include the "
196+ "tag, which is separated by ':'"
159197 )
160198
161199 print (
162- f"Getting the latest version of the container image [{ self .input_dto .config .container_settings .full_image_path } ]..." ,
200+ "Getting the latest version of the container image "
201+ f"[{ self .input_dto .config .container_settings .full_image_path } ]..." ,
163202 end = "" ,
164203 flush = True ,
165204 )
@@ -168,7 +207,8 @@ def create_DetectionTestingInfrastructureObjects(self):
168207 break
169208 except Exception as e :
170209 raise Exception (
171- f"Failed to pull docker container image [{ self .input_dto .config .container_settings .full_image_path } ]: { str (e )} "
210+ "Failed to pull docker container image "
211+ f"[{ self .input_dto .config .container_settings .full_image_path } ]: { str (e )} "
172212 )
173213
174214 already_staged_container_files = False
0 commit comments