-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2d2b34d
commit 992072e
Showing
32 changed files
with
2,939 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: Publish to PyPI | ||
name: Build | ||
|
||
on: | ||
push: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: Test, Lint, and Format | ||
name: Unit Tests | ||
|
||
on: | ||
push: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import unittest | ||
from unittest.mock import patch | ||
from carbontracker.components.apple_silicon.powermetrics import AppleSiliconCPU, AppleSiliconGPU, PowerMetricsUnified | ||
|
||
|
||
class TestAppleSiliconCPU(unittest.TestCase): | ||
def setUp(self): | ||
self.cpu_handler = AppleSiliconCPU(pids=[], devices_by_pid={}) | ||
self.cpu_handler.init() | ||
|
||
def test_shutdown(self): | ||
self.cpu_handler.shutdown() | ||
|
||
@patch('platform.system', return_value="Darwin") | ||
def test_available_darwin(self, mock_platform): | ||
self.assertTrue(self.cpu_handler.available()) | ||
|
||
@patch('platform.system', return_value="AlienOS") | ||
def test_available_not_darwin(self, mock_platform): | ||
self.assertFalse(self.cpu_handler.available()) | ||
|
||
def test_devices(self): | ||
self.assertEqual(self.cpu_handler.devices(), ["CPU"]) | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="CPU Power: 1000 mW") | ||
def test_power_usage_with_match(self, mock_get_output): | ||
self.assertEqual(self.cpu_handler.power_usage(), 1.0) | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="No CPU Power data") | ||
def test_power_usage_no_match(self, mock_get_output): | ||
self.assertEqual(self.cpu_handler.power_usage(), 0.0) | ||
|
||
|
||
class TestAppleSiliconGPU(unittest.TestCase): | ||
def setUp(self): | ||
self.gpu_handler = AppleSiliconGPU(pids=[], devices_by_pid={}) | ||
self.gpu_handler.init() | ||
|
||
@patch('platform.system', return_value="Darwin") | ||
def test_available_darwin(self, mock_platform): | ||
self.assertTrue(self.gpu_handler.available()) | ||
|
||
@patch('platform.system', return_value="Windows") | ||
def test_available_not_darwin(self, mock_platform): | ||
self.assertFalse(self.gpu_handler.available()) | ||
|
||
def test_devices(self): | ||
self.assertEqual(self.gpu_handler.devices(), ["GPU", "ANE"]) | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="GPU Power: 500 mW\nANE Power: 300 mW") | ||
def test_power_usage_with_match(self, mock_get_output): | ||
self.assertAlmostEqual(self.gpu_handler.power_usage(), 0.8, places=2) | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="No GPU Power data") | ||
def test_power_usage_no_match(self, mock_get_output): | ||
self.assertEqual(self.gpu_handler.power_usage(), 0.0) | ||
|
||
|
||
class TestPowerMetricsUnified(unittest.TestCase): | ||
@patch('subprocess.check_output', return_value="Sample Output") | ||
@patch('time.time', side_effect=[100, 101, 102, 200, 202]) | ||
def test_get_output_with_actual_call(self, mock_time, mock_check_output): | ||
# First call - should call subprocess | ||
output1 = PowerMetricsUnified.get_output() | ||
|
||
# Second call - should use cached output | ||
output2 = PowerMetricsUnified.get_output() | ||
|
||
# Advance time to invalidate cache | ||
PowerMetricsUnified._last_updated -= 2 | ||
|
||
# Third call - should call subprocess again | ||
output3 = PowerMetricsUnified.get_output() | ||
|
||
self.assertEqual(mock_check_output.call_count, 2) | ||
self.assertEqual(output1, "Sample Output") | ||
self.assertEqual(output2, "Sample Output") | ||
self.assertEqual(output3, "Sample Output") | ||
|
||
|
||
class TestAppleSiliconGPUPowerUsage(unittest.TestCase): | ||
def setUp(self): | ||
self.gpu_handler = AppleSiliconGPU(pids=[], devices_by_pid={}) | ||
self.gpu_handler.init() | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="GPU Power: 500 mW\nANE Power: 300 mW") | ||
def test_power_usage_with_match(self, mock_get_output): | ||
self.assertAlmostEqual(self.gpu_handler.power_usage(), 0.8, places=2) | ||
|
||
@patch('carbontracker.components.apple_silicon.powermetrics.PowerMetricsUnified.get_output', | ||
return_value="No GPU Power data") | ||
def test_power_usage_no_match(self, mock_get_output): | ||
self.assertEqual(self.gpu_handler.power_usage(), 0.0) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import unittest | ||
from unittest.mock import patch, mock_open | ||
from carbontracker.components.cpu.intel import IntelCPU | ||
from carbontracker.components.component import Component | ||
from carbontracker import exceptions | ||
import re | ||
|
||
class TestIntelCPU(unittest.TestCase): | ||
@patch("os.path.exists") | ||
@patch("os.listdir") | ||
def test_available(self, mock_listdir, mock_exists): | ||
mock_exists.return_value = True | ||
mock_listdir.return_value = ["some_directory"] | ||
|
||
component = Component(name='cpu', pids=[], devices_by_pid={}) | ||
self.assertTrue(component.available()) | ||
|
||
@patch("os.path.exists") | ||
@patch("os.listdir") | ||
@patch("builtins.open", new_callable=mock_open, read_data="some_name") | ||
def test_devices(self, mock_file, mock_listdir, mock_exists): | ||
mock_exists.return_value = True | ||
mock_listdir.side_effect = [["intel-rapl:0", "intel-rapl:1"], ["name"], ["name"]] | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu.init() | ||
|
||
self.assertEqual(cpu.devices(), ["cpu:0", "cpu:1"]) | ||
|
||
@patch("os.path.exists") | ||
@patch("os.listdir") | ||
@patch.object(Component, "available", return_value=False) | ||
def test_available_false(self, mock_available, mock_listdir, mock_exists): | ||
mock_exists.return_value = False | ||
mock_listdir.return_value = [] | ||
|
||
cpu = Component(name='cpu', pids=[], devices_by_pid={}) | ||
self.assertFalse(cpu.available()) | ||
|
||
@patch("time.sleep") | ||
@patch("carbontracker.components.cpu.intel.IntelCPU._get_measurements") | ||
def test_power_usage_positive(self, mock_get_measurements, mock_sleep): | ||
mock_get_measurements.side_effect = [[10, 20], [20, 30]] | ||
mock_sleep.return_value = None | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
power_usages = cpu.power_usage() | ||
|
||
self.assertEqual(power_usages, [0.00001, 0.00001]) | ||
|
||
@patch("time.sleep") | ||
@patch("carbontracker.components.cpu.intel.IntelCPU._get_measurements") | ||
def test_power_usage_negative(self, mock_get_measurements, mock_sleep): | ||
mock_get_measurements.side_effect = [[30, 20], [20, 30]] | ||
mock_sleep.return_value = None | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu._devices = ["cpu:0", "cpu:1"] | ||
power_usages = cpu.power_usage() | ||
|
||
self.assertEqual(power_usages, [0.00, 0.00]) | ||
|
||
|
||
@patch("builtins.open", new_callable=mock_open, read_data="1000000") | ||
def test__read_energy(self, mock_file): | ||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
energy = cpu._read_energy("/some/path") | ||
self.assertEqual(energy, 1000000) | ||
|
||
@patch("os.path.exists") | ||
@patch("os.listdir") | ||
@patch("builtins.open", new_callable=mock_open) | ||
def test__get_measurements(self, mock_file, mock_listdir, mock_exists): | ||
mock_exists.return_value = True | ||
mock_listdir.return_value = ["intel-rapl:0", "intel-rapl:1"] | ||
mock_file.return_value.read.return_value = "1000000" | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu.init() | ||
|
||
measurements = cpu._get_measurements() | ||
self.assertEqual(measurements, [1000000, 1000000]) | ||
|
||
@patch("os.listdir") | ||
@patch("builtins.open", new_callable=mock_open, read_data="cpu") | ||
def test__convert_rapl_name(self, mock_file, mock_listdir): | ||
mock_listdir.return_value = ["intel-rapl:0", "intel-rapl:1"] | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu.init() | ||
|
||
self.assertEqual(cpu._convert_rapl_name("intel-rapl:0", re.compile("intel-rapl:.")), "cpu:0") | ||
|
||
@patch("os.path.exists") | ||
@patch("os.listdir") | ||
@patch("builtins.open", new_callable=mock_open, read_data="cpu") | ||
def test_init(self, mock_file, mock_listdir, mock_exists): | ||
mock_exists.return_value = True | ||
mock_listdir.return_value = ["intel-rapl:0", "intel-rapl:1"] | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu.init() | ||
|
||
self.assertEqual(cpu.devices(), ["cpu:0", "cpu:1"]) | ||
|
||
@patch("os.path.join") | ||
@patch("os.listdir") | ||
@patch("carbontracker.components.cpu.intel.IntelCPU._read_energy") | ||
def test__get_measurements_permission_error(self, mock_read_energy, mock_listdir, mock_path_join): | ||
mock_path_join.return_value = "/some/path" | ||
mock_read_energy.side_effect = PermissionError() | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu._rapl_devices = ["device1"] | ||
with self.assertRaises(exceptions.IntelRaplPermissionError): | ||
cpu._get_measurements() | ||
|
||
@patch("os.path.join") | ||
@patch("os.listdir") | ||
@patch("carbontracker.components.cpu.intel.IntelCPU._read_energy") | ||
def test__get_measurements_file_not_found(self, mock_read_energy, mock_listdir, mock_path_join): | ||
mock_path_join.return_value = "/some/path" | ||
mock_read_energy.side_effect = [FileNotFoundError(), 1000000, 1000000, 1000000] | ||
mock_listdir.return_value = ["intel-rapl:0", "intel-rapl:1"] | ||
|
||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
cpu._rapl_devices = ["intel-rapl:0", "intel-rapl:1"] | ||
cpu.parts_pattern = re.compile(r"intel-rapl:.") | ||
measurements = cpu._get_measurements() | ||
|
||
self.assertEqual(measurements, [2000000, 1000000]) | ||
|
||
|
||
def test_shutdown(self): | ||
cpu = IntelCPU(pids=[], devices_by_pid={}) | ||
# As the shutdown method is currently a pass, there's nothing to assert here. | ||
# But we still call it for the sake of completeness and future modifications. | ||
cpu.shutdown() | ||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Oops, something went wrong.