@@ -14,8 +14,10 @@ use acpi::{
1414use log:: { error, trace} ;
1515use std:: {
1616 ffi:: OsStr ,
17+ fmt:: Debug ,
1718 fs:: File ,
1819 io:: { Read , Write } ,
20+ panic:: { AssertUnwindSafe , catch_unwind} ,
1921 path:: PathBuf ,
2022 process:: Command ,
2123 ptr:: NonNull ,
@@ -24,10 +26,59 @@ use std::{
2426} ;
2527use tempfile:: { NamedTempFile , TempDir , tempdir} ;
2628
29+ /// The result of a call to [`run_test`]. This is a bit of a combination - it contains the actual
30+ /// result of the test, but also the interpreter - if it is still valid.
31+ pub enum RunTestResult < T >
32+ where
33+ T : Handler ,
34+ {
35+ /// The test passed, and the interpreter is still valid.
36+ Pass ( Interpreter < T > ) ,
37+
38+ /// The test failed, but the interpreter is still valid. A failure reason is also provided.
39+ Failed ( Interpreter < T > , TestFailureReason ) ,
40+
41+ /// The test failed, and the interpreter is no longer valid.
42+ Panicked ,
43+ }
44+
45+ /// The result of a test
46+ #[ derive( Debug , PartialEq ) ]
47+ pub enum TestResult {
48+ /// The test passed.
49+ Pass ,
50+
51+ /// The test failed.
52+ Failed ( TestFailureReason ) ,
53+
54+ /// The test caused a panic, probably in the interpreter. This is separated out from the failed
55+ /// case for two reasons:
56+ ///
57+ /// 1. We want to highlight panics as they are in some ways worse than a failure - some AML
58+ /// might cause the problems for the interpreter, but the system as a whole shouldn't crash
59+ /// 2. The interpreter that was being used for testing is no longer valid - a new one is needed.
60+ Panicked ,
61+ }
62+
63+ impl < T > From < & RunTestResult < T > > for TestResult
64+ where
65+ T : Handler ,
66+ {
67+ fn from ( result : & RunTestResult < T > ) -> Self {
68+ match result {
69+ RunTestResult :: Pass ( _) => TestResult :: Pass ,
70+ RunTestResult :: Failed ( _, reason) => TestResult :: Failed ( reason. clone ( ) ) ,
71+ RunTestResult :: Panicked => TestResult :: Panicked ,
72+ }
73+ }
74+ }
75+
2776/// Possible results of [`resolve_and_compile`].
2877pub enum CompilationOutcome {
2978 /// Not a valid ASL or AML file.
3079 Ignored ,
80+ /// A filesystem error occurred.
81+ FilesystemErr ( PathBuf ) ,
3182 /// This file is already an AML file, does not need recompiling.
3283 IsAml ( PathBuf ) ,
3384 /// Both .asl and .aml files exist, and the .aml file is newer - it does not need recompiling.
@@ -41,17 +92,15 @@ pub enum CompilationOutcome {
4192 Succeeded ( PathBuf ) ,
4293}
4394
44- /// Possible results of [`run_test_for_file`].
45- #[ derive( Clone , Debug , Eq , Hash , PartialEq ) ]
46- pub enum TestResult {
47- /// The test passed.
48- Pass ,
95+ /// Possible causes of a test failure.
96+ #[ derive( Clone , Debug , /*Eq, Hash,*/ PartialEq ) ]
97+ pub enum TestFailureReason {
4998 /// The test ASL failed compilation by `iasl`.
5099 CompileFail ,
51- /// Our interpreter failed to parse the resulting AML .
52- ParseFail ,
53- // TODO: should we do this??
54- NotCompiled ,
100+ /// Some error occurred attempting to read or write the test file .
101+ FilesystemErr ,
102+ /// Our interpreter failed to parse or execute the resulting AML.
103+ ParseFail ( AmlError ) ,
55104}
56105
57106/// An internal-only struct that helps keep track of temporary files generated by
@@ -62,7 +111,6 @@ struct TempScriptFile {
62111 #[ allow( unused) ]
63112 dir : TempDir ,
64113 asl_file : NamedTempFile ,
65- aml_file : PathBuf ,
66114}
67115
68116/// Determine what to do with this file - ignore, compile and parse, or just parse.
@@ -73,47 +121,62 @@ struct TempScriptFile {
73121/// - `path`: The filename of an ASL file to (possibly) compile.
74122/// - `can_compile`: Whether compilation is allowed for this file. If not, any existing AML file
75123/// with the same name (but different extension) will be used.
76- pub fn resolve_and_compile ( path : & PathBuf , can_compile : bool ) -> std:: io:: Result < CompilationOutcome > {
124+ pub fn resolve_and_compile ( path : & PathBuf , can_compile : bool ) -> CompilationOutcome {
125+ let Ok ( meta) = path. metadata ( ) else {
126+ return CompilationOutcome :: FilesystemErr ( path. clone ( ) ) ;
127+ } ;
128+
77129 // If this file is aml and it exists, it's ready for parsing
78130 // metadata() will error if the file does not exist
79- if path. extension ( ) == Some ( OsStr :: new ( "aml" ) ) && path . metadata ( ) ? . is_file ( ) {
80- return Ok ( CompilationOutcome :: IsAml ( path. clone ( ) ) ) ;
131+ if path. extension ( ) == Some ( OsStr :: new ( "aml" ) ) && meta . is_file ( ) {
132+ return CompilationOutcome :: IsAml ( path. clone ( ) ) ;
81133 }
82134
83135 // If this file is not asl, it's not interesting. Error if the file does not exist.
84- if path. extension ( ) != Some ( OsStr :: new ( "asl" ) ) || !path . metadata ( ) ? . is_file ( ) {
85- return Ok ( CompilationOutcome :: Ignored ) ;
136+ if path. extension ( ) != Some ( OsStr :: new ( "asl" ) ) || !meta . is_file ( ) {
137+ return CompilationOutcome :: Ignored ;
86138 }
87139
88140 let aml_path = path. with_extension ( "aml" ) ;
89-
90141 if aml_path. is_file ( ) {
91- let asl_last_modified = path. metadata ( ) ?. modified ( ) ?;
92- let aml_last_modified = aml_path. metadata ( ) ?. modified ( ) ?;
142+ let Ok ( asl_last_modified) = meta. modified ( ) else {
143+ return CompilationOutcome :: FilesystemErr ( path. clone ( ) ) ;
144+ } ;
145+ let Ok ( aml_last_modified) = aml_path. metadata ( ) . and_then ( |m| m. modified ( ) ) else {
146+ return CompilationOutcome :: FilesystemErr ( path. clone ( ) ) ;
147+ } ;
93148 // If the aml is more recent than the asl, use the existing aml
94149 // Otherwise continue to compilation
95150 if asl_last_modified <= aml_last_modified {
96- return Ok ( CompilationOutcome :: Newer ( aml_path) ) ;
151+ return CompilationOutcome :: Newer ( aml_path) ;
97152 }
98153 }
99154
100155 if !can_compile {
101- return Ok ( CompilationOutcome :: NotCompiled ( path. clone ( ) ) ) ;
156+ return CompilationOutcome :: NotCompiled ( path. clone ( ) ) ;
102157 }
103158
104159 // Compile the ASL file using `iasl`
105160 println ! ( "Compiling file: {}" , path. display( ) ) ;
106- let output = Command :: new ( "iasl" ) . arg ( path) . output ( ) ? ;
161+ let output = Command :: new ( "iasl" ) . arg ( path) . output ( ) ;
107162
108- if !output. status . success ( ) {
109- println ! (
110- "Failed to compile ASL file: {}. Output from iasl:\n {}" ,
111- path. display( ) ,
112- String :: from_utf8_lossy( & output. stderr)
113- ) ;
114- Ok ( CompilationOutcome :: Failed ( path. clone ( ) ) )
115- } else {
116- Ok ( CompilationOutcome :: Succeeded ( aml_path) )
163+ match output {
164+ Ok ( output) => {
165+ if !output. status . success ( ) {
166+ println ! (
167+ "Failed to compile ASL file: {}. Output from iasl:\n {}" ,
168+ path. display( ) ,
169+ String :: from_utf8_lossy( & output. stderr)
170+ ) ;
171+ CompilationOutcome :: Failed ( path. clone ( ) )
172+ } else {
173+ CompilationOutcome :: Succeeded ( aml_path)
174+ }
175+ }
176+ Err ( e) => {
177+ println ! ( "Failed to compile ASL file: {}. Error: {}" , path. display( ) , e) ;
178+ CompilationOutcome :: Failed ( path. clone ( ) )
179+ }
117180 }
118181}
119182
@@ -185,11 +248,17 @@ where
185248/// * `asl`: A string slice containing an ASL script. This will be compiled to AML using `iasl` and
186249/// then tested using [`run_test`]
187250/// * `interpreter`: The interpreter to use for testing.
188- pub fn run_test_for_string ( asl : & ' static str , interpreter : & mut Interpreter < impl Handler + Clone > ) -> TestResult {
251+ pub fn run_test_for_string < T > ( asl : & ' static str , interpreter : Interpreter < T > ) -> RunTestResult < T >
252+ where
253+ T : Handler ,
254+ {
189255 let script = create_script_file ( asl) ;
190- resolve_and_compile ( & script. asl_file . path ( ) . to_path_buf ( ) , true ) . unwrap ( ) ;
191-
192- run_test_for_file ( & script. aml_file , interpreter)
256+ match resolve_and_compile ( & script. asl_file . path ( ) . to_path_buf ( ) , true ) {
257+ CompilationOutcome :: Succeeded ( aml_path) | CompilationOutcome :: IsAml ( aml_path) => {
258+ run_test_for_file ( & aml_path, interpreter)
259+ }
260+ _ => RunTestResult :: Failed ( interpreter, TestFailureReason :: CompileFail ) ,
261+ }
193262}
194263
195264/// Test an AML file using [`run_test`]
@@ -199,23 +268,20 @@ pub fn run_test_for_string(asl: &'static str, interpreter: &mut Interpreter<impl
199268/// * `file`: The path to the AML file to test. This must be an AML file otherwise the test will
200269/// fail very quickly.
201270/// * `interpreter`: The interpreter to use for testing.
202- pub fn run_test_for_file ( file : & PathBuf , interpreter : & mut Interpreter < impl Handler + Clone > ) -> TestResult {
203- let Ok ( mut file) = File :: open ( & file) else {
204- return TestResult :: CompileFail ;
271+ pub fn run_test_for_file < T > ( file : & PathBuf , interpreter : Interpreter < T > ) -> RunTestResult < T >
272+ where
273+ T : Handler ,
274+ {
275+ let Ok ( mut file) = File :: open ( file) else {
276+ return RunTestResult :: Failed ( interpreter, TestFailureReason :: FilesystemErr ) ;
205277 } ;
206278 let mut contents = Vec :: new ( ) ;
207279 file. read_to_end ( & mut contents) . unwrap ( ) ;
208280
209281 const AML_TABLE_HEADER_LENGTH : usize = 36 ;
210282 let stream = & contents[ AML_TABLE_HEADER_LENGTH ..] ;
211283
212- match run_test ( stream, interpreter) {
213- Ok ( ( ) ) => TestResult :: Pass ,
214- Err ( e) => {
215- error ! ( "Error running test: {:?}" , e) ;
216- TestResult :: ParseFail
217- }
218- }
284+ run_test ( stream, interpreter)
219285}
220286
221287/// Internal function to create a temporary script file from an ASL string, plus to calculate the
@@ -226,16 +292,10 @@ fn create_script_file(asl: &'static str) -> TempScriptFile {
226292 let mut script_file = NamedTempFile :: with_suffix_in ( ".asl" , & dir) . unwrap ( ) ;
227293 trace ! ( "Created temp file: {:?}" , script_file. path( ) ) ;
228294
229- let output_stem = script_file. path ( ) . file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
230- let output_name = format ! ( "{}.aml" , output_stem) ;
231- let output_full_name = dir. path ( ) . join ( output_name. clone ( ) ) ;
232-
233- let new_asl = asl. replace ( "%FN%" , output_name. as_str ( ) ) ;
234-
235- script_file. write_all ( new_asl. as_bytes ( ) ) . unwrap ( ) ;
295+ script_file. write_all ( asl. as_bytes ( ) ) . unwrap ( ) ;
236296 script_file. flush ( ) . unwrap ( ) ;
237297
238- TempScriptFile { dir, asl_file : script_file, aml_file : output_full_name }
298+ TempScriptFile { dir, asl_file : script_file }
239299}
240300
241301/// Run a test on an AML stream.
@@ -248,25 +308,43 @@ fn create_script_file(asl: &'static str) -> TempScriptFile {
248308/// Arguments:
249309///
250310/// * `stream`: A slice containing the AML bytecode to test.
251- /// * `interpreter`: The interpreter to test with.
252- pub fn run_test ( stream : & [ u8 ] , interpreter : & mut Interpreter < impl Handler + Clone > ) -> Result < ( ) , AmlError > {
253- interpreter. load_table ( stream) ?;
311+ /// * `interpreter`: The interpreter to test with. The interpreter is consumed to maintain unwind
312+ /// safety - if the interpreter panics, the caller should not be able to see the interpreter in
313+ /// an inconsistent state.
314+ pub fn run_test < T > ( stream : & [ u8 ] , interpreter : Interpreter < T > ) -> RunTestResult < T >
315+ where
316+ T : Handler ,
317+ {
318+ // Without `AssertUnwindSafe`, the following code will not build as the Interpreter is not
319+ // unwind safe. To avoid the caller being able to see an inconsistent Interpreter, if a panic
320+ // occurs we drop the Interpreter, forcing the caller to create a new one.
321+ let result = catch_unwind ( AssertUnwindSafe ( || -> Result < ( ) , AmlError > {
322+ interpreter. load_table ( stream) ?;
254323
255- if let Some ( result) = interpreter. evaluate_if_present ( AmlName :: from_str ( "\\ MAIN" ) . unwrap ( ) , vec ! [ ] ) ? {
256- match * result {
257- Object :: Integer ( 0 ) => Ok ( ( ) ) ,
258- Object :: Integer ( other) => {
259- error ! ( "Test _MAIN returned non-zero exit code: {}" , other) ;
260- // TODO: wrong error - this should probs return a more complex err type
261- Err ( AmlError :: NoCurrentOp )
262- }
263- _ => {
264- error ! ( "Test _MAIN returned unexpected object type: {}" , * result) ;
265- // TODO: wrong error
266- Err ( AmlError :: NoCurrentOp )
324+ if let Some ( result) = interpreter. evaluate_if_present ( AmlName :: from_str ( "\\ MAIN" ) . unwrap ( ) , vec ! [ ] ) ? {
325+ match * result {
326+ Object :: Integer ( 0 ) => Ok ( ( ) ) ,
327+ Object :: Integer ( other) => {
328+ error ! ( "Test _MAIN returned non-zero exit code: {}" , other) ;
329+ // TODO: wrong error - this should probs return a more complex err type
330+ Err ( AmlError :: NoCurrentOp )
331+ }
332+ _ => {
333+ error ! ( "Test _MAIN returned unexpected object type: {}" , * result) ;
334+ // TODO: wrong error
335+ Err ( AmlError :: NoCurrentOp )
336+ }
267337 }
338+ } else {
339+ Ok ( ( ) )
268340 }
269- } else {
270- Ok ( ( ) )
341+ } ) ) ;
342+
343+ match result {
344+ Ok ( inner) => match inner {
345+ Ok ( ( ) ) => RunTestResult :: Pass ( interpreter) ,
346+ Err ( e) => RunTestResult :: Failed ( interpreter, TestFailureReason :: ParseFail ( e) ) ,
347+ } ,
348+ Err ( _e) => RunTestResult :: Panicked ,
271349 }
272350}
0 commit comments