Skip to content

Commit 8e6e719

Browse files
committed
test(swap_config): add unit tests for BaseSwapConfig, GkeSwapConfig, EksSwapConfig and GKE wiring
1 parent a65db70 commit 8e6e719

3 files changed

Lines changed: 557 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright 2026 PerfKitBenchmarker Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Tests for perfkitbenchmarker.linux_benchmarks.swap_encryption_benchmark."""
15+
16+
import unittest
17+
from unittest import mock
18+
19+
from perfkitbenchmarker.linux_benchmarks import swap_encryption_benchmark
20+
from tests import pkb_common_test_case
21+
22+
23+
class GetConfigTest(pkb_common_test_case.PkbCommonTestCase):
24+
"""Tests that BENCHMARK_CONFIG is well-formed and loadable."""
25+
26+
def test_get_config_returns_dict(self):
27+
config = swap_encryption_benchmark.GetConfig({})
28+
self.assertIsInstance(config, dict)
29+
30+
def test_get_config_has_container_cluster(self):
31+
# configs.LoadConfig returns the inner benchmark dict directly (no benchmark
32+
# name wrapper), so top-level keys are 'container_cluster', 'description', etc.
33+
config = swap_encryption_benchmark.GetConfig({})
34+
self.assertIn('container_cluster', config)
35+
36+
def test_get_config_benchmark_nodepool_present(self):
37+
config = swap_encryption_benchmark.GetConfig({})
38+
nodepools = config['container_cluster']['nodepools']
39+
self.assertIn(
40+
swap_encryption_benchmark._BENCHMARK_NODEPOOL,
41+
nodepools,
42+
)
43+
44+
def test_get_config_swap_config_present_on_benchmark_nodepool(self):
45+
config = swap_encryption_benchmark.GetConfig({})
46+
nodepool = config['container_cluster']['nodepools'][
47+
swap_encryption_benchmark._BENCHMARK_NODEPOOL
48+
]
49+
self.assertIn('swap_config', nodepool)
50+
self.assertTrue(nodepool['swap_config'].get('enabled', False))
51+
52+
53+
class ParseCipherTest(pkb_common_test_case.PkbCommonTestCase):
54+
"""Tests for _parse_cipher() output parsing."""
55+
56+
def test_parse_cipher_standard_aes_xts(self):
57+
# Typical dmsetup status line: <name> <start>-<end> crypt <cipher> ...
58+
status = '0 67108864 crypt aes-xts-plain64 0 8:16 0 1 sector_size:4096'
59+
self.assertEqual(
60+
swap_encryption_benchmark._parse_cipher(status), 'aes-xts-plain64'
61+
)
62+
63+
def test_parse_cipher_returns_empty_when_no_crypt_token(self):
64+
status = '0 67108864 linear 8:16 0'
65+
self.assertEqual(swap_encryption_benchmark._parse_cipher(status), '')
66+
67+
def test_parse_cipher_returns_empty_on_empty_string(self):
68+
self.assertEqual(swap_encryption_benchmark._parse_cipher(''), '')
69+
70+
def test_parse_cipher_crypt_at_end_returns_empty(self):
71+
# 'crypt' present but no token after it.
72+
status = 'something crypt'
73+
self.assertEqual(swap_encryption_benchmark._parse_cipher(status), '')
74+
75+
def test_parse_cipher_not_encrypted_string(self):
76+
# Output from the benchmark when dm-crypt not active.
77+
status = 'not_encrypted'
78+
self.assertEqual(swap_encryption_benchmark._parse_cipher(status), '')
79+
80+
81+
class DetectSwapDeviceTest(pkb_common_test_case.PkbCommonTestCase):
82+
"""Tests for _detect_swap_device() with mocked PodExec."""
83+
84+
def _make_ds(self, pod_exec_output):
85+
ds = mock.Mock()
86+
ds.PodExec.return_value = (pod_exec_output, '')
87+
return ds
88+
89+
def test_detect_swap_device_returns_device_basename(self):
90+
# /proc/swaps first device column (after header skip via awk NR>1).
91+
ds = self._make_ds('/dev/dm-0\n')
92+
result = swap_encryption_benchmark._detect_swap_device(ds)
93+
self.assertEqual(result, 'dm-0')
94+
95+
def test_detect_swap_device_returns_first_device_when_multiple(self):
96+
ds = self._make_ds('/dev/dm-0\n/dev/dm-1\n')
97+
result = swap_encryption_benchmark._detect_swap_device(ds)
98+
self.assertEqual(result, 'dm-0')
99+
100+
def test_detect_swap_device_returns_empty_when_no_swap(self):
101+
ds = self._make_ds('')
102+
result = swap_encryption_benchmark._detect_swap_device(ds)
103+
self.assertEqual(result, '')
104+
105+
def test_detect_swap_device_returns_empty_on_pod_exec_exception(self):
106+
ds = mock.Mock()
107+
ds.PodExec.side_effect = Exception('pod not found')
108+
result = swap_encryption_benchmark._detect_swap_device(ds)
109+
self.assertEqual(result, '')
110+
111+
112+
class BuildMetadataTest(pkb_common_test_case.PkbCommonTestCase):
113+
"""Tests for _build_metadata() with mocked PodExec."""
114+
115+
def test_build_metadata_includes_swap_device(self):
116+
ds = mock.Mock()
117+
ds.PodExec.return_value = ('5.15.0-gke-1234\n', '')
118+
meta = swap_encryption_benchmark._build_metadata(ds, 'dm-0')
119+
self.assertEqual(meta['swap_device'], 'dm-0')
120+
121+
def test_build_metadata_swap_device_unknown_when_empty(self):
122+
ds = mock.Mock()
123+
ds.PodExec.return_value = ('5.15.0\n', '')
124+
meta = swap_encryption_benchmark._build_metadata(ds, '')
125+
self.assertEqual(meta['swap_device'], 'unknown')
126+
127+
def test_build_metadata_includes_kernel_version(self):
128+
ds = mock.Mock()
129+
ds.PodExec.return_value = ('5.15.0-gke-1234\n', '')
130+
meta = swap_encryption_benchmark._build_metadata(ds, 'dm-0')
131+
self.assertEqual(meta['kernel_version'], '5.15.0-gke-1234')
132+
133+
def test_build_metadata_kernel_version_absent_on_pod_exec_exception(self):
134+
ds = mock.Mock()
135+
ds.PodExec.side_effect = Exception('timeout')
136+
meta = swap_encryption_benchmark._build_metadata(ds, 'dm-0')
137+
self.assertNotIn('kernel_version', meta)
138+
139+
140+
if __name__ == '__main__':
141+
unittest.main()

tests/providers/gcp/google_kubernetes_engine_test.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from perfkitbenchmarker.resources.container_service import container
3535
from perfkitbenchmarker.resources.container_service import kubectl
3636
from perfkitbenchmarker.resources.container_service import kubernetes_commands
37+
from perfkitbenchmarker.resources.container_service import swap_config as swap_config_lib
3738
from tests import pkb_common_test_case
3839

3940
FLAGS = flgs.FLAGS
@@ -949,5 +950,160 @@ def testCreateWithPerNodepoolAutoscaling(self):
949950
self.assertIn('--max-nodes 10', nodepool_cmd)
950951

951952

953+
class GoogleKubernetesEngineSwapConfigTestCase(PatchedObjectsTestCase):
954+
"""Tests that _AddNodeParamsToCmd wires swap_config flags correctly."""
955+
956+
@staticmethod
957+
def _make_swap_spec(
958+
boot_disk_iops=160000,
959+
boot_disk_throughput=2400,
960+
lssd=False,
961+
lssd_count=0,
962+
):
963+
"""Build a ContainerClusterSpec with swap_config on the benchmark nodepool."""
964+
return container_spec.ContainerClusterSpec(
965+
'NAME',
966+
**{
967+
'cloud': 'GCP',
968+
'vm_spec': {
969+
'GCP': {
970+
'machine_type': 'e2-medium',
971+
'zone': 'us-central1-a',
972+
},
973+
},
974+
'nodepools': {
975+
'benchmark': {
976+
'vm_spec': {
977+
'GCP': {
978+
'machine_type': 'n4-highmem-32',
979+
'zone': 'us-central1-a',
980+
},
981+
},
982+
'swap_config': {
983+
'enabled': True,
984+
'swappiness': 100,
985+
'min_free_kbytes': 200,
986+
'watermark_scale_factor': 500,
987+
'lssd': lssd,
988+
'lssd_count': lssd_count,
989+
'boot_disk_iops': boot_disk_iops,
990+
'boot_disk_throughput': boot_disk_throughput,
991+
},
992+
},
993+
},
994+
},
995+
)
996+
997+
def setUp(self):
998+
super().setUp()
999+
# Avoid real tempfile creation in GKE command-generation tests.
1000+
# GkeSwapConfig implementation is tested separately in swap_config_test.py.
1001+
self.enter_context(
1002+
mock.patch.object(
1003+
swap_config_lib.GkeSwapConfig,
1004+
'WriteLinuxConfigYaml',
1005+
return_value='/tmp/fake_linux_config.yaml',
1006+
)
1007+
)
1008+
1009+
def test_swap_config_sets_system_config_from_file_flag(self):
1010+
spec = self._make_swap_spec()
1011+
with self.patch_critical_objects() as issue_command:
1012+
cluster = google_kubernetes_engine.GkeCluster(spec)
1013+
cluster._Create()
1014+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1015+
'node-pools create benchmark'
1016+
)
1017+
self.assertIsNotNone(nodepool_cmd)
1018+
self.assertIn('--system-config-from-file', nodepool_cmd)
1019+
self.assertIn('/tmp/fake_linux_config.yaml', nodepool_cmd)
1020+
1021+
def test_swap_config_sets_ubuntu_containerd_image_type(self):
1022+
spec = self._make_swap_spec()
1023+
with self.patch_critical_objects() as issue_command:
1024+
cluster = google_kubernetes_engine.GkeCluster(spec)
1025+
cluster._Create()
1026+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1027+
'node-pools create benchmark'
1028+
)
1029+
self.assertIn('UBUNTU_CONTAINERD', nodepool_cmd)
1030+
1031+
def test_swap_config_sets_no_enable_autorepair(self):
1032+
spec = self._make_swap_spec()
1033+
with self.patch_critical_objects() as issue_command:
1034+
cluster = google_kubernetes_engine.GkeCluster(spec)
1035+
cluster._Create()
1036+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1037+
'node-pools create benchmark'
1038+
)
1039+
self.assertIn('--no-enable-autorepair', nodepool_cmd)
1040+
1041+
def test_swap_config_with_boot_disk_iops_sets_provisioned_flags(self):
1042+
spec = self._make_swap_spec(boot_disk_iops=160000, boot_disk_throughput=2400)
1043+
with self.patch_critical_objects() as issue_command:
1044+
cluster = google_kubernetes_engine.GkeCluster(spec)
1045+
cluster._Create()
1046+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1047+
'node-pools create benchmark'
1048+
)
1049+
self.assertIn('--boot-disk-provisioned-iops', nodepool_cmd)
1050+
self.assertIn('--boot-disk-provisioned-throughput', nodepool_cmd)
1051+
1052+
def test_swap_config_lssd_omits_boot_disk_provisioned_flags(self):
1053+
# When lssd=True the swap device is local NVMe, not the boot disk.
1054+
spec = self._make_swap_spec(lssd=True, lssd_count=2, boot_disk_iops=0)
1055+
with self.patch_critical_objects() as issue_command:
1056+
cluster = google_kubernetes_engine.GkeCluster(spec)
1057+
cluster._Create()
1058+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1059+
'node-pools create benchmark'
1060+
)
1061+
self.assertNotIn('--boot-disk-provisioned-iops', nodepool_cmd)
1062+
self.assertNotIn('--boot-disk-provisioned-throughput', nodepool_cmd)
1063+
1064+
def test_nodepool_without_swap_config_omits_all_swap_flags(self):
1065+
spec = container_spec.ContainerClusterSpec(
1066+
'NAME',
1067+
**{
1068+
'cloud': 'GCP',
1069+
'vm_spec': {
1070+
'GCP': {
1071+
'machine_type': 'e2-medium',
1072+
'zone': 'us-central1-a',
1073+
},
1074+
},
1075+
'nodepools': {
1076+
'benchmark': {
1077+
'vm_spec': {
1078+
'GCP': {
1079+
'machine_type': 'n4-highmem-32',
1080+
'zone': 'us-central1-a',
1081+
},
1082+
},
1083+
},
1084+
},
1085+
},
1086+
)
1087+
with self.patch_critical_objects() as issue_command:
1088+
cluster = google_kubernetes_engine.GkeCluster(spec)
1089+
cluster._Create()
1090+
nodepool_cmd = issue_command.GetCommandWithSubstring(
1091+
'node-pools create benchmark'
1092+
)
1093+
self.assertNotIn('--system-config-from-file', nodepool_cmd)
1094+
self.assertNotIn('UBUNTU_CONTAINERD', nodepool_cmd)
1095+
self.assertNotIn('--no-enable-autorepair', nodepool_cmd)
1096+
1097+
def test_cleanup_yaml_called_after_nodepool_create(self):
1098+
spec = self._make_swap_spec()
1099+
with mock.patch.object(
1100+
swap_config_lib.GkeSwapConfig, 'CleanupYaml'
1101+
) as mock_cleanup:
1102+
with self.patch_critical_objects():
1103+
cluster = google_kubernetes_engine.GkeCluster(spec)
1104+
cluster._Create()
1105+
mock_cleanup.assert_called_once()
1106+
1107+
9521108
if __name__ == '__main__':
9531109
unittest.main()

0 commit comments

Comments
 (0)