diff options
Diffstat (limited to 'testing/mozbase/mozpower/tests')
11 files changed, 999 insertions, 0 deletions
diff --git a/testing/mozbase/mozpower/tests/conftest.py b/testing/mozbase/mozpower/tests/conftest.py new file mode 100644 index 0000000000..9d81b0229b --- /dev/null +++ b/testing/mozbase/mozpower/tests/conftest.py @@ -0,0 +1,103 @@ +import os +import tempfile +import time +from unittest import mock + +import pytest +from mozpower import MozPower +from mozpower.intel_power_gadget import IntelPowerGadget, IPGResultsHandler +from mozpower.macintelpower import MacIntelPower +from mozpower.powerbase import PowerBase + + +def os_side_effect(*args, **kwargs): + """Used as a side effect to os.path.exists when + checking if the Intel Power Gadget executable exists. + """ + return True + + +def subprocess_side_effect(*args, **kwargs): + """Used as a side effect when running the Intel Power + Gadget tool. + """ + time.sleep(1) + + +@pytest.fixture(scope="function") +def powermeasurer(): + """Returns a testing subclass of the PowerBase class + for testing. + """ + + class PowerMeasurer(PowerBase): + pass + + return PowerMeasurer() + + +@pytest.fixture(scope="function") +def ipg_obj(): + """Returns an IntelPowerGadget object with the test + output file path. + """ + return IntelPowerGadget( + "ipg-path", + output_file_path=os.path.abspath(os.path.dirname(__file__)) + + "/files/raptor-tp6-amazon-firefox_powerlog", + ) + + +@pytest.fixture(scope="function") +def ipg_rh_obj(): + """Returns an IPGResultsHandler object set up with the + test files and cleans up the directory after the tests + are complete. + """ + base_path = os.path.abspath(os.path.dirname(__file__)) + "/files/" + tmpdir = tempfile.mkdtemp() + + # Return the results handler for the test + yield IPGResultsHandler( + [ + base_path + "raptor-tp6-amazon-firefox_powerlog_1_.txt", + base_path + "raptor-tp6-amazon-firefox_powerlog_2_.txt", + base_path + "raptor-tp6-amazon-firefox_powerlog_3_.txt", + ], + tmpdir, + ) + + +@pytest.fixture(scope="function") +def macintelpower_obj(): + """Returns a MacIntelPower object with subprocess.check_output + and os.path.exists calls patched with side effects. + """ + with mock.patch("subprocess.check_output") as subprocess_mock: + with mock.patch("os.path.exists") as os_mock: + subprocess_mock.side_effect = subprocess_side_effect + os_mock.side_effect = os_side_effect + + yield MacIntelPower(ipg_measure_duration=2) + + +@pytest.fixture(scope="function") +def mozpower_obj(): + """Returns a MozPower object with subprocess.check_output + and os.path.exists calls patched with side effects. + """ + with mock.patch.object( + MozPower, "_get_os", return_value="Darwin" + ) as _, mock.patch.object( + MozPower, "_get_processor_info", return_value="GenuineIntel" + ) as _, mock.patch.object( + MacIntelPower, "get_ipg_path", return_value="/" + ) as _, mock.patch( + "subprocess.check_output" + ) as subprocess_mock, mock.patch( + "os.path.exists" + ) as os_mock: + subprocess_mock.side_effect = subprocess_side_effect + os_mock.side_effect = os_side_effect + + yield MozPower(ipg_measure_duration=2) diff --git a/testing/mozbase/mozpower/tests/files/emptyfile.txt b/testing/mozbase/mozpower/tests/files/emptyfile.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/mozbase/mozpower/tests/files/emptyfile.txt diff --git a/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_1_.txt b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_1_.txt new file mode 100644 index 0000000000..d5cc3f91a5 --- /dev/null +++ b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_1_.txt @@ -0,0 +1,30 @@ +"System Time","RDTSC","Elapsed Time (sec)","CPU Utilization(%)","CPU Frequency_0(MHz)","Processor Power_0(Watt)","Cumulative Processor Energy_0(Joules)","Cumulative Processor Energy_0(mWh)","IA Power_0(Watt)","Cumulative IA Energy_0(Joules)","Cumulative IA Energy_0(mWh)","Package Temperature_0(C)","Package Hot_0","DRAM Power_0(Watt)","Cumulative DRAM Energy_0(Joules)","Cumulative DRAM Energy_0(mWh)","GT Power_0(Watt)","Cumulative GT Energy_0(Joules)","Cumulative GT Energy_0(mWh)","Package Power Limit_0(Watt)","GT Frequency(MHz)" +"12:11:05:769","61075218263548"," 2.002"," 14.000"," 3400"," 23.427"," 23.460"," 6.517"," 21.019"," 21.048"," 5.847"," 75","0"," 0.877"," 0.878"," 0.244"," 0.020"," 0.020"," 0.006"," 45.000"," 0" +"12:11:06:774","61077822279584"," 3.007"," 20.000"," 3000"," 25.014"," 48.590"," 13.497"," 22.386"," 43.538"," 12.094"," 74","0"," 1.194"," 2.078"," 0.577"," 0.029"," 0.049"," 0.014"," 45.000"," 0" +"12:11:07:778","61080424421708"," 4.011"," 8.000"," 1300"," 9.512"," 58.140"," 16.150"," 6.904"," 50.469"," 14.019"," 65","0"," 0.836"," 2.917"," 0.810"," 0.007"," 0.057"," 0.016"," 45.000"," 0" +"12:11:08:781","61083023535972"," 5.013"," 1.000"," 1300"," 1.786"," 59.931"," 16.647"," 0.687"," 51.158"," 14.210"," 63","0"," 0.585"," 3.504"," 0.973"," 0.000"," 0.057"," 0.016"," 45.000"," 0" +"12:11:09:784","61085623402302"," 6.016"," 4.000"," 4000"," 5.249"," 65.195"," 18.110"," 3.743"," 54.912"," 15.253"," 75","0"," 0.660"," 4.166"," 1.157"," 0.003"," 0.059"," 0.017"," 45.000"," 0" +"12:11:10:787","61088224087008"," 7.020"," 20.000"," 3000"," 35.118"," 100.432"," 27.898"," 31.647"," 86.666"," 24.074"," 72","0"," 1.048"," 5.218"," 1.449"," 0.016"," 0.076"," 0.021"," 45.000"," 0" +"12:11:11:791","61090825821126"," 8.024"," 13.000"," 3000"," 25.436"," 125.965"," 34.990"," 22.228"," 108.979"," 30.272"," 71","0"," 0.868"," 6.089"," 1.691"," 0.004"," 0.080"," 0.022"," 45.000"," 0" +"12:11:12:795","61093429020836"," 9.028"," 5.000"," 1300"," 5.266"," 131.253"," 36.459"," 3.270"," 112.263"," 31.184"," 64","0"," 0.711"," 6.802"," 1.890"," 0.000"," 0.080"," 0.022"," 45.000"," 0" +"12:11:13:796","61096024160928"," 10.029"," 5.000"," 4000"," 11.070"," 142.338"," 39.538"," 9.613"," 121.888"," 33.858"," 78","0"," 0.650"," 7.453"," 2.070"," 0.000"," 0.080"," 0.022"," 45.000"," 0" +"12:11:13:796","61096024279264"," 10.029"," 57.000"," 4000"," 0.000"," 142.338"," 39.538"," 0.000"," 121.888"," 33.858"," 78","0"," 0.000"," 7.453"," 2.070"," 0.000"," 0.080"," 0.022"," 45.000"," 0" +
+"Total Elapsed Time (sec) = 10.029232" +"Measured RDTSC Frequency (GHz) = 2.592" +
+"Cumulative Processor Energy_0 (Joules) = 142.337524" +"Cumulative Processor Energy_0 (mWh) = 39.538201" +"Average Processor Power_0 (Watt) = 14.192265" +
+"Cumulative IA Energy_0 (Joules) = 121.888000" +"Cumulative IA Energy_0 (mWh) = 33.857778" +"Average IA Power_0 (Watt) = 12.153273" +
+"Cumulative DRAM Energy_0 (Joules) = 7.453308" +"Cumulative DRAM Energy_0 (mWh) = 2.070363" +"Average DRAM Power_0 (Watt) = 0.743158" +
+"Cumulative GT Energy_0 (Joules) = 0.079834" +"Cumulative GT Energy_0 (mWh) = 0.022176" +"Average GT Power_0 (Watt) = 0.007960" diff --git a/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_2_.txt b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_2_.txt new file mode 100644 index 0000000000..9157ad1fad --- /dev/null +++ b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_2_.txt @@ -0,0 +1,30 @@ +"System Time","RDTSC","Elapsed Time (sec)","CPU Utilization(%)","CPU Frequency_0(MHz)","Processor Power_0(Watt)","Cumulative Processor Energy_0(Joules)","Cumulative Processor Energy_0(mWh)","IA Power_0(Watt)","Cumulative IA Energy_0(Joules)","Cumulative IA Energy_0(mWh)","Package Temperature_0(C)","Package Hot_0","DRAM Power_0(Watt)","Cumulative DRAM Energy_0(Joules)","Cumulative DRAM Energy_0(mWh)","GT Power_0(Watt)","Cumulative GT Energy_0(Joules)","Cumulative GT Energy_0(mWh)","Package Power Limit_0(Watt)","GT Frequency(MHz)" +"12:11:15:821","61101270635674"," 2.007"," 9.000"," 4200"," 10.341"," 10.382"," 2.884"," 7.710"," 7.740"," 2.150"," 80","0"," 0.828"," 0.831"," 0.231"," 0.000"," 0.000"," 0.000"," 45.000"," 0" +"12:11:16:823","61103869919264"," 3.010"," 4.000"," 1300"," 9.353"," 19.762"," 5.489"," 8.079"," 15.842"," 4.400"," 64","0"," 0.597"," 1.430"," 0.397"," 0.000"," 0.000"," 0.000"," 45.000"," 0" +"12:11:17:826","61106469262970"," 4.013"," 19.000"," 2500"," 21.127"," 40.950"," 11.375"," 17.753"," 33.645"," 9.346"," 69","0"," 1.091"," 2.524"," 0.701"," 0.011"," 0.011"," 0.003"," 45.000"," 0" +"12:11:18:827","61109063770516"," 5.014"," 11.000"," 2000"," 11.620"," 52.581"," 14.606"," 8.333"," 41.986"," 11.663"," 64","0"," 0.961"," 3.486"," 0.968"," 0.000"," 0.011"," 0.003"," 45.000"," 0" +"12:11:19:831","61111666134444"," 6.018"," 3.000"," 1300"," 2.722"," 55.314"," 15.365"," 1.497"," 43.489"," 12.080"," 64","0"," 0.620"," 4.108"," 1.141"," 0.000"," 0.011"," 0.003"," 45.000"," 0" +"12:11:20:835","61114266619236"," 7.021"," 14.000"," 3400"," 17.108"," 72.478"," 20.133"," 14.538"," 58.075"," 16.132"," 79","0"," 0.918"," 5.029"," 1.397"," 0.004"," 0.016"," 0.004"," 45.000"," 0" +"12:11:21:835","61116859007218"," 8.021"," 15.000"," 3000"," 16.651"," 89.132"," 24.759"," 13.055"," 71.132"," 19.759"," 70","0"," 1.103"," 6.132"," 1.703"," 0.005"," 0.020"," 0.006"," 45.000"," 0" +"12:11:22:836","61119453982614"," 9.023"," 4.000"," 2000"," 3.334"," 92.470"," 25.686"," 1.974"," 73.109"," 20.308"," 64","0"," 0.654"," 6.787"," 1.885"," 0.000"," 0.020"," 0.006"," 45.000"," 0" +"12:11:23:840","61122057636318"," 10.027"," 12.000"," 3400"," 13.925"," 106.458"," 29.572"," 11.603"," 84.764"," 23.546"," 79","0"," 0.856"," 7.647"," 2.124"," 0.006"," 0.027"," 0.007"," 45.000"," 0" +"12:11:23:840","61122057733984"," 10.027"," 66.000"," 3400"," 0.000"," 106.458"," 29.572"," 0.000"," 84.764"," 23.546"," 79","0"," 0.000"," 7.647"," 2.124"," 0.000"," 0.027"," 0.007"," 45.000"," 0" +
+"Total Elapsed Time (sec) = 10.027134" +"Measured RDTSC Frequency (GHz) = 2.592" +
+"Cumulative Processor Energy_0 (Joules) = 106.457581" +"Cumulative Processor Energy_0 (mWh) = 29.571550" +"Average Processor Power_0 (Watt) = 10.616950" +
+"Cumulative IA Energy_0 (Joules) = 84.764404" +"Cumulative IA Energy_0 (mWh) = 23.545668" +"Average IA Power_0 (Watt) = 8.453503" +
+"Cumulative DRAM Energy_0 (Joules) = 7.647095" +"Cumulative DRAM Energy_0 (mWh) = 2.124193" +"Average DRAM Power_0 (Watt) = 0.762640" +
+"Cumulative GT Energy_0 (Joules) = 0.026550" +"Cumulative GT Energy_0 (mWh) = 0.007375" +"Average GT Power_0 (Watt) = 0.002648" diff --git a/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_3_.txt b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_3_.txt new file mode 100644 index 0000000000..a2779d93a1 --- /dev/null +++ b/testing/mozbase/mozpower/tests/files/raptor-tp6-amazon-firefox_powerlog_3_.txt @@ -0,0 +1,30 @@ +"System Time","RDTSC","Elapsed Time (sec)","CPU Utilization(%)","CPU Frequency_0(MHz)","Processor Power_0(Watt)","Cumulative Processor Energy_0(Joules)","Cumulative Processor Energy_0(mWh)","IA Power_0(Watt)","Cumulative IA Energy_0(Joules)","Cumulative IA Energy_0(mWh)","Package Temperature_0(C)","Package Hot_0","DRAM Power_0(Watt)","Cumulative DRAM Energy_0(Joules)","Cumulative DRAM Energy_0(mWh)","GT Power_0(Watt)","Cumulative GT Energy_0(Joules)","Cumulative GT Energy_0(mWh)","Package Power Limit_0(Watt)","GT Frequency(MHz)" +"12:11:25:867","61127310813944"," 2.006"," 6.000"," 2000"," 4.862"," 4.879"," 1.355"," 3.087"," 3.097"," 0.860"," 65","0"," 0.774"," 0.776"," 0.216"," 0.021"," 0.022"," 0.006"," 45.000"," 0" +"12:11:26:867","61129903677124"," 3.006"," 2.000"," 1300"," 2.883"," 7.763"," 2.156"," 1.253"," 4.350"," 1.208"," 63","0"," 0.644"," 1.421"," 0.395"," 0.018"," 0.039"," 0.011"," 45.000"," 0" +"12:11:27:873","61132509307964"," 4.011"," 2.000"," 1300"," 1.788"," 9.561"," 2.656"," 0.699"," 5.054"," 1.404"," 63","0"," 0.538"," 1.961"," 0.545"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:28:877","61135111021262"," 5.015"," 0.000"," 1300"," 1.177"," 10.743"," 2.984"," 0.321"," 5.375"," 1.493"," 63","0"," 0.535"," 2.499"," 0.694"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:29:881","61137714179976"," 6.020"," 0.000"," 1300"," 1.064"," 11.811"," 3.281"," 0.251"," 5.627"," 1.563"," 61","0"," 0.520"," 3.021"," 0.839"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:30:881","61140307250904"," 7.020"," 0.000"," 1300"," 1.053"," 12.864"," 3.573"," 0.256"," 5.884"," 1.634"," 61","0"," 0.528"," 3.550"," 0.986"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:31:885","61142907976768"," 8.023"," 0.000"," 1300"," 1.069"," 13.937"," 3.871"," 0.274"," 6.159"," 1.711"," 61","0"," 0.524"," 4.075"," 1.132"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:32:886","61145503940512"," 9.025"," 0.000"," 1300"," 1.008"," 14.947"," 4.152"," 0.228"," 6.387"," 1.774"," 60","0"," 0.522"," 4.598"," 1.277"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:33:891","61148109378824"," 10.030"," 0.000"," 1300"," 1.007"," 15.959"," 4.433"," 0.230"," 6.618"," 1.838"," 61","0"," 0.522"," 5.123"," 1.423"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +"12:11:33:892","61148109766160"," 10.030"," 27.000"," 1300"," 0.000"," 15.959"," 4.433"," 0.000"," 6.618"," 1.838"," 61","0"," 2.448"," 5.123"," 1.423"," 0.000"," 0.039"," 0.011"," 45.000"," 0" +
+"Total Elapsed Time (sec) = 10.030310" +"Measured RDTSC Frequency (GHz) = 2.592" +
+"Cumulative Processor Energy_0 (Joules) = 15.958862" +"Cumulative Processor Energy_0 (mWh) = 4.433017" +"Average Processor Power_0 (Watt) = 1.591064" +
+"Cumulative IA Energy_0 (Joules) = 6.618042" +"Cumulative IA Energy_0 (mWh) = 1.838345" +"Average IA Power_0 (Watt) = 0.659804" +
+"Cumulative DRAM Energy_0 (Joules) = 5.122925" +"Cumulative DRAM Energy_0 (mWh) = 1.423035" +"Average DRAM Power_0 (Watt) = 0.510744" +
+"Cumulative GT Energy_0 (Joules) = 0.039490" +"Cumulative GT Energy_0 (mWh) = 0.010969" +"Average GT Power_0 (Watt) = 0.003937" diff --git a/testing/mozbase/mozpower/tests/files/valueerrorfile.txt b/testing/mozbase/mozpower/tests/files/valueerrorfile.txt new file mode 100644 index 0000000000..8f2d3485eb --- /dev/null +++ b/testing/mozbase/mozpower/tests/files/valueerrorfile.txt @@ -0,0 +1,30 @@ +"System Time","RDTSC","Elapsed Time (sec)","CPU Utilization(%)","CPU Frequency_0(MHz)","Processor Power_0(Watt)","Cumulative Processor Energy_0(Joules)","Cumulative Processor Energy_0(mWh)","IA Power_0(Watt)","Cumulative IA Energy_0(Joules)","Cumulative IA Energy_0(mWh)","Package Temperature_0(C)","Package Hot_0","DRAM Power_0(Watt)","Cumulative DRAM Energy_0(Joules)","Cumulative DRAM Energy_0(mWh)","GT Power_0(Watt)","Cumulative GT Energy_0(Joules)","Cumulative GT Energy_0(mWh)","Package Power Limit_0(Watt)","GT Frequency(MHz)" +"12:11:05:769","61075218263548"," 2.002"," 14.000"," 3400"," 23.427"," 23.460"," 6.517"," 21.019"," 21.048"," 5.847"," 75","0"," 0.877"," 0.878"," 0.244"," 0.020"," 0.020"," 0.006"," 45.000"," 0" +"12:11:06:774","61077822279584"," 3.007"," 20.000"," 3000"," 25.014"," 48.590"," 13.497"," 22.386"," 43.538"," 12.094"," 74","0"," 1.194"," 2.078"," 0.577"," 0.029"," 0.049"," 0.014"," 45.000"," 0" +"12:11:07:778","61080424421708"," 4.011"," 8.000"," 1300"," 9.512"," 58.140"," 16.150"," 6.904"," 50.469"," 14.019"," 65","0"," 0.836"," 2.917"," 0.810"," 0.007"," 0.057"," 0.016"," 45.000"," 0" +"12:11:08:781","61083023535972"," 5.013"," 1.000"," 1300"," 1.786"," 59.931"," 16.647"," 0.687"," 51.158"," 14.210"," 63","0"," 0.585"," 3.504"," 0.973"," 0.000"," 0.057"," 0.016"," 45.000"," 0" +"12:11:09:784","61085623402302"," none"," 4.000"," 4000"," 5.249"," 65.195"," 18.110"," 3.743"," 54.912"," 15.253"," 75","0"," 0.660"," 4.166"," 1.157"," 0.003"," 0.059"," 0.017"," 45.000"," 0" +"12:11:10:787","61088224087008"," 7.020"," 20.000"," 3000"," 35.118"," 100.432"," 27.898"," 31.647"," 86.666"," 24.074"," 72","0"," 1.048"," 5.218"," 1.449"," 0.016"," 0.076"," 0.021"," 45.000"," 0" +"12:11:11:791","61090825821126"," 8.024"," 13.000"," 3000"," 25.436"," 125.965"," 34.990"," 22.228"," 108.979"," 30.272"," 71","0"," 0.868"," 6.089"," 1.691"," 0.004"," 0.080"," 0.022"," 45.000"," 0" +"12:11:12:795","61093429020836"," 9.028"," 5.000"," 1300"," 5.266"," 131.253"," 36.459"," 3.270"," 112.263"," 31.184"," 64","0"," 0.711"," 6.802"," 1.890"," 0.000"," 0.080"," 0.022"," 45.000"," 0" +"12:11:13:796","61096024160928"," 10.029"," 5.000"," 4000"," 11.070"," 142.338"," 39.538"," 9.613"," 121.888"," 33.858"," 78","0"," 0.650"," 7.453"," 2.070"," 0.000"," 0.080"," 0.022"," 45.000"," 0" +"12:11:13:796","61096024279264"," 10.029"," 57.000"," 4000"," 0.000"," 142.338"," 39.538"," 0.000"," 121.888"," 33.858"," 78","0"," 0.000"," 7.453"," 2.070"," 0.000"," 0.080"," 0.022"," 45.000"," 0" + +"Total Elapsed Time (sec) = 10.029232" +"Measured RDTSC Frequency (GHz) = 2.592" + +"Cumulative Processor Energy_0 (Joules) = 142.337524" +"Cumulative Processor Energy_0 (mWh) = 39.538201" +"Average Processor Power_0 (Watt) = 14.192265" + +"Cumulative IA Energy_0 (Joules) = 121.888000" +"Cumulative IA Energy_0 (mWh) = 33.857778" +"Average IA Power_0 (Watt) = 12.153273" + +"Cumulative DRAM Energy_0 (Joules) = 7.453308" +"Cumulative DRAM Energy_0 (mWh) = 2.070363" +"Average DRAM Power_0 (Watt) = 0.743158" + +"Cumulative GT Energy_0 (Joules) = 0.079834" +"Cumulative GT Energy_0 (mWh) = 0.022176" +"Average GT Power_0 (Watt) = 0.007960" diff --git a/testing/mozbase/mozpower/tests/manifest.ini b/testing/mozbase/mozpower/tests/manifest.ini new file mode 100644 index 0000000000..2e825e2fa5 --- /dev/null +++ b/testing/mozbase/mozpower/tests/manifest.ini @@ -0,0 +1,6 @@ +[DEFAULT] +subsuite = mozbase +[test_powerbase.py] +[test_intelpowergadget.py] +[test_macintelpower.py] +[test_mozpower.py] diff --git a/testing/mozbase/mozpower/tests/test_intelpowergadget.py b/testing/mozbase/mozpower/tests/test_intelpowergadget.py new file mode 100644 index 0000000000..9be01cb720 --- /dev/null +++ b/testing/mozbase/mozpower/tests/test_intelpowergadget.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python + +import datetime +import os +import time +from unittest import mock + +import mozunit +import pytest +import six +from mozpower.intel_power_gadget import ( + IPGEmptyFileError, + IPGMissingOutputFileError, + IPGTimeoutError, + IPGUnknownValueTypeError, +) + + +def thread_is_alive(thread): + if six.PY2: + return thread.isAlive() + return thread.is_alive() + + +def test_ipg_pathsplitting(ipg_obj): + """Tests that the output file path and prefix was properly split. + This test assumes that it is in the same directory as the conftest.py file. + """ + assert ( + ipg_obj.output_dir_path == os.path.abspath(os.path.dirname(__file__)) + "/files" + ) + assert ipg_obj.output_file_prefix == "raptor-tp6-amazon-firefox_powerlog" + + +def test_ipg_get_output_file_path(ipg_obj): + """Tests that the output file path is constantly changing + based on the file_counter value. + """ + test_path = "/test_path/" + test_ext = ".txt" + ipg_obj._file_counter = 1 + ipg_obj._output_dir_path = test_path + ipg_obj._output_file_ext = test_ext + + for i in range(1, 6): + fpath = ipg_obj._get_output_file_path() + + assert fpath.startswith(test_path) + assert fpath.endswith(test_ext) + assert str(i) in fpath + + +def test_ipg_start_and_stop(ipg_obj): + """Tests that the IPG thread can start and stop properly.""" + + def subprocess_side_effect(*args, **kwargs): + time.sleep(1) + + with mock.patch("subprocess.check_output") as m: + m.side_effect = subprocess_side_effect + + # Start recording IPG measurements + ipg_obj.start_ipg() + assert not ipg_obj._stop + + # Wait a bit for thread to start, then check it + timeout = 10 + start = time.time() + while time.time() - start < timeout and not ipg_obj._running: + time.sleep(1) + + assert ipg_obj._running + assert thread_is_alive(ipg_obj._thread) + + # Stop recording IPG measurements + ipg_obj.stop_ipg(wait_interval=1, timeout=30) + assert ipg_obj._stop + assert not ipg_obj._running + + +def test_ipg_stopping_timeout(ipg_obj): + """Tests that an IPGTimeoutError is raised when + the thread is still "running" and the wait in _wait_for_ipg + has exceeded the timeout value. + """ + with pytest.raises(IPGTimeoutError): + ipg_obj._running = True + ipg_obj._wait_for_ipg(wait_interval=1, timeout=2) + + +def test_ipg_rh_combine_cumulatives(ipg_rh_obj): + """Tests that cumulatives are correctly combined in + the _combine_cumulative_rows function. + """ + cumulatives_to_combine = [ + [0, 1, 2, 3, 4, 5], + [0, 1, 2, 3, 4, 5], + [0, 1, 2, 3, 4, 5], + [0, 1, 2, 3, 4, 5], + ] + + combined_cumulatives = ipg_rh_obj._combine_cumulative_rows(cumulatives_to_combine) + + # Check that accumulation worked, final value must be the maximum + assert combined_cumulatives[-1] == max(combined_cumulatives) + + # Check that the cumulative values are monotonically increasing + for count, val in enumerate(combined_cumulatives[:-1]): + assert combined_cumulatives[count + 1] - val >= 0 + + +def test_ipg_rh_clean_file(ipg_rh_obj): + """Tests that IPGResultsHandler correctly cleans the data + from one file. + """ + file = ipg_rh_obj._output_files[0] + linecount = 0 + with open(file, "r") as f: + for line in f: + linecount += 1 + + results, summary, clean_file = ipg_rh_obj._clean_ipg_file(file) + + # Check that each measure from the csv header + # is in the results dict and that the clean file output + # exists. + for measure in results: + assert measure in ipg_rh_obj._csv_header + assert os.path.exists(clean_file) + + clean_rows = [] + with open(clean_file, "r") as f: + for line in f: + if line.strip(): + clean_rows.append(line) + + # Make sure that the results and summary entries + # have the expected lengths. + for measure in results: + # Add 6 for new lines that were removed + assert len(results[measure]) + len(summary) + 6 == linecount + # Subtract 1 for the csv header + assert len(results[measure]) == len(clean_rows) - 1 + + +def test_ipg_rh_clean_ipg_data_no_files(ipg_rh_obj): + """Tests that IPGResultsHandler correctly handles the case + when no output files exist. + """ + ipg_rh_obj._output_files = [] + clean_data = ipg_rh_obj.clean_ipg_data() + assert clean_data is None + + +def test_ipg_rh_clean_ipg_data(ipg_rh_obj): + """Tests that IPGResultsHandler correctly handles cleaning + all known files and that the results and the merged output + are correct. + """ + clean_data = ipg_rh_obj.clean_ipg_data() + clean_files = ipg_rh_obj.cleaned_files + merged_output_path = ipg_rh_obj.merged_output_path + + # Check that the expected output exists + assert clean_data is not None + assert len(clean_files) == len(ipg_rh_obj._output_files) + assert os.path.exists(merged_output_path) + + # Check that the merged file length and results length + # is correct, and that no lines were lost and no extra lines + # were added. + expected_merged_line_count = 0 + for file in clean_files: + with open(file, "r") as f: + for count, line in enumerate(f): + if count == 0: + continue + if line.strip(): + expected_merged_line_count += 1 + + merged_line_count = 0 + with open(merged_output_path, "r") as f: + for count, line in enumerate(f): + if count == 0: + continue + if line.strip(): + merged_line_count += 1 + + assert merged_line_count == expected_merged_line_count + for measure in clean_data: + assert len(clean_data[measure]) == merged_line_count + + # Check that the clean data rows are ordered in increasing time + times_in_seconds = [] + for sys_time in clean_data["System Time"]: + split_sys_time = sys_time.split(":") + hour_min_sec = ":".join(split_sys_time[:-1]) + millis = float(split_sys_time[-1]) / 1000 + + timestruct = time.strptime(hour_min_sec, "%H:%M:%S") + times_in_seconds.append( + datetime.timedelta( + hours=timestruct.tm_hour, + minutes=timestruct.tm_min, + seconds=timestruct.tm_sec, + ).total_seconds() + + millis + ) + + for count, val in enumerate(times_in_seconds[:-1]): + assert times_in_seconds[count + 1] - val >= 0 + + +def test_ipg_rh_format_to_perfherder_with_no_results(ipg_rh_obj): + """Tests that formatting the data to a perfherder-like format + fails when clean_ipg_data was not called beforehand. + """ + formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder( + 1000, ipg_rh_obj._output_file_prefix + ) + assert formatted_data is None + + +def test_ipg_rh_format_to_perfherder_without_cutoff(ipg_rh_obj): + """Tests that formatting the data to a perfherder-like format + works as expected. + """ + ipg_rh_obj.clean_ipg_data() + formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder( + 1000, ipg_rh_obj._output_file_prefix + ) + + # Check that the expected entries exist + assert len(formatted_data.keys()) == 5 + assert "utilization" in formatted_data and "power-usage" in formatted_data + + assert ( + formatted_data["power-usage"]["test"] + == ipg_rh_obj._output_file_prefix + "-cumulative" + ) + assert ( + formatted_data["utilization"]["test"] + == ipg_rh_obj._output_file_prefix + "-utilization" + ) + assert ( + formatted_data["frequency-gpu"]["test"] + == ipg_rh_obj._output_file_prefix + "-frequency-gpu" + ) + assert ( + formatted_data["frequency-cpu"]["test"] + == ipg_rh_obj._output_file_prefix + "-frequency-cpu" + ) + assert ( + formatted_data["power-watts"]["test"] + == ipg_rh_obj._output_file_prefix + "-watts" + ) + + for measure in formatted_data: + # Make sure that the data exists + assert len(formatted_data[measure]["values"]) >= 1 + + for valkey in formatted_data[measure]["values"]: + # Make sure the names were simplified + assert "(" not in valkey + assert ")" not in valkey + + # Check that gpu utilization doesn't exist but cpu does + utilization_vals = formatted_data["utilization"]["values"] + assert "cpu" in utilization_vals + assert "gpu" not in utilization_vals + + expected_fields = ["processor-cores", "processor-package", "gpu", "dram"] + consumption_vals = formatted_data["power-usage"]["values"] + + consumption_vals_measures = list(consumption_vals.keys()) + + # This assertion ensures that the consumption values contain the expected + # fields and nothing more. + assert not list(set(consumption_vals_measures) - set(expected_fields)) + + +def test_ipg_rh_format_to_perfherder_with_cutoff(ipg_rh_obj): + """Tests that formatting the data to a perfherder-like format + works as expected. + """ + ipg_rh_obj.clean_ipg_data() + formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder( + 2.5, ipg_rh_obj._output_file_prefix + ) + + # Check that the formatted data was cutoff at the correct point, + # expecting that only the first row of merged will exist. + utilization_vals = formatted_data["utilization"]["values"] + assert utilization_vals["cpu"] == 14 + + # Expected vals are ordered in this way: [processor, cores, dram, gpu] + expected_vals = [6.517, 5.847, 0.244, 0.006] + consumption_vals = [ + formatted_data["power-usage"]["values"][measure] + for measure in formatted_data["power-usage"]["values"] + ] + assert not list(set(expected_vals) - set(consumption_vals)) + + +def test_ipg_rh_missingoutputfile(ipg_rh_obj): + """Tests that the IPGMissingOutputFileError is raised + when a bad file path is passed to _clean_ipg_file. + """ + bad_files = ["non-existent-file"] + with pytest.raises(IPGMissingOutputFileError): + ipg_rh_obj._clean_ipg_file(bad_files[0]) + + ipg_rh_obj._output_files = bad_files + with pytest.raises(IPGMissingOutputFileError): + ipg_rh_obj.clean_ipg_data() + + +def test_ipg_rh_emptyfile(ipg_rh_obj): + """Tests that the empty file error is raised when + a file exists, but does not contain any results in + it. + """ + base_path = os.path.abspath(os.path.dirname(__file__)) + "/files/" + bad_files = [base_path + "emptyfile.txt"] + with pytest.raises(IPGEmptyFileError): + ipg_rh_obj._clean_ipg_file(bad_files[0]) + + ipg_rh_obj._output_files = bad_files + with pytest.raises(IPGEmptyFileError): + ipg_rh_obj.clean_ipg_data() + + +def test_ipg_rh_valuetypeerrorfile(ipg_rh_obj): + """Tests that the IPGUnknownValueTypeError is raised + when a bad entry is encountered in a file that is cleaned. + """ + base_path = os.path.abspath(os.path.dirname(__file__)) + "/files/" + bad_files = [base_path + "valueerrorfile.txt"] + with pytest.raises(IPGUnknownValueTypeError): + ipg_rh_obj._clean_ipg_file(bad_files[0]) + + ipg_rh_obj._output_files = bad_files + with pytest.raises(IPGUnknownValueTypeError): + ipg_rh_obj.clean_ipg_data() + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozpower/tests/test_macintelpower.py b/testing/mozbase/mozpower/tests/test_macintelpower.py new file mode 100644 index 0000000000..2332c94c3e --- /dev/null +++ b/testing/mozbase/mozpower/tests/test_macintelpower.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import time +from unittest import mock + +import mozunit + + +def test_macintelpower_init(macintelpower_obj): + """Tests that the MacIntelPower object is correctly initialized.""" + assert macintelpower_obj.ipg_path + assert macintelpower_obj.ipg + assert macintelpower_obj._os == "darwin" + assert macintelpower_obj._cpu == "intel" + + +def test_macintelpower_measuring(macintelpower_obj): + """Tests that measurement initialization and finalization works + for the MacIntelPower object. + """ + assert not macintelpower_obj.start_time + assert not macintelpower_obj.ipg._running + assert not macintelpower_obj.ipg._output_files + macintelpower_obj.initialize_power_measurements() + + # Check that initialization set start_time, and started the + # IPG measurer thread. + + # Wait a bit for thread to start, then check it + timeout = 10 + start = time.time() + while time.time() - start < timeout and not macintelpower_obj.ipg._running: + time.sleep(1) + + assert macintelpower_obj.start_time + assert macintelpower_obj.ipg._running + + test_data = {"power-usage": "data"} + + def formatter_side_effect(*args, **kwargs): + return test_data + + with mock.patch( + "mozpower.intel_power_gadget.IPGResultsHandler.clean_ipg_data" + ) as _: + with mock.patch( + "mozpower.intel_power_gadget.IPGResultsHandler." + "format_ipg_data_to_partial_perfherder" + ) as formatter: + formatter.side_effect = formatter_side_effect + + macintelpower_obj.finalize_power_measurements(wait_interval=2, timeout=30) + + # Check that finalization set the end_time, stopped the IPG measurement + # thread, added atleast one output file name, and initialized + # an IPGResultsHandler object + assert macintelpower_obj.end_time + assert not macintelpower_obj.ipg._running + assert macintelpower_obj.ipg._output_files + assert macintelpower_obj.ipg_results_handler + + # Check that the IPGResultHandler's methods were + # called + macintelpower_obj.ipg_results_handler.clean_ipg_data.assert_called() + macintelpower_obj.ipg_results_handler.format_ipg_data_to_partial_perfherder.assert_called_once_with( # NOQA: E501 + macintelpower_obj.end_time - macintelpower_obj.start_time, + "power-testing", + ) + + # Make sure we can get the expected perfherder data + # after formatting + assert macintelpower_obj.get_perfherder_data() == test_data + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozpower/tests/test_mozpower.py b/testing/mozbase/mozpower/tests/test_mozpower.py new file mode 100644 index 0000000000..fd513e836b --- /dev/null +++ b/testing/mozbase/mozpower/tests/test_mozpower.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python + +import subprocess +import sys +from unittest import mock + +import mozunit +import pytest +from mozpower import MozPower +from mozpower.mozpower import MissingProcessorInfoError, OsCpuComboMissingError + + +def test_mozpower_android_init_failure(): + """Tests that the MozPower object fails when the android + flag is set. Remove this test once android is implemented. + """ + with pytest.raises(NotImplementedError): + MozPower(android=True) + + +def test_mozpower_oscpu_combo_missing_error(): + """Tests that the error OsCpuComboMissingError is raised + when we can't find a OS, and CPU combination (and, therefore, cannot + find a power measurer). + """ + with mock.patch.object( + MozPower, "_get_os", return_value="Not-An-OS" + ) as _, mock.patch.object( + MozPower, "_get_processor_info", return_value="Not-A-Processor" + ) as _: + with pytest.raises(OsCpuComboMissingError): + MozPower() + + +def test_mozpower_processor_info_missing_error(): + """Tests that the error MissingProcessorInfoError is raised + when failures occur during processor information parsing. + """ + # The builtins module name differs between python 2 and 3 + builtins_name = "__builtin__" + if sys.version_info[0] == 3: + builtins_name = "builtins" + + def os_side_effect_true(*args, **kwargs): + """Used as a passing side effect for os.path.exists calls.""" + return True + + def os_side_effect_false(*args, **kwargs): + """Used as a failing side effect for os.path.exists calls.""" + return False + + def subprocess_side_effect_fail(*args, **kwargs): + """Used to mock a failure in subprocess.check_output calls.""" + raise subprocess.CalledProcessError(1, "Testing failure") + + # Test failures in macos processor information parsing + with mock.patch.object(MozPower, "_get_os", return_value="Darwin") as _: + + with mock.patch("os.path.exists") as os_mock: + os_mock.side_effect = os_side_effect_false + + # Check that we fail properly if the processor + # information file doesn't exist. + with pytest.raises(MissingProcessorInfoError): + MozPower() + + # Check that we fail properly when an error occurs + # in the subprocess call. + os_mock.side_effect = os_side_effect_true + with mock.patch("subprocess.check_output") as subprocess_mock: + subprocess_mock.side_effect = subprocess_side_effect_fail + with pytest.raises(MissingProcessorInfoError): + MozPower() + + # Test failures in linux processor information parsing + with mock.patch.object(MozPower, "_get_os", return_value="Linux") as _: + + with mock.patch("os.path.exists") as os_mock: + os_mock.side_effect = os_side_effect_false + + # Check that we fail properly if the processor + # information file doesn't exist. + with pytest.raises(MissingProcessorInfoError): + MozPower() + + # Check that we fail properly when the model cannot be found + # with by searching for 'model name'. + os_mock.side_effect = os_side_effect_true + with mock.patch( + "%s.open" % builtins_name, mock.mock_open(read_data="") + ) as _: + with pytest.raises(MissingProcessorInfoError): + MozPower() + + +def test_mozpower_oscpu_combo(mozpower_obj): + """Tests that the correct class is instantiated for a given + OS and CPU combination (MacIntelPower in this case). + """ + assert mozpower_obj.measurer.__class__.__name__ == "MacIntelPower" + assert ( + mozpower_obj.measurer._os == "darwin" and mozpower_obj.measurer._cpu == "intel" + ) + + +def test_mozpower_measuring(mozpower_obj): + """Tests that measurers are properly called with each method.""" + with mock.patch( + "mozpower.macintelpower.MacIntelPower.initialize_power_measurements" + ) as _, mock.patch( + "mozpower.macintelpower.MacIntelPower.finalize_power_measurements" + ) as _, mock.patch( + "mozpower.macintelpower.MacIntelPower.get_perfherder_data" + ) as _: + mozpower_obj.initialize_power_measurements() + mozpower_obj.measurer.initialize_power_measurements.assert_called() + + mozpower_obj.finalize_power_measurements() + mozpower_obj.measurer.finalize_power_measurements.assert_called() + + mozpower_obj.get_perfherder_data() + mozpower_obj.measurer.get_perfherder_data.assert_called() + + +def test_mozpower_measuring_with_no_measurer(mozpower_obj): + """Tests that no errors occur when the measurer is None, and the + initialize, finalize, and get_perfherder_data functions are called. + """ + with mock.patch( + "mozpower.macintelpower.MacIntelPower.initialize_power_measurements" + ) as _, mock.patch( + "mozpower.macintelpower.MacIntelPower.finalize_power_measurements" + ) as _, mock.patch( + "mozpower.macintelpower.MacIntelPower.get_perfherder_data" + ) as _: + measurer = mozpower_obj.measurer + mozpower_obj.measurer = None + + mozpower_obj.initialize_power_measurements() + assert not measurer.initialize_power_measurements.called + + mozpower_obj.finalize_power_measurements() + assert not measurer.finalize_power_measurements.called + + mozpower_obj.get_perfherder_data() + assert not measurer.get_perfherder_data.called + + mozpower_obj.get_full_perfherder_data("mozpower") + assert not measurer.get_perfherder_data.called + + +def test_mozpower_get_full_perfherder_data(mozpower_obj): + """Tests that the full perfherder data blob is properly + produced given a partial perfherder data blob with correct + entries. + """ + partial_perfherder = { + "utilization": { + "type": "power", + "test": "mozpower", + "unit": "%", + "values": {"cpu": 50, "gpu": 0}, + }, + "power-usage": { + "type": "power", + "test": "mozpower", + "unit": "mWh", + "values": {"cpu": 2.0, "dram": 0.1, "gpu": 4.0}, + }, + "frequency-cpu": { + "type": "power", + "test": "mozpower", + "unit": "MHz", + "values": { + "cpu-favg": 2.0, + "cpu-fmax": 5.0, + "cpu-fmin": 0.0, + }, + }, + "frequency-gpu": { + "type": "power", + "test": "mozpower", + "unit": "MHz", + "values": {"gpu-favg": 3.0, "gpu-fmax": 6.0, "gpu-fmin": 0.0}, + }, + } + utilization_vals = [0, 50] + power_usage_vals = [2.0, 0.1, 4.0] + frequency_cpu_vals = [2.0, 5.0, 0.0] + frequency_gpu_vals = [3.0, 6.0, 0.0] + + with mock.patch("mozpower.macintelpower.MacIntelPower.get_perfherder_data") as gpd: + gpd.return_value = partial_perfherder + + full_perfherder = mozpower_obj.get_full_perfherder_data("mozpower") + assert full_perfherder["framework"]["name"] == "mozpower" + assert len(full_perfherder["suites"]) == 4 + + # Check that each of the two suites were created correctly. + suites = full_perfherder["suites"] + for suite in suites: + assert "subtests" in suite + + assert suite["type"] == "power" + assert suite["alertThreshold"] == 2.0 + assert suite["lowerIsBetter"] + + all_vals = [] + for subtest in suite["subtests"]: + assert "value" in subtest + + # Check that the subtest names were created correctly + if "utilization" in suite["name"]: + assert "utilization" in subtest["name"] + elif "power-usage" in suite["name"]: + assert "power-usage" in subtest["name"] + elif "frequency-cpu" in suite["name"]: + assert "frequency-cpu" in subtest["name"] + elif "frequency-gpu" in suite["name"]: + assert "frequency-gpu" in subtest["name"] + else: + assert False, "Unknown subtest name %s" % subtest["name"] + + all_vals.append(subtest["value"]) + + if "utilization" in suite["name"]: + assert len(all_vals) == 2 + assert suite["unit"] == "%" + assert suite["name"] == "mozpower-utilization" + assert not list(set(all_vals) - set(utilization_vals)) + assert suite["value"] == float(25) + elif "power-usage" in suite["name"]: + assert len(all_vals) == 3 + assert suite["unit"] == "mWh" + assert suite["name"] == "mozpower-power-usage" + assert not list(set(all_vals) - set(power_usage_vals)) + assert suite["value"] == float(6.1) + elif "frequency-cpu" in suite["name"]: + assert len(all_vals) == 3 + assert suite["unit"] == "MHz" + assert suite["name"] == "mozpower-frequency-cpu" + assert not list(set(all_vals) - set(frequency_cpu_vals)) + assert suite["value"] == float(2.0) + elif "frequency-gpu" in suite["name"]: + assert len(all_vals) == 3 + assert suite["unit"] == "MHz" + assert suite["name"] == "mozpower-frequency-gpu" + assert not list(set(all_vals) - set(frequency_gpu_vals)) + assert suite["value"] == float(3.0) + else: + assert False, "Unknown suite name %s" % suite["name"] + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozpower/tests/test_powerbase.py b/testing/mozbase/mozpower/tests/test_powerbase.py new file mode 100644 index 0000000000..46de941457 --- /dev/null +++ b/testing/mozbase/mozpower/tests/test_powerbase.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +from unittest import mock + +import mozunit +import pytest +from mozpower.powerbase import ( + IPGExecutableMissingError, + PlatformUnsupportedError, + PowerBase, +) + + +def test_powerbase_intialization(): + """Tests that the PowerBase class correctly raises + a NotImplementedError when attempting to instantiate + it directly. + """ + with pytest.raises(NotImplementedError): + PowerBase() + + +def test_powerbase_missing_methods(powermeasurer): + """Tests that trying to call PowerBase methods + without an implementation in the subclass raises + the NotImplementedError. + """ + with pytest.raises(NotImplementedError): + powermeasurer.initialize_power_measurements() + + with pytest.raises(NotImplementedError): + powermeasurer.finalize_power_measurements() + + with pytest.raises(NotImplementedError): + powermeasurer.get_perfherder_data() + + +def test_powerbase_get_ipg_path_mac(powermeasurer): + """Tests that getting the IPG path returns the expected results.""" + powermeasurer._os = "darwin" + powermeasurer._cpu = "not-intel" + + def os_side_effect(arg): + """Used to get around the os.path.exists check in + get_ipg_path which raises an IPGExecutableMissingError + otherwise. + """ + return True + + with mock.patch("os.path.exists", return_value=True) as m: + m.side_effect = os_side_effect + + # None is returned when a non-intel based machine is + # tested. + ipg_path = powermeasurer.get_ipg_path() + assert ipg_path is None + + # Check the path returned for mac intel-based machines. + powermeasurer._cpu = "intel" + ipg_path = powermeasurer.get_ipg_path() + assert ipg_path == "/Applications/Intel Power Gadget/PowerLog" + + +def test_powerbase_get_ipg_path_errors(powermeasurer): + """Tests that the appropriate error is output when calling + get_ipg_path with invalid/unsupported _os and _cpu entries. + """ + + powermeasurer._cpu = "intel" + powermeasurer._os = "Not-An-OS" + + def os_side_effect(arg): + """Used to force the error IPGExecutableMissingError to occur + (in case a machine running these tests is a mac, and has intel + power gadget installed). + """ + return False + + with pytest.raises(PlatformUnsupportedError): + powermeasurer.get_ipg_path() + + with mock.patch("os.path.exists", return_value=False) as m: + m.side_effect = os_side_effect + + powermeasurer._os = "darwin" + with pytest.raises(IPGExecutableMissingError): + powermeasurer.get_ipg_path() + + +if __name__ == "__main__": + mozunit.main() |