1
+ import concurrent .futures
2
+ import datetime
3
+ import signal
4
+ import traceback
5
+ from dataclasses import dataclass
1
6
from 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
+
3
11
from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructure import (
4
12
DetectionTestingInfrastructure ,
13
+ DetectionTestingManagerOutputDto ,
5
14
)
6
15
from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructureContainer import (
7
16
DetectionTestingInfrastructureContainer ,
8
17
)
9
18
from contentctl .actions .detection_testing .infrastructures .DetectionTestingInfrastructureServer import (
10
19
DetectionTestingInfrastructureServer ,
11
20
)
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
- )
22
21
from contentctl .actions .detection_testing .views .DetectionTestingView import (
23
22
DetectionTestingView ,
24
23
)
25
- from contentctl .objects .enums import PostTestBehavior
26
- from pydantic import BaseModel
24
+ from contentctl .objects .config import Container , Infrastructure , test , test_servers
27
25
from contentctl .objects .detection import Detection
28
- import concurrent .futures
29
- import docker
26
+ from contentctl .objects .enums import PostTestBehavior
30
27
31
28
32
29
@dataclass (frozen = False )
@@ -63,12 +60,14 @@ def sigint_handler(signum, frame):
63
60
# a newline '\r\n' which will cause that wait to stop
64
61
print ("*******************************" )
65
62
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."
67
65
)
68
66
print ("*******************************" )
69
67
70
68
signal .signal (signal .SIGINT , sigint_handler )
71
69
70
+ # TODO (#337): futures can be hard to maintain/debug; let's consider alternatives
72
71
with (
73
72
concurrent .futures .ThreadPoolExecutor (
74
73
max_workers = len (self .input_dto .config .test_instances ),
@@ -80,10 +79,19 @@ def sigint_handler(signum, frame):
80
79
max_workers = len (self .input_dto .config .test_instances ),
81
80
) as view_shutdowner ,
82
81
):
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
+
83
90
# Start all the views
84
91
future_views = {
85
92
view_runner .submit (view .setup ): view for view in self .input_dto .views
86
93
}
94
+
87
95
# Configure all the instances
88
96
future_instances_setup = {
89
97
instance_pool .submit (instance .setup ): instance
@@ -96,7 +104,11 @@ def sigint_handler(signum, frame):
96
104
future .result ()
97
105
except Exception as e :
98
106
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 )
100
112
101
113
# Start and wait for all tests to run
102
114
if not self .output_dto .terminate :
@@ -111,7 +123,11 @@ def sigint_handler(signum, frame):
111
123
future .result ()
112
124
except Exception as e :
113
125
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 )
115
131
116
132
self .output_dto .terminate = True
117
133
@@ -123,14 +139,34 @@ def sigint_handler(signum, frame):
123
139
try :
124
140
future .result ()
125
141
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 )
127
147
128
148
# Wait for original view-related threads to complete
129
149
for future in concurrent .futures .as_completed (future_views ):
130
150
try :
131
151
future .result ()
132
152
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 ()
134
170
135
171
return self .output_dto
136
172
@@ -154,12 +190,15 @@ def create_DetectionTestingInfrastructureObjects(self):
154
190
)
155
191
if len (parts ) != 2 :
156
192
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 ':'"
159
197
)
160
198
161
199
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 } ]..." ,
163
202
end = "" ,
164
203
flush = True ,
165
204
)
@@ -168,7 +207,8 @@ def create_DetectionTestingInfrastructureObjects(self):
168
207
break
169
208
except Exception as e :
170
209
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 )} "
172
212
)
173
213
174
214
already_staged_container_files = False
0 commit comments