@@ -62,6 +62,41 @@ def read_metadata():
6262 thread1 .join ()
6363 thread2 .join ()
6464
65+ def test_stream_read_async (self ):
66+ """Test reading C2PA metadata from a file using async tasks"""
67+ async def read_metadata_async ():
68+ with open (self .testPath , "rb" ) as file :
69+ reader = Reader ("image/jpeg" , file )
70+ json_data = reader .json ()
71+ self .assertIn ("C.jpg" , json_data )
72+ return json_data
73+
74+ async def run_async_tests ():
75+ # Create multiple async tasks
76+ tasks = []
77+ num_tasks = 2
78+ for i in range (num_tasks ):
79+ task = asyncio .create_task (read_metadata_async ())
80+ tasks .append (task )
81+
82+ # Wait for all tasks to complete and collect results
83+ results = await asyncio .gather (* tasks , return_exceptions = True )
84+
85+ # Process results
86+ errors = []
87+ for i , result in enumerate (results ):
88+ if isinstance (result , Exception ):
89+ errors .append (f"Async task { i } failed with exception: { str (result )} " )
90+ elif result is None : # No result indicates an error
91+ errors .append (f"Async task { i } returned None" )
92+
93+ # If any errors occurred, fail the test with all error messages
94+ if errors :
95+ self .fail ("\n " .join (errors ))
96+
97+ # Run the async tests
98+ asyncio .run (run_async_tests ())
99+
65100 def test_stream_read_and_parse (self ):
66101 def read_and_parse ():
67102 with open (self .testPath , "rb" ) as file :
@@ -273,6 +308,33 @@ def setUp(self):
273308 ]
274309 }
275310
311+ # Define a V2 manifest as a dictionary
312+ self .manifestDefinitionV2_1 = {
313+ "claim_generator" : "python_test" ,
314+ "claim_generator_info" : [{
315+ "name" : "python_test" ,
316+ "version" : "0.0.1" ,
317+ }],
318+ # claim version 2 is the default
319+ # "claim_version": 2,
320+ "format" : "image/jpeg" ,
321+ "title" : "Python Test Image V2" ,
322+ "ingredients" : [],
323+ "assertions" : [
324+ {
325+ "label" : "c2pa.actions" ,
326+ "data" : {
327+ "actions" : [
328+ {
329+ "action" : "c2pa.created" ,
330+ "digitalSourceType" : "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
331+ }
332+ ]
333+ }
334+ }
335+ ]
336+ }
337+
276338 def test_sign_all_files (self ):
277339 """Test signing all files in both fixtures directories using a thread pool"""
278340 signing_dir = os .path .join (self .data_dir , "files-for-signing-tests" )
@@ -2024,5 +2086,256 @@ async def run_async_tests():
20242086 # Settings are thread-local, so we reset to the default "true" here
20252087 load_settings ('{"builder": { "thumbnail": {"enabled": true}}}' )
20262088
2089+ def test_streams_sign_with_thumbnail_resource (self ):
2090+ """Test Builder class operations with thumbnail resource using multiple threads."""
2091+ # Thread synchronization
2092+ thread_results = {}
2093+ completed_threads = 0
2094+ thread_lock = threading .Lock ()
2095+
2096+ def thread_work (thread_id ):
2097+ nonlocal completed_threads
2098+ try :
2099+ with open (self .testPath2 , "rb" ) as file :
2100+ builder = Builder (self .manifestDefinitionV2_1 )
2101+ output = io .BytesIO (bytearray ())
2102+
2103+ with open (self .testPath2 , "rb" ) as thumbnail_file :
2104+ builder .add_resource ("thumbnail" , thumbnail_file )
2105+
2106+ builder .sign (self .signer , "image/jpeg" , file , output )
2107+ output .seek (0 )
2108+ reader = Reader ("image/jpeg" , output )
2109+ json_data = reader .json ()
2110+
2111+ # Store results for verification
2112+ with thread_lock :
2113+ thread_results [thread_id ] = {
2114+ 'json_data' : json_data ,
2115+ 'thread_id' : thread_id
2116+ }
2117+
2118+ # Verify the JSON data contains expected content
2119+ self .assertIn ("Python Test" , json_data )
2120+ self .assertNotIn ("validation_status" , json_data )
2121+
2122+ output .close ()
2123+
2124+ except Exception as e :
2125+ with thread_lock :
2126+ thread_results [thread_id ] = {
2127+ 'error' : str (e ),
2128+ 'thread_id' : thread_id
2129+ }
2130+ finally :
2131+ with thread_lock :
2132+ completed_threads += 1
2133+
2134+ # Create and start multiple threads
2135+ threads = []
2136+ num_threads = 3
2137+ for i in range (1 , num_threads + 1 ):
2138+ thread = threading .Thread (target = thread_work , args = (i ,))
2139+ threads .append (thread )
2140+ thread .start ()
2141+
2142+ # Wait for all threads to complete
2143+ for thread in threads :
2144+ thread .join ()
2145+
2146+ # Verify all threads completed
2147+ self .assertEqual (completed_threads , num_threads , f"All { num_threads } threads should have completed" )
2148+ self .assertEqual (len (thread_results ), num_threads , f"Should have results from all { num_threads } threads" )
2149+
2150+ # Verify results for each thread
2151+ for thread_id in range (1 , num_threads + 1 ):
2152+ result = thread_results [thread_id ]
2153+
2154+ # Check if thread encountered an error
2155+ if 'error' in result :
2156+ self .fail (f"Thread { thread_id } failed with error: { result ['error' ]} " )
2157+
2158+ json_data = result ['json_data' ]
2159+
2160+ # Verify the JSON data contains expected content
2161+ self .assertIn ("Python Test" , json_data )
2162+ self .assertNotIn ("validation_status" , json_data )
2163+
2164+ def test_streams_sign_with_thumbnail_resource_async (self ):
2165+ """Test Builder class operations with thumbnail resource using async tasks."""
2166+ async def async_thread_work (task_id ):
2167+ try :
2168+ with open (self .testPath2 , "rb" ) as file :
2169+ builder = Builder (self .manifestDefinitionV2_1 )
2170+ output = io .BytesIO (bytearray ())
2171+
2172+ with open (self .testPath2 , "rb" ) as thumbnail_file :
2173+ builder .add_resource ("thumbnail" , thumbnail_file )
2174+
2175+ builder .sign (self .signer , "image/jpeg" , file , output )
2176+ output .seek (0 )
2177+ reader = Reader ("image/jpeg" , output )
2178+ json_data = reader .json ()
2179+
2180+ # Verify the JSON data contains expected content
2181+ self .assertIn ("Python Test" , json_data )
2182+ self .assertNotIn ("validation_status" , json_data )
2183+
2184+ output .close ()
2185+ return None # Success case
2186+
2187+ except Exception as e :
2188+ return f"Async task { task_id } error: { str (e )} "
2189+
2190+ async def run_async_tests ():
2191+ # Create multiple async tasks
2192+ tasks = []
2193+ num_tasks = 3
2194+ for i in range (1 , num_tasks + 1 ):
2195+ task = asyncio .create_task (async_thread_work (i ))
2196+ tasks .append (task )
2197+
2198+ # Wait for all tasks to complete and collect results
2199+ results = await asyncio .gather (* tasks , return_exceptions = True )
2200+
2201+ # Process results
2202+ errors = []
2203+ for i , result in enumerate (results , 1 ):
2204+ if isinstance (result , Exception ):
2205+ errors .append (f"Async task { i } failed with exception: { str (result )} " )
2206+ elif result : # Non-None result indicates an error
2207+ errors .append (result )
2208+
2209+ # If any errors occurred, fail the test with all error messages
2210+ if errors :
2211+ self .fail ("\n " .join (errors ))
2212+
2213+ # Run the async tests
2214+ asyncio .run (run_async_tests ())
2215+
2216+ def test_remote_sign_using_returned_bytes_V2 (self ):
2217+ """Test Builder class operations with remote signing using returned bytes and multiple threads."""
2218+ # Thread synchronization
2219+ thread_results = {}
2220+ completed_threads = 0
2221+ thread_lock = threading .Lock ()
2222+
2223+ def thread_work (thread_id ):
2224+ nonlocal completed_threads
2225+ try :
2226+ with open (self .testPath , "rb" ) as file :
2227+ builder = Builder (self .manifestDefinitionV2_1 )
2228+ builder .set_no_embed ()
2229+ with io .BytesIO () as output_buffer :
2230+ manifest_data = builder .sign (
2231+ self .signer , "image/jpeg" , file , output_buffer )
2232+ output_buffer .seek (0 )
2233+ read_buffer = io .BytesIO (output_buffer .getvalue ())
2234+
2235+ with Reader ("image/jpeg" , read_buffer , manifest_data ) as reader :
2236+ manifest_data = reader .json ()
2237+
2238+ # Store results for verification
2239+ with thread_lock :
2240+ thread_results [thread_id ] = {
2241+ 'manifest_data' : manifest_data ,
2242+ 'thread_id' : thread_id
2243+ }
2244+
2245+ # Verify the manifest data contains expected content
2246+ self .assertIn ("Python Test" , manifest_data )
2247+ self .assertNotIn ("validation_status" , manifest_data )
2248+
2249+ except Exception as e :
2250+ with thread_lock :
2251+ thread_results [thread_id ] = {
2252+ 'error' : str (e ),
2253+ 'thread_id' : thread_id
2254+ }
2255+ finally :
2256+ with thread_lock :
2257+ completed_threads += 1
2258+
2259+ # Create and start multiple threads
2260+ threads = []
2261+ num_threads = 3
2262+ for i in range (1 , num_threads + 1 ):
2263+ thread = threading .Thread (target = thread_work , args = (i ,))
2264+ threads .append (thread )
2265+ thread .start ()
2266+
2267+ # Wait for all threads to complete
2268+ for thread in threads :
2269+ thread .join ()
2270+
2271+ # Verify all threads completed
2272+ self .assertEqual (completed_threads , num_threads , f"All { num_threads } threads should have completed" )
2273+ self .assertEqual (len (thread_results ), num_threads , f"Should have results from all { num_threads } threads" )
2274+
2275+ # Verify results for each thread
2276+ for thread_id in range (1 , num_threads + 1 ):
2277+ result = thread_results [thread_id ]
2278+
2279+ # Check if thread encountered an error
2280+ if 'error' in result :
2281+ self .fail (f"Thread { thread_id } failed with error: { result ['error' ]} " )
2282+
2283+ manifest_data = result ['manifest_data' ]
2284+
2285+ # Verify the manifest data contains expected content
2286+ self .assertIn ("Python Test" , manifest_data )
2287+ self .assertNotIn ("validation_status" , manifest_data )
2288+
2289+ def test_remote_sign_using_returned_bytes_V2_async (self ):
2290+ """Test Builder class operations with remote signing using returned bytes and async tasks."""
2291+ async def async_thread_work (task_id ):
2292+ try :
2293+ with open (self .testPath , "rb" ) as file :
2294+ builder = Builder (self .manifestDefinitionV2_1 )
2295+ builder .set_no_embed ()
2296+ with io .BytesIO () as output_buffer :
2297+ manifest_data = builder .sign (
2298+ self .signer , "image/jpeg" , file , output_buffer )
2299+ output_buffer .seek (0 )
2300+ read_buffer = io .BytesIO (output_buffer .getvalue ())
2301+
2302+ with Reader ("image/jpeg" , read_buffer , manifest_data ) as reader :
2303+ manifest_data = reader .json ()
2304+
2305+ # Verify the manifest data contains expected content
2306+ self .assertIn ("Python Test" , manifest_data )
2307+ self .assertNotIn ("validation_status" , manifest_data )
2308+
2309+ return None # Success case
2310+
2311+ except Exception as e :
2312+ return f"Async task { task_id } error: { str (e )} "
2313+
2314+ async def run_async_tests ():
2315+ # Create multiple async tasks
2316+ tasks = []
2317+ num_tasks = 3
2318+ for i in range (1 , num_tasks + 1 ):
2319+ task = asyncio .create_task (async_thread_work (i ))
2320+ tasks .append (task )
2321+
2322+ # Wait for all tasks to complete and collect results
2323+ results = await asyncio .gather (* tasks , return_exceptions = True )
2324+
2325+ # Process results
2326+ errors = []
2327+ for i , result in enumerate (results , 1 ):
2328+ if isinstance (result , Exception ):
2329+ errors .append (f"Async task { i } failed with exception: { str (result )} " )
2330+ elif result : # Non-None result indicates an error
2331+ errors .append (result )
2332+
2333+ # If any errors occurred, fail the test with all error messages
2334+ if errors :
2335+ self .fail ("\n " .join (errors ))
2336+
2337+ # Run the async tests
2338+ asyncio .run (run_async_tests ())
2339+
20272340if __name__ == '__main__' :
20282341 unittest .main ()
0 commit comments