2727
2828import unittest
2929import os
30+ import random
3031import OpTestConfiguration
3132import OpTestLogger
3233
3334from common .OpTestSystem import OpSystemState
3435from common .OpTestUtil import OpTestUtil
36+ from testcases .OpTestKernelDump import OptestKernelDump
3537
3638log = OpTestLogger .optest_logger_glob .get_logger (__name__ )
3739
3840
39- class CrashToolInteractiveTest (unittest .TestCase ):
41+ class CrashTool (unittest .TestCase ):
4042 """
41- Detects and validates the latest crash dump (vmcore) under /var/crash/
42-
43- Identifies appropriate debug symbols (vmlinux) based on the OS (e.g., RHEL or SLES)
44-
45- Launches crash vmlinux vmcore in interactive mode on the console
46-
47- Executes a sequence of commands (log, bt, ps, etc.) in a single session
48-
49- Verifies command output and exits cleanly from the crash shell
43+ test class providing common setup and helper methods for crash dump testing
5044 """
5145
5246 def setUp (self ):
@@ -58,12 +52,38 @@ def setUp(self):
5852 self .distro = self .op_test_util .distro_name ()
5953 self .version = self .op_test_util .get_distro_version ().split ("." )[0 ]
6054 self .cv_SYSTEM .goto_state (OpSystemState .OS )
55+ self .c = self .cv_SYSTEM .console
56+ self .bmc_type = conf .args .bmc_type
57+ if self .bmc_type == "FSP_PHYP" or self .bmc_type == "EBMC_PHYP" :
58+ self .is_lpar = True
59+ self .cv_HMC = self .cv_SYSTEM .hmc
6160 try :
6261 self .crash_commands = conf .args .crash_commands
6362 except AttributeError :
6463 self .crash_commands = "log,bt,ps,runq,kmem -i,kmem -o,kmem -h,vm,sys,mod"
6564 self .commands = [cmd .strip () for cmd in self .crash_commands .split ("," )]
6665
66+ def get_random_crash_cpu (self ):
67+ """
68+ Pick a random valid CPU from available system CPUs
69+ """
70+ out = self .cv_HOST .host_run_command ("nproc" )
71+ if out :
72+ nproc = int (out [0 ].strip ())
73+ else :
74+ nproc = int (
75+ self .cv_HOST .host_run_command ("getconf _NPROCESSORS_ONLN" )[0 ].strip ()
76+ )
77+ if nproc <= 0 :
78+ raise Exception ("Invalid CPU count detected" )
79+ crash_cpu = random .randint (0 , nproc - 1 )
80+ log .info (
81+ "Selected random crash CPU %d from %d CPUs" ,
82+ crash_cpu , nproc
83+ )
84+
85+ return crash_cpu
86+
6787 def run_crash_command (self , cmd , timeout = 180 ):
6888 """
6989 1.Sends a command to the running crash session
@@ -114,17 +134,11 @@ def verify_packages(self):
114134 log .info (
115135 f"All required debug packages for { self .distro } are installed." )
116136
117- def runTest (self ):
137+ def crash_console (self ):
118138 """
119- 1.Gets kernel version (uname -r)
120- 2.Finds latest crash directory (e.g., /var/crash/2025-06-03-07-08/)
121- 3.Validates vmcore file type and size
122- 4.Detects OS type/version and constructs appropriate vmlinux path
123- 5.Launches crash interactively
124- 6.Runs commands (stored in self.commands)
125- 7.Exits crash cleanly
139+ Locates latest crash dump directory, validates vmcore file,
140+ determines correct vmlinux path and launches the crash tool.
126141 """
127- self .verify_packages ()
128142 kernel_version = self .cv_HOST .host_run_command ("uname -r" )[0 ].strip ()
129143 cmd = "ls -1td /var/crash/*/ | head -n 1 | xargs basename"
130144 crash_dir = self .cv_HOST .host_run_command (cmd )[0 ].strip ()
@@ -149,11 +163,31 @@ def runTest(self):
149163 self .console .sendline (crash_cmd )
150164 try :
151165 self .console .expect_exact ("crash>" , timeout = 300 )
166+ banner = self .console .before
167+ # Return crash startup banner
168+ return banner
152169 except Exception as e :
153170 if "crash>" not in self .console .before :
154171 raise Exception (
155172 "Crash tool failed to launch or kernel panic detected" ) from e
156173
174+ class CrashToolInteractiveTest (CrashTool ):
175+ """
176+ Interactive test case that verifies crash dump analysis by running a set of crash commands.
177+ """
178+
179+ def runTest (self ):
180+ """
181+ 1.Verifies required packages
182+ 2.Triggers a kernel crash
183+ 3.Launches crash tool and disables scroll paging.
184+ 4.Iterates through configured crash commands (e.g., log, bt, ps, kmem) and captures output.
185+ 5.Exits crash session cleanly.
186+ """
187+ self .verify_packages ()
188+ OptestKernelDump .kernel_crash (self )
189+ self .console = self .cv_SYSTEM .console .get_console ()
190+ self .crash_console ()
157191 # Set scroll off to avoid pager issues
158192 self .run_crash_command ("set scroll off" )
159193
@@ -165,3 +199,48 @@ def runTest(self):
165199 # Exit crash session
166200 self .console .sendline ("exit" )
167201 self .console .expect ("#" , timeout = 30 )
202+
203+ class CrashTaskset (CrashTool ):
204+ """
205+ Test case that validates crash dump collection when a crash is triggered on a specific CPU
206+ """
207+
208+ def runTest (self ):
209+ self .verify_packages ()
210+ # Pick random valid CPU
211+ self .crash_cpu = self .get_random_crash_cpu ()
212+ # Trigger crash with CPU affinity
213+ OptestKernelDump .kernel_crash (self , crash_cpu = self .crash_cpu )
214+ self .console = self .cv_SYSTEM .console .get_console ()
215+ # Launch crash and capture banner
216+ banner = self .crash_console ()
217+ found_cpu = False
218+ for line in banner .splitlines ():
219+ line = line .strip ()
220+ if line .startswith ("CPU:" ):
221+ found_cpu = True
222+ cpu = int (line .split ()[1 ])
223+
224+ if cpu != self .crash_cpu :
225+ self .fail (
226+ "Expected crash CPU %d, got %d"
227+ % (self .crash_cpu , cpu )
228+ )
229+ log .info (
230+ "Crash occurred on expected CPU %d" , cpu
231+ )
232+ break
233+
234+ if not found_cpu :
235+ self .fail ("CPU field not found in crash startup output" )
236+
237+ # Exit crash cleanly
238+ self .console .sendline ("exit" )
239+ self .console .expect ("#" , timeout = 30 )
240+
241+ def crash_suite ():
242+ s = unittest .TestSuite ()
243+ s .addTest (CrashToolInteractiveTest ())
244+ s .addTest (CrashTaskset ())
245+ return s
246+
0 commit comments