1414import glob
1515import numpy as np
1616from netCDF4 import Dataset
17+ import time
1718
1819# Check if pyshtools is available
1920try :
@@ -39,9 +40,16 @@ def setUpClass(cls):
3940 cls .project_root = os .path .dirname (os .path .dirname (os .path .abspath (__file__ )))
4041 print (f"Project root directory: { cls .project_root } " )
4142
43+ # Start timing for test file creation
44+ start_time = time .time ()
45+
4246 # Run the script to create test netCDF files
4347 cls .create_test_files ()
4448
49+ # Report how long file creation took
50+ elapsed = time .time () - start_time
51+ print (f"Test file creation completed in { elapsed :.2f} seconds" )
52+
4553 # Dictionary to keep track of modified files
4654 cls .modified_files = {}
4755
@@ -75,12 +83,15 @@ def create_test_files(cls):
7583 cmd ,
7684 capture_output = True ,
7785 text = True ,
78- cwd = cls .test_dir # Run in the test directory to ensure files are created there
86+ cwd = cls .test_dir , # Run in the test directory to ensure files are created there
87+ timeout = 300 # Add a timeout to prevent hanging
7988 )
8089
8190 if result .returncode != 0 :
8291 raise Exception (f"Failed to create test files: { result .stderr } " )
8392
93+ except subprocess .TimeoutExpired :
94+ raise Exception ("Timeout creating test files - process took too long and was terminated" )
8495 except Exception as e :
8596 raise Exception (f"Error running create_ames_gcm_files.py: { e } " )
8697
@@ -98,6 +109,9 @@ def create_test_files(cls):
98109 filepath = os .path .join (cls .test_dir , filename )
99110 if not os .path .exists (filepath ):
100111 raise Exception (f"Test file { filename } was not created in { cls .test_dir } " )
112+ # Print file size to help with debugging
113+ file_size = os .path .getsize (filepath ) / (1024 * 1024 ) # Size in MB
114+ print (f"Created { filename } : { file_size :.2f} MB" )
101115
102116
103117 def setUp (self ):
@@ -107,6 +121,10 @@ def setUp(self):
107121 # Store the current test method name
108122 self .current_test = self .id ().split ('.' )[- 1 ]
109123
124+ # Print test start time for debugging
125+ print (f"\n Starting test: { self .current_test } at { time .strftime ('%H:%M:%S' )} " )
126+ self .start_time = time .time ()
127+
110128 # Initialize an empty list to track files created by this test
111129 self .__class__ .test_created_files [self .current_test ] = []
112130
@@ -115,6 +133,10 @@ def setUp(self):
115133
116134 def tearDown (self ):
117135 """Clean up after each test"""
136+ # Calculate and print test duration
137+ elapsed = time .time () - self .start_time
138+ print (f"Test { self .current_test } completed in { elapsed :.2f} seconds" )
139+
118140 # Get files that exist after the test
119141 files_after_test = set (os .listdir (self .test_dir ))
120142
@@ -186,15 +208,19 @@ def run_mars_files(self, args):
186208 # Get a snapshot of files before running the command
187209 files_before = set (os .listdir (self .test_dir ))
188210
189- # Run the command
211+ # Run the command with a timeout
212+ start_time = time .time ()
190213 try :
191214 result = subprocess .run (
192215 cmd ,
193216 capture_output = True ,
194217 text = True ,
195218 cwd = self .test_dir , # Run in the test directory
196- env = dict (os .environ , PWD = self .test_dir ) # Ensure current working directory is set
219+ env = dict (os .environ , PWD = self .test_dir ), # Ensure current working directory is set
220+ timeout = 300 # Set a reasonable timeout (5 minutes) per subprocess
197221 )
222+ elapsed = time .time () - start_time
223+ print (f"Subprocess completed in { elapsed :.2f} seconds" )
198224
199225 # If command succeeded, find any new files that were created
200226 if result .returncode == 0 :
@@ -213,6 +239,9 @@ def run_mars_files(self, args):
213239 self .modified_files [filename ] = file_path
214240
215241 return result
242+ except subprocess .TimeoutExpired :
243+ print (f"ERROR: Subprocess timed out after { time .time () - start_time :.2f} seconds" )
244+ self .fail ("Subprocess timed out" )
216245 except Exception as e :
217246 self .fail (f"Failed to run MarsFiles: { e } " )
218247
@@ -269,7 +298,7 @@ def test_help_message(self):
269298 for check in help_checks :
270299 self .assertIn (check , result .stdout , f"Help message missing '{ check } '" )
271300
272- print ("✓ Help message displayed successfully" )
301+ print ("Help message displayed successfully" )
273302
274303 def test_time_shift (self ):
275304 """Test time_shift operation on diurn file"""
@@ -285,7 +314,7 @@ def test_time_shift(self):
285314 self .verify_netcdf_has_variable (output_file , 'time' )
286315 self .verify_netcdf_has_variable (output_file , 'time_of_day_24' ) # Default is 24 time of day bins
287316
288- print ("✓ Time shift operation succeeded" )
317+ print ("Time shift operation succeeded" )
289318
290319 def test_time_shift_specific_times (self ):
291320 """Test time_shift operation with specific local times"""
@@ -319,7 +348,7 @@ def test_time_shift_specific_times(self):
319348 finally :
320349 nc .close ()
321350
322- print ("✓ Time shift with specific times succeeded" )
351+ print ("Time shift with specific times succeeded" )
323352
324353 def test_bin_average (self ):
325354 """Test bin_average operation on daily file"""
@@ -347,7 +376,7 @@ def test_bin_average(self):
347376 nc_in .close ()
348377 nc_out .close ()
349378
350- print ("✓ Bin average operation succeeded" )
379+ print ("Bin average operation succeeded" )
351380
352381 def test_bin_diurn (self ):
353382 """Test bin_diurn operation on daily file"""
@@ -373,7 +402,7 @@ def test_bin_diurn(self):
373402 finally :
374403 nc .close ()
375404
376- print ("✓ Bin diurn operation succeeded" )
405+ print ("Bin diurn operation succeeded" )
377406
378407 def test_split_file_by_areo (self ):
379408 """Test split operation on average file by Ls (areo)"""
@@ -406,7 +435,7 @@ def test_split_file_by_areo(self):
406435 ls_files = glob .glob (os .path .join (self .test_dir , '*_Ls*_*.nc' ))
407436 self .assertTrue (len (ls_files ) > 0 , "No output files from split operation found" )
408437
409- print (f"✓ Split file by areo succeeded (Ls range: { ls_range [0 ]} -{ ls_range [1 ]} )" )
438+ print (f"Split file by areo succeeded (Ls range: { ls_range [0 ]} -{ ls_range [1 ]} )" )
410439
411440 def test_split_file_by_lat (self ):
412441 """Test split operation on average file by latitude"""
@@ -436,7 +465,7 @@ def test_split_file_by_lat(self):
436465 finally :
437466 nc .close ()
438467
439- print ("✓ Split file by latitude succeeded" )
468+ print ("Split file by latitude succeeded" )
440469
441470 def test_split_file_by_lon (self ):
442471 """Test split operation on average file by longitude"""
@@ -466,7 +495,7 @@ def test_split_file_by_lon(self):
466495 finally :
467496 nc .close ()
468497
469- print ("✓ Split file by longitude succeeded" )
498+ print ("Split file by longitude succeeded" )
470499
471500 def test_temporal_filters (self ):
472501 """Test all temporal filtering operations"""
@@ -475,21 +504,21 @@ def test_temporal_filters(self):
475504 self .assertEqual (result .returncode , 0 , "High-pass temporal filter command failed" )
476505 high_pass_file = self .check_file_exists ('01336.atmos_daily_hpt.nc' )
477506 self .verify_netcdf_has_variable (high_pass_file , 'temp' )
478- print ("✓ High-pass temporal filter succeeded" )
507+ print ("High-pass temporal filter succeeded" )
479508
480509 # Low-pass filter
481510 result = self .run_mars_files (['01336.atmos_daily.nc' , '-lpt' , '0.75' , '-incl' , 'temp' ])
482511 self .assertEqual (result .returncode , 0 , "Low-pass temporal filter command failed" )
483512 low_pass_file = self .check_file_exists ('01336.atmos_daily_lpt.nc' )
484513 self .verify_netcdf_has_variable (low_pass_file , 'temp' )
485- print ("✓ Low-pass temporal filter succeeded" )
514+ print ("Low-pass temporal filter succeeded" )
486515
487516 # Band-pass filter
488517 result = self .run_mars_files (['01336.atmos_daily.nc' , '-bpt' , '0.75' , '10' , '-add_trend' , '-incl' , 'temp' ])
489518 self .assertEqual (result .returncode , 0 , "Band-pass temporal filter with trend command failed" )
490519 band_pass_file = self .check_file_exists ('01336.atmos_daily_bpt_trended.nc' )
491520 self .verify_netcdf_has_variable (band_pass_file , 'temp' )
492- print ("✓ Band-pass temporal filter succeeded" )
521+ print ("Band-pass temporal filter succeeded" )
493522
494523 def test_spatial_filters (self ):
495524 """Test all spatial filtering operations"""
@@ -502,21 +531,21 @@ def test_spatial_filters(self):
502531 self .assertEqual (result .returncode , 0 , "High-pass spatial filter command failed" )
503532 high_pass_file = self .check_file_exists ('01336.atmos_daily_hps.nc' )
504533 self .verify_netcdf_has_variable (high_pass_file , 'temp' )
505- print ("✓ High-pass spatial filter succeeded" )
534+ print ("High-pass spatial filter succeeded" )
506535
507536 # Low-pass filter
508537 result = self .run_mars_files (['01336.atmos_daily.nc' , '-lps' , '20' , '-incl' , 'temp' ])
509538 self .assertEqual (result .returncode , 0 , "Low-pass spatial filter command failed" )
510539 low_pass_file = self .check_file_exists ('01336.atmos_daily_lps.nc' )
511540 self .verify_netcdf_has_variable (low_pass_file , 'temp' )
512- print ("✓ Low-pass spatial filter succeeded" )
541+ print ("Low-pass spatial filter succeeded" )
513542
514543 # Band-pass filter
515544 result = self .run_mars_files (['01336.atmos_daily.nc' , '-bps' , '10' , '20' , '-incl' , 'temp' ])
516545 self .assertEqual (result .returncode , 0 , "Band-pass spatial filter command failed" )
517546 band_pass_file = self .check_file_exists ('01336.atmos_daily_bps.nc' )
518547 self .verify_netcdf_has_variable (band_pass_file , 'temp' )
519- print ("✓ Band-pass spatial filter succeeded" )
548+ print ("Band-pass spatial filter succeeded" )
520549
521550 def test_tide_decomposition (self ):
522551 """Test tidal decomposition on diurn file"""
@@ -538,7 +567,7 @@ def test_tide_decomposition(self):
538567 self .verify_netcdf_has_variable (output_file , 'temp_amp' )
539568 self .verify_netcdf_has_variable (output_file , 'temp_phas' )
540569
541- print ("✓ Tide decomposition succeeded" )
570+ print ("Tide decomposition succeeded" )
542571
543572 def test_tide_decomposition_with_normalize (self ):
544573 """Test tidal decomposition with normalization"""
@@ -575,7 +604,7 @@ def test_tide_decomposition_with_normalize(self):
575604 finally :
576605 nc .close ()
577606
578- print ("✓ Tide decomposition with normalization succeeded" )
607+ print ("Tide decomposition with normalization succeeded" )
579608
580609 def test_tide_decomposition_with_reconstruct (self ):
581610 """Test tidal decomposition with reconstruction"""
@@ -595,7 +624,7 @@ def test_tide_decomposition_with_reconstruct(self):
595624 self .verify_netcdf_has_variable (output_file , 'ps_N1' )
596625 self .verify_netcdf_has_variable (output_file , 'ps_N2' )
597626
598- print ("✓ Tide decomposition with reconstruction succeeded" )
627+ print ("Tide decomposition with reconstruction succeeded" )
599628
600629 # Verify that only included variables (plus dimensions) are in the output
601630 nc = Dataset (output_file , 'r' )
@@ -628,7 +657,7 @@ def test_tide_decomposition_with_reconstruct(self):
628657 finally :
629658 nc .close ()
630659
631- print ("✓ Include argument succeeded" )
660+ print ("Include argument succeeded" )
632661
633662 def test_regrid (self ):
634663 """Test regridding operation"""
@@ -662,7 +691,7 @@ def test_regrid(self):
662691 nc_target .close ()
663692 nc_output .close ()
664693
665- print ("✓ Regrid operation succeeded" )
694+ print ("Regrid operation succeeded" )
666695
667696 def test_zonal_average (self ):
668697 """Test zonal averaging operation"""
@@ -681,7 +710,7 @@ def test_zonal_average(self):
681710 finally :
682711 nc .close ()
683712
684- print ("✓ Zonal average operation succeeded" )
713+ print ("Zonal average operation succeeded" )
685714
686715 def test_custom_extension (self ):
687716 """Test using custom extension"""
@@ -693,7 +722,7 @@ def test_custom_extension(self):
693722 # Check that output file was created with custom extension
694723 output_file = self .check_file_exists ('01336.atmos_average_zavg_custom.nc' )
695724
696- print ("✓ Custom extension operation succeeded" )
725+ print ("Custom extension operation succeeded" )
697726
698727 def test_zzz_output_cleanup_stats (self ):
699728 """
@@ -720,7 +749,7 @@ def test_zzz_output_cleanup_stats(self):
720749 print ("==================================\n " )
721750
722751 # Test passes if we reach this point
723- print ("✓ Selective cleanup system is working" )
752+ print ("Selective cleanup system is working" )
724753
725754 # This isn't really a test, but we'll assert True to make it pass
726755 self .assertTrue (True )
0 commit comments