@@ -656,3 +656,98 @@ def test_get_detected_hardware(
656656 self .assertIn ("gpu_count" , hardware_info )
657657 self .assertIn ("gpu_model" , hardware_info )
658658 self .assertIn ("gpu_ids" , hardware_info )
659+
660+ @mock .patch ("codecarbon.emissions_tracker.EmissionsTracker._get_geo_metadata" )
661+ @mock .patch ("codecarbon.emissions_tracker.EmissionsTracker._get_cloud_metadata" )
662+ @mock .patch ("codecarbon.core.electricitymaps_api.requests.get" )
663+ @mock .patch ("codecarbon.emissions_tracker.ResourceTracker" )
664+ @mock .patch (
665+ "codecarbon.emissions_tracker.BaseEmissionsTracker.get_detected_hardware"
666+ )
667+ @mock .patch ("codecarbon.emissions_tracker.PeriodicScheduler" )
668+ def test_cumulative_emissions_with_varying_intensity (
669+ self ,
670+ mock_scheduler ,
671+ mock_get_hw ,
672+ mock_resource_tracker ,
673+ mock_get ,
674+ mock_cloud ,
675+ mock_geo ,
676+ mock_cli_setup ,
677+ mock_log_values ,
678+ mocked_get_cloud_metadata_class ,
679+ mocked_get_gpu_details ,
680+ mocked_is_gpu_details_available ,
681+ ):
682+ # Setup mocks
683+ mock_geo .return_value = mock .MagicMock (
684+ latitude = 1.0 ,
685+ longitude = 1.0 ,
686+ country_iso_code = "USA" ,
687+ country_2letter_iso_code = "US" ,
688+ )
689+ mock_cloud .return_value = mock .MagicMock (
690+ is_on_private_infra = True , provider = "azure" , region = "eastus"
691+ )
692+ mock_get_hw .return_value = {
693+ "ram_total_size" : 16.0 ,
694+ "cpu_count" : 8 ,
695+ "cpu_physical_count" : 4 ,
696+ "cpu_model" : "Mock CPU" ,
697+ "gpu_count" : 0 ,
698+ "gpu_model" : "None" ,
699+ "gpu_ids" : None ,
700+ }
701+
702+ # Mock Electricity Maps API responses with different intensities
703+ # 1st call: 100 g/kWh, 2nd call: 200 g/kWh, 3rd call: 300 g/kWh
704+ responses = [
705+ mock .MagicMock (status_code = 200 , json = lambda : {"carbonIntensity" : 100 }),
706+ mock .MagicMock (status_code = 200 , json = lambda : {"carbonIntensity" : 200 }),
707+ mock .MagicMock (status_code = 200 , json = lambda : {"carbonIntensity" : 300 }),
708+ ]
709+ mock_get .side_effect = responses
710+
711+ tracker = EmissionsTracker (
712+ electricitymaps_api_token = "test-token" ,
713+ save_to_file = False ,
714+ measure_power_secs = 1 ,
715+ allow_multiple_runs = True ,
716+ )
717+
718+ # Manually inject a mock hardware component
719+ mock_cpu = mock .MagicMock ()
720+ from codecarbon .external .hardware import CPU
721+
722+ mock_cpu .__class__ = CPU
723+ # Mock measure_power_and_energy: return 1kWh delta each time
724+ mock_cpu .measure_power_and_energy .return_value = (
725+ Power .from_watts (100 ),
726+ Energy .from_energy (kWh = 1.0 ),
727+ )
728+ tracker ._hardware = [mock_cpu ]
729+
730+ # Start tracking
731+ tracker .start ()
732+
733+ tracker ._measure_power_and_energy ()
734+ # total_energy = 1.0, intensity = 100 => emissions = 0.1 kg
735+ data1 = tracker ._prepare_emissions_data ()
736+ self .assertAlmostEqual (data1 .emissions , 0.1 )
737+
738+ # Step 2
739+ tracker ._measure_power_and_energy ()
740+ # total_energy = 2.0, delta_energy = 1.0, intensity = 200 => delta_emissions = 0.2 kg
741+ # total_emissions = 0.3 kg
742+ data2 = tracker ._prepare_emissions_data ()
743+ self .assertAlmostEqual (data2 .emissions , 0.3 )
744+
745+ # Step 3
746+ tracker ._measure_power_and_energy ()
747+ # total_energy = 3.0, delta_energy = 1.0, intensity = 300 => delta_emissions = 0.3 kg
748+ # total_emissions = 0.6 kg
749+ data3 = tracker ._prepare_emissions_data ()
750+ self .assertAlmostEqual (data3 .emissions , 0.6 )
751+
752+ # Verification: If it wasn't cumulative, it would be 3.0 kWh * 300 g/kWh = 0.9 kg
753+ self .assertLess (data3 .emissions , 0.8 )
0 commit comments