24
24
from importlib import import_module
25
25
from pathlib import Path
26
26
from typing import Iterable , List , Optional
27
+ from collections import namedtuple
27
28
28
29
import drgn
29
30
import sdb
30
31
from sdb .internal .cli import load_debug_info
31
32
from sdb .internal .repl import REPL
32
33
33
34
THIS_DIR = os .path .dirname (os .path .realpath (__file__ ))
34
- DATA_DIR = f"{ THIS_DIR } /data"
35
- DUMP_PATH = f"{ DATA_DIR } /dump.201912060006"
36
- MODS_PATH = f"{ DATA_DIR } /mods"
37
- VMLX_PATH = f"{ DATA_DIR } /vmlinux-5.0.0-36-generic"
38
- TEST_OUTPUT_DIR = f"{ DATA_DIR } /regression_output"
39
-
40
-
41
- def dump_exists () -> bool :
42
- """
43
- Used as the sole indicator of whether the integration
44
- tests will run.
45
- """
46
- return os .path .exists (DUMP_PATH ) and os .path .exists (
47
- MODS_PATH ) and os .path .exists (VMLX_PATH )
48
-
49
-
50
- def setup_target () -> Optional [drgn .Program ]:
51
- """
52
- Create a drgn.Program instance and setup the SDB
53
- context for all the integration tests. If there
54
- is no crash dump to attach to this is going to
55
- be an empty drgn.Program.
56
- """
57
- prog = drgn .Program ()
58
- if not dump_exists ():
59
- return prog
60
- prog .set_core_dump (DUMP_PATH )
61
- load_debug_info (prog , [VMLX_PATH , MODS_PATH ])
62
- return prog
63
-
64
-
65
- TEST_PROGRAM = setup_target ()
66
- TEST_REPL = REPL (TEST_PROGRAM , list (sdb .get_registered_commands ().keys ()))
67
-
68
-
69
- def repl_invoke (cmd : str ) -> int :
70
- """
71
- Accepts a command/pipeline in string form and evaluates
72
- it returning the exit code of the evaluation emulating
73
- the SDB repl.
74
- """
75
- assert TEST_PROGRAM
76
- return TEST_REPL .eval_cmd (cmd )
77
-
78
-
79
- def sdb_invoke (objs : Iterable [drgn .Object ], line : str ) -> Iterable [drgn .Object ]:
80
- """
81
- Dispatch to sdb.invoke, but also drain the generator it returns, so
82
- the tests can more easily access the returned objects.
83
-
84
- This method is preferred over repl_invoke() when the test wants to
85
- do fancier checks by mocking a few objects that are later passed
86
- down to the pipeline. Other scenarios include but are not limited
87
- to testing that specific exceptions are thrown or analyzing internal
88
- state of objects that is not part of the output in stdout.
89
- """
90
- assert TEST_PROGRAM
91
- return list (sdb .invoke (TEST_PROGRAM , objs , line ))
92
-
93
-
94
- def slurp_output_file (modname : str , cmd : str ) -> str :
95
- """
96
- Given a module name and a command, find the output file
97
- and return all of its contents as a string.
98
- """
99
- return Path (f"{ TEST_OUTPUT_DIR } /{ modname } /{ cmd } " ).read_text ()
100
-
101
-
102
- def generate_output_for_commands (cmds : List [str ], dirpath : str ) -> None :
103
- """
104
- Takes a list of SDB commands in string form, invokes them in the
105
- context of the current crash dump/sdb.REPL, and stores their output
106
- in the directory specified, each under a different file.
107
-
108
- Note: Keep in mind that if the directory specified exists then
109
- it will be removed together with all of its contents.
110
- """
111
- assert TEST_PROGRAM
112
- if os .path .exists (dirpath ):
113
- shutil .rmtree (dirpath )
114
- os .makedirs (dirpath )
115
- for cmd in cmds :
116
- with open (f"{ dirpath } /{ cmd } " , 'w' ) as f :
117
- with redirect_stdout (f ):
118
- repl_invoke (cmd )
119
-
120
-
121
- def generate_output_for_test_module (modname : str ) -> None :
122
- """
123
- Generates the regression output for all the commands of
124
- a test module given module name. The assumption for this
125
- to work automatically is that for the given modname "mod"
126
- there exist a test module under test.integration named
127
- test_mod_generic which has a list of commands in string
128
- form called CMD_TABLE.
129
- """
130
- test_mod = import_module (f"tests.integration.test_{ modname } _generic" )
131
- generate_output_for_commands (
132
- test_mod .CMD_TABLE , # type: ignore[attr-defined]
133
- f"{ TEST_OUTPUT_DIR } /{ modname } " )
134
- print (f"Generated regression test output for { modname } ..." )
35
+ PRIMARY = "primary"
36
+ ALTERNATE = "alternate"
135
37
136
38
39
+ @staticmethod
137
40
def get_all_generic_test_modules () -> List [str ]:
138
41
"""
139
42
Look at this current directory and capture all modules
@@ -148,10 +51,127 @@ def get_all_generic_test_modules() -> List[str]:
148
51
return modnames
149
52
150
53
151
- def generate_known_regression_output () -> None :
152
- """
153
- Auto-generate the baseline regression output for all
154
- the detected test modules in this directory.
155
- """
156
- for modname in get_all_generic_test_modules ():
157
- generate_output_for_test_module (modname )
54
+ class Infra :
55
+ """
56
+ Encapsulate crash dump management for automated tests.
57
+ """
58
+ Crashdump_Record = namedtuple (
59
+ 'Crashdump_Record' , 'data_dir, dump_path, \
60
+ mods_path, vmlx_path, regression_directory' )
61
+ cdr_primary = Crashdump_Record (f"{ THIS_DIR } /data" ,
62
+ f"{ THIS_DIR } /data/dump.201912060006" ,
63
+ f"{ THIS_DIR } /data/mods" ,
64
+ f"{ THIS_DIR } /data/vmlinux-5.0.0-36-generic" ,
65
+ f"{ THIS_DIR } /data/regression_output" )
66
+ cdr_alternate = Crashdump_Record (
67
+ f"{ THIS_DIR } /data_alternate" ,
68
+ f"{ THIS_DIR } /data_alternate/dump.202102031354" ,
69
+ f"{ THIS_DIR } /data_alternate/mods" ,
70
+ f"{ THIS_DIR } /data_alternate/vmlinux-5.8.0-41-generic" ,
71
+ f"{ THIS_DIR } /data_alternate/regression_output" )
72
+
73
+ def __init__ (self , which_dump : str ):
74
+ assert which_dump in (PRIMARY , ALTERNATE )
75
+ if which_dump == PRIMARY :
76
+ self .cdr = self .cdr_primary
77
+ else :
78
+ self .cdr = self .cdr_alternate
79
+
80
+ def dump_exists (self ) -> bool :
81
+ """
82
+ Used as the sole indicator of whether the integration
83
+ tests will run.
84
+ """
85
+ return os .path .exists (self .cdr .dump_path ) and os .path .exists (
86
+ self .cdr .mods_path ) and os .path .exists (self .cdr .vmlx_path )
87
+
88
+ def setup_target (self ) -> Optional [drgn .Program ]:
89
+ """
90
+ Create a drgn.Program instance and setup the SDB
91
+ context for all the integration tests. If there
92
+ is no crash dump to attach to this is going to
93
+ be an empty drgn.Program.
94
+ """
95
+ prog = drgn .Program ()
96
+ if not self .dump_exists ():
97
+ return prog
98
+ prog .set_core_dump (self .cdr .dump_path )
99
+ load_debug_info (prog , [self .cdr .vmlx_path , self .cdr .mods_path ])
100
+ return prog
101
+
102
+ def repl_invoke (self , cmd : str ) -> int :
103
+ """
104
+ Accepts a command/pipeline in string form and evaluates
105
+ it returning the exit code of the evaluation emulating
106
+ the SDB repl.
107
+ """
108
+ prog = self .setup_target ()
109
+ assert prog
110
+ return REPL (prog ,
111
+ list (sdb .get_registered_commands ().keys ())).eval_cmd (cmd )
112
+
113
+ def sdb_invoke (self , objs : Iterable [drgn .Object ],
114
+ line : str ) -> Iterable [drgn .Object ]:
115
+ """
116
+ Dispatch to sdb.invoke, but also drain the generator it returns, so
117
+ the tests can more easily access the returned objects.
118
+
119
+ This method is preferred over repl_invoke() when the test wants to
120
+ do fancier checks by mocking a few objects that are later passed
121
+ down to the pipeline. Other scenarios include but are not limited
122
+ to testing that specific exceptions are thrown or analyzing internal
123
+ state of objects that is not part of the output in stdout.
124
+ """
125
+ prog = self .setup_target ()
126
+ assert prog
127
+ return list (sdb .invoke (prog , objs , line ))
128
+
129
+ def slurp_output_file (self , modname : str , cmd : str ) -> str :
130
+ """
131
+ Given a module name and a command, find the output file
132
+ and return all of its contents as a string.
133
+ """
134
+ return Path (
135
+ f"{ self .cdr .regression_directory } /{ modname } /{ cmd } " ).read_text ()
136
+
137
+ def generate_output_for_commands (self , cmds : List [str ],
138
+ dirpath : str ) -> None :
139
+ """
140
+ Takes a list of SDB commands in string form, invokes them in the
141
+ context of the current crash dump/sdb.REPL, and stores their output
142
+ in the directory specified, each under a different file.
143
+
144
+ Note: Keep in mind that if the directory specified exists then
145
+ it will be removed together with all of its contents.
146
+ """
147
+ assert self .setup_target ()
148
+ if os .path .exists (dirpath ):
149
+ shutil .rmtree (dirpath )
150
+ os .makedirs (dirpath )
151
+ for cmd in cmds :
152
+ with open (f"{ dirpath } /{ cmd } " , 'w' ) as f :
153
+ with redirect_stdout (f ):
154
+ self .repl_invoke (cmd )
155
+
156
+ def generate_output_for_test_module (self , modname : str ) -> None :
157
+ """
158
+ Generates the regression output for all the commands of
159
+ a test module given module name. The assumption for this
160
+ to work automatically is that for the given modname "mod"
161
+ there exist a test module under test.integration named
162
+ test_mod_generic which has a list of commands in string
163
+ form called CMD_TABLE.
164
+ """
165
+ test_mod = import_module (f"tests.integration.test_{ modname } _generic" )
166
+ self .generate_output_for_commands (
167
+ test_mod .CMD_TABLE , # type: ignore[attr-defined]
168
+ f"{ self .cdr .regression_directory } /{ modname } " )
169
+ print (f"Generated regression test output for { modname } ..." )
170
+
171
+ def generate_known_regression_output (self ) -> None :
172
+ """
173
+ Auto-generate the baseline regression output for all
174
+ the detected test modules in this directory.
175
+ """
176
+ for modname in get_all_generic_test_modules ():
177
+ self .generate_output_for_test_module (modname )
0 commit comments