11import json
2+ from types import MethodType
23
34import boto3
45import pytest
5- from moto import mock_ecs , mock_sqs , mock_s3 , mock_ec2
6+ from moto import mock_ecs , mock_sqs , mock_s3 , mock_ec2 , mock_logs
67
78import run
89import config
@@ -51,26 +52,61 @@ def test_generate_ecs_config(self, s3, run_submitJob, tmp_path):
5152class EarlyTermination (Exception ):
5253 ...
5354
54- def hijack_client (real_client , service_name ):
55+ def hijack_client (real_client , service_name , service_fn = None , service_count = 1 , service_fn_count = 1 ):
5556 """
56- Patches boto3.client so that an invocation of a service
57+ If service_fn is None,
58+ Patches boto3.client('<some_service>') so that an invocation of a service
5759 (eg 'ec2' or 'logs') will raise an EarlyTermination exception, allowing
5860 inspection and testing of the stack frame up until that point.
61+
62+ If service_fn is not None,
63+ Patches boto3.client('<some_service>').<service_fn> so that an invocation
64+ of a service function (eg 'put_retention_policy') will raise an EarlyTermination
65+ exception, allowing inspection and testing of the stack frame up until that point.
66+
67+ 'service_count' represents the number of times the service can be called
68+ before the EarlyTermination exception is raised.
69+
70+ `service_fn_count` represents the number of times the service function can be called
71+ before the EarlyTermination exception is raised.
5972 """
73+ if service_fn is None :
74+ real_client ._called_n_times = service_count
75+
6076 def f (* args , ** kwargs ):
61- if (args [0 ] == service_name ):
62- raise EarlyTermination ("early termination" )
63-
64- return real_client (* args , ** kwargs )
77+ real_client_obj = real_client (* args , ** kwargs )
78+
79+ if service_fn is None and args [0 ] == service_name :
80+ real_client ._called_n_times -= 1
81+ if real_client ._called_n_times == 0 :
82+ raise EarlyTermination ("early termination" )
83+
84+ elif service_fn is not None and args [0 ] == service_name :
85+ real_client_obj ._called_n_times = service_fn_count
86+
87+ real_fn = getattr (real_client_obj , service_fn )
88+
89+ def hijacked_fn (* args , ** kwargs ):
90+ real_fn (* args , ** kwargs )
91+ real_client_obj ._called_n_times -= 1
92+ if real_client_obj ._called_n_times == 0 :
93+ raise EarlyTermination ("early termination" )
94+
95+ setattr (real_client_obj , service_fn , hijacked_fn )
96+
97+ return real_client_obj
6598
6699 return f
67100
68- class TestSpotFleetConfig :
69101
102+ class TestSpotFleetConfig :
70103 @mock_ecs
71104 @mock_sqs
72105 @mock_s3
73106 def test_spot_fleet_config (self , run_startCluster , monkeypatch ):
107+ """
108+ startCluster Step 1: set up the configuration files
109+ """
74110 monkeypatch .setattr (boto3 , "client" , hijack_client (boto3 .client , 'ec2' ))
75111 with pytest .raises (EarlyTermination ) as e_info :
76112 run_startCluster ()
@@ -141,6 +177,9 @@ def test_spot_fleet_config(self, run_startCluster, monkeypatch):
141177 @mock_s3
142178 @mock_ec2
143179 def test_make_spot_fleet_request (self , run_startCluster , monkeypatch ):
180+ """
181+ startCluster Step 2: make the spot fleet request
182+ """
144183 monkeypatch .setattr (boto3 , "client" , hijack_client (boto3 .client , 'logs' ))
145184 with pytest .raises (EarlyTermination ) as e_info :
146185 run_startCluster ()
@@ -168,6 +207,9 @@ class TestCreateMonitor:
168207 @mock_s3
169208 @mock_ec2
170209 def test_create_monitor (self , run_startCluster , monkeypatch ):
210+ """
211+ startCluster Step 3: Make the monitor
212+ """
171213 monkeypatch .setattr (boto3 , "client" , hijack_client (boto3 .client , 'logs' ))
172214 with pytest .raises (EarlyTermination ) as e_info :
173215 run_startCluster ()
@@ -189,6 +231,55 @@ def test_create_monitor(self, run_startCluster, monkeypatch):
189231 assert monitor_file_res ["MONITOR_LOG_GROUP_NAME" ] == config .LOG_GROUP_NAME
190232
191233
234+ class TestCreateLogGroup :
235+ @mock_ecs
236+ @mock_sqs
237+ @mock_s3
238+ @mock_ec2
239+ @mock_logs
240+ def test_create_log_group (self , run_startCluster , monkeypatch ):
241+ """
242+ startCluster Step 4: Create the log group
243+ """
244+ monkeypatch .setattr (boto3 , "client" , hijack_client (
245+ boto3 .client ,
246+ 'logs' ,
247+ service_fn = 'put_retention_policy' ,
248+ service_fn_count = 2
249+ ))
250+
251+ with pytest .raises (EarlyTermination ) as e_info :
252+ run_startCluster ()
253+
254+ log_group_info_res = None
255+ for tb in e_info .traceback :
256+ if (tb .name == "startCluster" ):
257+ log_group_info_res = tb .frame .f_locals ["loggroupinfo" ]
258+
259+ assert log_group_info_res is not None
260+ assert log_group_info_res ["ResponseMetadata" ]["HTTPStatusCode" ] == 200
261+
262+ logs = boto3 .client ("logs" )
263+ log_group_info_res = logs .describe_log_groups (logGroupNamePrefix = config .LOG_GROUP_NAME )
264+
265+ assert "logGroups" in log_group_info_res
266+ log_groups = log_group_info_res ["logGroups" ]
267+
268+ name_log_groups = list (filter (
269+ lambda lg : lg ["logGroupName" ] == config .LOG_GROUP_NAME , log_groups
270+ ))
271+
272+ per_instance_log_groups = list (filter (
273+ lambda lg : lg ["logGroupName" ] == config .LOG_GROUP_NAME + "_perInstance" , log_groups
274+ ))
275+
276+ assert len (name_log_groups ) == 1
277+ assert len (per_instance_log_groups ) == 1
278+
279+ assert name_log_groups [0 ]["arn" ] == f"arn:aws:logs:{ config .AWS_REGION } :123456789012:log-group:{ config .LOG_GROUP_NAME } "
280+ assert per_instance_log_groups [0 ]["arn" ] == f"arn:aws:logs:{ config .AWS_REGION } :123456789012:log-group:{ config .LOG_GROUP_NAME } _perInstance"
281+
282+
192283class TestStartCluster :
193284 @mock_ecs
194285 @mock_sqs
0 commit comments