From 3951974b35c8f413bdb274c4cd9a7f1452593962 Mon Sep 17 00:00:00 2001 From: Martin Hughes Date: Wed, 25 Mar 2026 11:32:52 +0000 Subject: [PATCH 1/4] Add support for running the tests from uACPI. There are some examples copied directly from the uACPI codebase into the integration tests. I believe the attribution at the top of the file should be sufficient to meet the terms of the MIT licence, since we're not including a "substantial" part of their codebase. --- Cargo.toml | 2 +- src/sdt/facs.rs | 4 +- tests/README.md | 56 ++++++++ tests/test_infra/mod.rs | 2 +- tests/uacpi_examples.rs | 191 +++++++++++++++++++++++++++ tools/aml_test_tools/src/lib.rs | 81 ++++++++---- tools/aml_test_tools/src/result.rs | 15 +++ tools/aml_tester/src/main.rs | 44 +++++- tools/uacpi_test_adapter/Cargo.toml | 11 ++ tools/uacpi_test_adapter/src/main.rs | 55 ++++++++ 10 files changed, 430 insertions(+), 31 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/uacpi_examples.rs create mode 100644 tools/aml_test_tools/src/result.rs create mode 100644 tools/uacpi_test_adapter/Cargo.toml create mode 100644 tools/uacpi_test_adapter/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index eb26d84d..e429a7fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["tools/aml_tester", "tools/acpi_dumper", "tools/aml_test_tools"] +members = ["tools/aml_tester", "tools/acpi_dumper", "tools/aml_test_tools", "tools/uacpi_test_adapter"] resolver = "2" [package] diff --git a/src/sdt/facs.rs b/src/sdt/facs.rs index 2b1dd1c8..a80f019f 100644 --- a/src/sdt/facs.rs +++ b/src/sdt/facs.rs @@ -11,7 +11,7 @@ pub struct Facs { pub flags: u32, pub x_firmware_waking_vector: u64, pub version: u8, - _reserved0: [u8; 3], + pub _reserved0: [u8; 3], pub ospm_flags: u32, - reserved1: [u8; 24], + pub reserved1: [u8; 24], } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..3d0e823e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,56 @@ +# Tests in the ACPI crate. + +There are 4 categories of tests: + +1. Unit tests in the main crate source. +2. Integration tests in the `tests` directory - these all have the extension `.rs`. +3. Raw ASL files that can be checked using `aml_tester` +4. Support for running tests from the `uacpi` project. + +## Unit tests + +These are standard Rust unit tests. Run them using `cargo test` (which will also run the integration tests). There are +relatively few unit tests at present - feel free to send PRs with more! + +## Integration tests + +These are the `.rs` files in the `tests` directory. They largely consist of snippets of ASL code that are parsed and +then executed using the `aml_test_tools` sub-crate. Most of the tests specify the expected output from the provided +`Handler`, which provides reasonable confidence the parser is working correctly. + +The `aml_test_tools` sub-crate provides various utilities for writing this style of test. + +Like the unit tests, they can be run using `cargo test`. + +## Raw ASL files + +Various ASL files are provided in the `tests` directory. They can be parsed and executed by running +`cargo run_tests -p tests` from the root of the repository. This cargo alias runs the `aml_tester` tool. + +The `aml_tester` tool provides a command-line way to run ASL files - similar to `acpiexec`. + +## Running tests from the `uacpi` project + +> See the documentation for `uacpi_test_adapter` for more detailed information. Note that a large proportion of the +> tests in the `uacpi` project do not yet pass in this crate. + +The `uacpi` project has a fairly extensive test suite. It makes sense for us to be able to check against their test +suite as well as our own. This can be done by: + +1. Checking out the [`uacpi` repository.](https://github.com/uACPI/uACPI) +2. Navigating to the root of that repo. +3. Running something like: + ```shell + AML_TESTER_PATH=../acpi/target/debug/aml_tester python3 tests/run_tests.py --test-runner ../acpi/target/debug/uacpi_test_adapter` + ``` + Adjusting the paths as necessary. (And editing as needed for PowerShell) + +Note that, at present, several of the uACPI tests run indefinitely. You may want to skip these! The easiest way to do so +is probably just to delete them... The relevant tests are: + +* `global-lock.asl` +* `hanging-while.asl` +* `infinite-recursion.asl` +* `mutex-1.asl` +* `mutex-2.asl` +* `mutex-3.asl` diff --git a/tests/test_infra/mod.rs b/tests/test_infra/mod.rs index 6ed55ffe..3e8751bf 100644 --- a/tests/test_infra/mod.rs +++ b/tests/test_infra/mod.rs @@ -14,6 +14,6 @@ pub fn run_aml_test(asl: &'static str, handler: impl Handler) { let logged_handler = LoggingHandler::new(handler); let interpreter = new_interpreter(logged_handler); - let result = run_test_for_string(asl, interpreter); + let result = run_test_for_string(asl, interpreter, &None); assert!(matches!(result, RunTestResult::Pass(_)), "Test failed with: {:?}", TestResult::from(&result)); } diff --git a/tests/uacpi_examples.rs b/tests/uacpi_examples.rs new file mode 100644 index 00000000..0402966a --- /dev/null +++ b/tests/uacpi_examples.rs @@ -0,0 +1,191 @@ +//! These examples have been adapted from the Readme of the [uACPI](https://github.com/uACPI/uACPI) +//! project at commit 1ca45f3. +//! +//! At present most of these tests probably won't work, but it'll help to guide us towards better +//! compatibility. +//! +//! The comments demonstrate some of the differences between the NT "real world" interpreter and +//! the ACPI reference standard. + +mod test_infra; + +use aml_test_tools::{handlers::null_handler::NullHandler}; + +#[test] +#[ignore] // Fails with ObjectNotOfExpectedType { expected: Reference, got: Integer } +fn expressions_with_package() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Method (TEST) { + Local0 = 10 + Local1 = Package { Local0 * 5 } + Return (DerefOf(Local1[0])) + } + + // ACPICA: AE_SUPPORT, Expressions within package elements are not supported + // Windows, uACPI: Local0 = 50 + Local0 = TEST() +} + "#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +fn package_outside_of_control_method() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + // ACPICA: internal error + // Windows, uACPI: ok + Local0 = Package { 1 } + + // I don't have a good way of testing this, but if it completes without errors it's probably OK. + Debug = Local0[0] +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // Local0 is set to 123 - we follow the ACPICA way, not the Windows way. +fn reference_rebind_semantics() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Method (MAIN, 0, NotSerialized) { + Local0 = 123 + Local1 = RefOf(Local0) + + // ACPICA: Local1 = 321, Local0 = 123 + // Windows, uACPI: Local1 = reference->Local0, Local0 = 321 + Local1 = 321 + + Return (Local0 == 123) + } +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // ParseFail(ObjectNotOfExpectedType { expected: Integer, got: Integer } (a referencing failure) +fn increment_decrement() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Local0 = 123 + Local1 = RefOf(Local0) + + // ACPICA: error + // Windows, uACPI: Local0 = 124 + Local1++ +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // ParseFail(ObjectNotOfExpectedType { expected: Reference, got: Integer }) +fn multilevel_references() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Local0 = 123 + Local1 = RefOf(Local0) + Local2 = RefOf(Local1) + + // ACPICA: Local3 = reference->Local0 + // Windows, uACPI: Local3 = 123 + Local3 = DerefOf(Local2) +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // "Stores to objects like WrappedObject(UnsafeCell { .. }) are not yet supported" +fn implicit_case_semantics() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Name (TEST, "BAR") + + // ACPICA: TEST = "00000000004F4F46" + // Windows, uACPI: TEST = "FOO" + TEST = 0x4F4F46 +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // "Stores to objects like WrappedObject(UnsafeCell { .. }) are not yet supported" +fn buffer_size_mutability() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Name (TEST, "XXXX") + Name (VAL, "") + + // ACPICA: TEST = "LONGSTRING" + // Windows, UACPI: TEST = "LONG" + TEST = "LONGSTRING" + + // ACPICA: VAL = "FOO" + // Windows, UACPI: VAL = "" + VAL = "FOO" +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +fn ref_to_local() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Method (TEST) { + Local0 = 123 + + // Use-after-free in ACPICA, perfectly fine in uACPI + Return (RefOf(Local0)) + } + + Method (FOO) { + Name (TEST, 123) + + // Use-after-free in ACPICA, object lifetime prolonged in uACPI (node is still removed from the namespace) + Return (RefOf(TEST)) + } + + Method (MAIN) { + FOO () + Return (0) + } +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} + +#[test] +#[ignore] // CopyObject not yet implemented +fn copy_object_to_self() { + const ASL: &str = r#" +DefinitionBlock("", "DSDT", 1, "RSACPI", "UACPI", 1) { + Method (TEST) { + CopyObject(123, TEST) + Return (1) + } + + // Segfault in ACPICA, prints 1 in uACPI + Debug = TEST() + + // Unreachable in ACPICA, prints 123 in uACPI + Debug = TEST +}"#; + + let handler = NullHandler; + test_infra::run_aml_test(ASL, handler); +} diff --git a/tools/aml_test_tools/src/lib.rs b/tools/aml_test_tools/src/lib.rs index 9246b618..4631569b 100644 --- a/tools/aml_test_tools/src/lib.rs +++ b/tools/aml_test_tools/src/lib.rs @@ -4,15 +4,19 @@ //! As always, feel free to offer PRs for improvements. pub mod handlers; +pub mod result; pub mod tables; -use crate::tables::{TestAcpiTable, bytes_to_tables}; +use crate::{ + result::{ExpectedResult, result_matches}, + tables::{TestAcpiTable, bytes_to_tables}, +}; use acpi::{ Handler, PhysicalMapping, address::MappedGas, - aml::{AmlError, Interpreter, namespace::AmlName, object::Object}, - sdt::Signature, + aml::{AmlError, Interpreter, namespace::AmlName}, + sdt::{Signature, facs::Facs}, }; use log::{error, trace}; use std::{ @@ -25,7 +29,7 @@ use std::{ process::Command, ptr::NonNull, str::FromStr, - sync::Arc, + sync::{Arc, atomic::AtomicU32}, }; use tempfile::{NamedTempFile, TempDir, tempdir}; @@ -234,17 +238,39 @@ where }, }); + let fake_facs = Box::new(Facs { + signature: Signature::FACS, + length: size_of::() as u32, + hardware_signature: 0, + firmware_waking_vector: 0, + global_lock: AtomicU32::new(0), + flags: 0, + x_firmware_waking_vector: 0, + version: 2, + _reserved0: [0; 3], + ospm_flags: 0, + reserved1: [0; 24], + }); + + // TODO: This *is* a memory leak - but a tolerable one for now. The FACS would be 'static in a + // real machine, and we effectively need that here since the crate will never try to release it. + // + // It'd be possible to tidy this up creating a newtype containing both the Interpeter and FACS + // which manually drops the FACS when appropriate, but since that makes the test interfaces + // less ergonomic, this is left until the leak is an actual problem. + let fake_facs_ptr = Box::leak(fake_facs) as *mut Facs; + // This PhysicalMapping is dropped when the interpreter is dropped, and if you use logging in // the handler object you'll see a call to Handler::unmap_physical_region without any // corresponding call to Interpreter::map_physical_region. - let fake_facs = PhysicalMapping { + let fake_facs_mapping = PhysicalMapping { physical_start: 0x0, - virtual_start: NonNull::new(0x8000_0000_0000_0000 as *mut acpi::sdt::facs::Facs).unwrap(), + virtual_start: NonNull::new(fake_facs_ptr).unwrap(), region_length: 32, mapped_length: 32, handler: handler.clone(), }; - Interpreter::new(handler, 2, fake_registers, Some(fake_facs)) + Interpreter::new(handler, 2, fake_registers, Some(fake_facs_mapping)) } /// Test an ASL script given as a string, using [`run_test`]. @@ -253,14 +279,18 @@ where /// * `asl`: A string slice containing an ASL script. This will be compiled to AML using `iasl` and /// then tested using [`run_test`] /// * `interpreter`: The interpreter to use for testing. -pub fn run_test_for_string(asl: &'static str, interpreter: Interpreter) -> RunTestResult +pub fn run_test_for_string( + asl: &'static str, + interpreter: Interpreter, + expected_result: &Option, +) -> RunTestResult where T: Handler, { let script = create_script_file(asl); match resolve_and_compile(&script.asl_file.path().to_path_buf(), true) { CompilationOutcome::Succeeded(aml_path) | CompilationOutcome::IsAml(aml_path) => { - run_test_for_file(&aml_path, interpreter) + run_test_for_file(&aml_path, interpreter, expected_result) } _ => RunTestResult::Failed(interpreter, TestFailureReason::CompileFail), } @@ -273,7 +303,11 @@ where /// * `file`: The path to the AML file to test. This must be an AML file otherwise the test will /// fail very quickly. /// * `interpreter`: The interpreter to use for testing. -pub fn run_test_for_file(file: &PathBuf, interpreter: Interpreter) -> RunTestResult +pub fn run_test_for_file( + file: &PathBuf, + interpreter: Interpreter, + expected_result: &Option, +) -> RunTestResult where T: Handler, { @@ -287,7 +321,7 @@ where return RunTestResult::Failed(interpreter, TestFailureReason::TablesErr); }; - run_test(tables, interpreter) + run_test(tables, interpreter, expected_result) } /// Internal function to create a temporary script file from an ASL string, plus to calculate the @@ -318,7 +352,11 @@ fn create_script_file(asl: &'static str) -> TempScriptFile { /// * `interpreter`: The interpreter to test with. The interpreter is consumed to maintain unwind /// safety - if the interpreter panics, the caller should not be able to see the interpreter in /// an inconsistent state. -pub fn run_test(tables: Vec, interpreter: Interpreter) -> RunTestResult +pub fn run_test( + tables: Vec, + interpreter: Interpreter, + expected_result: &Option, +) -> RunTestResult where T: Handler, { @@ -341,18 +379,13 @@ where trace!("All tables loaded"); if let Some(result) = interpreter.evaluate_if_present(AmlName::from_str("\\MAIN").unwrap(), vec![])? { - match *result { - Object::Integer(0) => Ok(()), - Object::Integer(other) => { - let e = format!("Test _MAIN returned non-zero exit code: {}", other); - error!("{}", e); - Err(AmlError::HostError(e)) - } - _ => { - let e = format!("Test _MAIN returned unexpected object type: {}", *result); - error!("{}", e); - Err(AmlError::HostError(e)) - } + let expected_result = expected_result.as_ref().unwrap_or(&ExpectedResult::Integer(0)); + if result_matches(expected_result, &result) { + Ok(()) + } else { + let e = format!("Unexpected MAIN result: {:?}", expected_result); + error!("{}", e); + Err(AmlError::HostError(e)) } } else { Ok(()) diff --git a/tools/aml_test_tools/src/result.rs b/tools/aml_test_tools/src/result.rs new file mode 100644 index 00000000..b615b944 --- /dev/null +++ b/tools/aml_test_tools/src/result.rs @@ -0,0 +1,15 @@ +use acpi::aml::object::Object; + +#[derive(Clone, Debug)] +pub enum ExpectedResult { + Integer(u64), + String(String), +} + +pub fn result_matches(expected: &ExpectedResult, actual: &Object) -> bool { + match (expected, actual) { + (ExpectedResult::Integer(expected), Object::Integer(actual)) => expected == actual, + (ExpectedResult::String(expected), Object::String(actual)) => expected == actual, + _ => false, + } +} diff --git a/tools/aml_tester/src/main.rs b/tools/aml_tester/src/main.rs index b48ef4bc..cd37effd 100644 --- a/tools/aml_tester/src/main.rs +++ b/tools/aml_tester/src/main.rs @@ -7,6 +7,9 @@ * - Run the AML parser on each AML file, printing test output like `cargo test` does in a nice table for * each AML file * - For failing tests, print out a nice summary of the errors for each file + * + * `aml_tester` can be used with `uacpi_test_adapter` to run the uACPI test suite (except for the + * resource tests). See the `uacpi_test_adapter` documentation for more information. */ use acpi::Handler; @@ -14,6 +17,7 @@ use aml_test_tools::{ handlers::{logging_handler::LoggingHandler, null_handler::NullHandler}, new_interpreter, resolve_and_compile, + result::ExpectedResult, CompilationOutcome, RunTestResult, TestFailureReason, @@ -26,9 +30,8 @@ use std::{ fs::{self}, io::Write, path::{Path, PathBuf}, - process::Command, + process::{Command, ExitCode}, }; -use std::process::ExitCode; /// The result of a test, with all other information (error codes etc.) stripped away. This value /// can then be stored in a Set - the test results with more info cannot. @@ -73,6 +76,16 @@ If the ASL contains a MAIN method, it will be executed.", .help("Don't clear the namespace between tests"), ) .arg(Arg::new("path").short('p').long("path").required(false).action(ArgAction::Set).value_name("DIR")) + // The --expect argument is defined to exactly match the parameters sent by the uACPI test + // runner. This makes it much easier to test our crate against their tests. + .arg( + Arg::new("expect") + .long("expect") + .value_names(["TYPE", "VALUE"]) + .num_args(2) + .required(false) + .help("Expect MAIN to return a value: --expect int or --expect str "), + ) .arg(Arg::new("files").action(ArgAction::Append).value_name("FILE.{asl,aml}")) .group(ArgGroup::new("files_list").args(["path", "files"]).required(true)); if std::env::args().count() <= 1 { @@ -83,6 +96,31 @@ If the ASL contains a MAIN method, it will be executed.", let matches = cmd.get_matches(); + let Ok(expected_result) = matches + .get_many::("expect") + .map(|mut values| { + let expect_type = values.next().unwrap(); + let expect_value = values.next().unwrap(); + + match expect_type.as_str() { + "int" => { + let parsed = expect_value.parse::().map_err(|_| { + println!("Invalid --expect value for type int: expected an unsigned integer"); + })?; + Ok(ExpectedResult::Integer(parsed)) + } + "str" => Ok(ExpectedResult::String(expect_value.clone())), + _ => { + println!("Invalid --expect type: expected `int` or `str`"); + Err(()) + } + } + }) + .transpose() + else { + return ExitCode::FAILURE; + }; + // Make sure we have the ability to compile ASL -> AML, if user wants it let user_wants_compile = !matches.get_flag("no_compile"); let can_compile = user_wants_compile && @@ -164,7 +202,7 @@ If the ASL contains a MAIN method, it will be executed.", print!("Testing AML file: {:?}... ", file_entry); std::io::stdout().flush().unwrap(); - let result = aml_test_tools::run_test_for_file(&file_entry, interpreter); + let result = aml_test_tools::run_test_for_file(&file_entry, interpreter, &expected_result); let simple_result: FinalTestResult = TestResult::from(&result).into(); let interpreter_returned = match result { diff --git a/tools/uacpi_test_adapter/Cargo.toml b/tools/uacpi_test_adapter/Cargo.toml new file mode 100644 index 00000000..c6620f5b --- /dev/null +++ b/tools/uacpi_test_adapter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "uacpi_test_adapter" +version = "0.0.0" +publish = false +authors = ["Martin Hughes"] +repository = "https://github.com/rust-osdev/acpi" +description = "uACPI to aml_tester adapter" +categories = ["hardware-support"] +readme = "../README.md" +license = "MIT/Apache-2.0" +edition = "2024" diff --git a/tools/uacpi_test_adapter/src/main.rs b/tools/uacpi_test_adapter/src/main.rs new file mode 100644 index 00000000..9e79b9d3 --- /dev/null +++ b/tools/uacpi_test_adapter/src/main.rs @@ -0,0 +1,55 @@ +//! A wrapper around the `aml_tester` crate that allows it to run the uACPI test suite. +//! +//! Simply forwards all arguments sent from the uACPI test runner to `aml_tester`, *except* for when +//! the resource-tests are requested (which we don't support). +//! +//! Why make an adapter instead of just running `aml_tester` directly? +//! * It allows uACPI to change their testing system without affecting users of `aml_tester`. +//! * We can write a separate binary or other tool for running "resource-tests" without needing to +//! complicate `aml_tester`. +//! +//! Usage: +//! * Set the environment variable `AML_TESTER_PATH` to the path of the `aml_tester` binary. +//! * Run the uACPI test suite but setting `uacpi_test_adapter` to the test runner. +//! +//! e.g.: from the uACPI root directory: +//! ```sh +//! AML_TESTER_PATH=../acpi/target/debug/aml_tester python3 tests/run_tests.py --test-runner ../acpi/target/debug/uacpi_test_adapter +//! ``` +//! > Adjust the paths as needed! +//! +//! Notes: +//! +//! You may prefer to manually run aml_tester with individual ASL files from the uACPI test suite, +//! as you'll get better formatting. However, that would require you to manually enter the expected +//! results. +use std::{ + env, + process::{Command, ExitCode}, +}; + +fn main() -> ExitCode { + // We don't support the resource tests, so just claim success to allow the main test suite to + // run. + if env::args_os().skip(1).any(|arg| arg == "resource-tests") { + return ExitCode::SUCCESS; + } + + let Some(tester_path) = env::var_os("AML_TESTER_PATH") else { + eprintln!("AML_TESTER_PATH is not set"); + return ExitCode::FAILURE; + }; + + let status = match Command::new(tester_path).args(env::args_os().skip(1)).status() { + Ok(status) => status, + Err(err) => { + eprintln!("Failed to execute AML_TESTER_PATH: {err}"); + return ExitCode::FAILURE; + } + }; + + match status.code() { + Some(code) => ExitCode::from(code as u8), + None => ExitCode::FAILURE, + } +} From c81b3f78aec1408420378544f8168688d617495a Mon Sep 17 00:00:00 2001 From: Martin Hughes Date: Fri, 10 Apr 2026 21:19:52 +0100 Subject: [PATCH 2/4] Make it easier to find `aml_tester` `uacpi_test_adapter` will now try three different ways to find `aml_tester`: 1. From the `AML_TESTER_PATH` environment variable, 2. From the system path, 3. As a sibling executable to the running one. Also, in `aml_test_tools`, remove the memory leak. --- tests/README.md | 5 +- tools/aml_test_tools/src/lib.rs | 22 +++++---- tools/uacpi_test_adapter/Cargo.toml | 5 +- tools/uacpi_test_adapter/src/main.rs | 68 ++++++++++++++++++++++++++-- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/tests/README.md b/tests/README.md index 3d0e823e..e3444eb7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -41,9 +41,10 @@ suite as well as our own. This can be done by: 2. Navigating to the root of that repo. 3. Running something like: ```shell - AML_TESTER_PATH=../acpi/target/debug/aml_tester python3 tests/run_tests.py --test-runner ../acpi/target/debug/uacpi_test_adapter` + python3 tests/run_tests.py --test-runner ../acpi/target/debug/uacpi_test_adapter ``` - Adjusting the paths as necessary. (And editing as needed for PowerShell) + Adjusting the paths as necessary. See the `uacpi_test_adapter` documentation for details on how it finds + `aml_tester`. Note that, at present, several of the uACPI tests run indefinitely. You may want to skip these! The easiest way to do so is probably just to delete them... The relevant tests are: diff --git a/tools/aml_test_tools/src/lib.rs b/tools/aml_test_tools/src/lib.rs index 4631569b..95871574 100644 --- a/tools/aml_test_tools/src/lib.rs +++ b/tools/aml_test_tools/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(sync_unsafe_cell)] //! A collection of helper utilities for testing AML using the [`acpi`] crate. //! //! These utilities are very heavily based on the way the [`acpi`] crate has used them historically. @@ -20,6 +21,7 @@ use acpi::{ }; use log::{error, trace}; use std::{ + cell::SyncUnsafeCell, ffi::OsStr, fmt::Debug, fs::File, @@ -198,6 +200,11 @@ pub fn resolve_and_compile(path: &PathBuf, can_compile: bool) -> CompilationOutc /// /// * `handler`: The Handler to be called by the interpreter when needed. This crate includes some /// example [handlers]. +/// +/// Thread safety: +/// +/// This function uses a single, static, FACS for all tests. If tests are run in parallel, this +/// means they will share a single global lock. pub fn new_interpreter(handler: T) -> Interpreter where T: Handler + Clone, @@ -238,7 +245,12 @@ where }, }); - let fake_facs = Box::new(Facs { + // As noted in the doc-comment, this Facs is shared between all tests - so if tests are run in + // parallel, they will share a single global lock. + // + // This construct is needed because the FACS in a real system is effectively 'static, and the + // interpreter relies on that. + static FAKE_FACS: SyncUnsafeCell = SyncUnsafeCell::new(Facs { signature: Signature::FACS, length: size_of::() as u32, hardware_signature: 0, @@ -252,13 +264,7 @@ where reserved1: [0; 24], }); - // TODO: This *is* a memory leak - but a tolerable one for now. The FACS would be 'static in a - // real machine, and we effectively need that here since the crate will never try to release it. - // - // It'd be possible to tidy this up creating a newtype containing both the Interpeter and FACS - // which manually drops the FACS when appropriate, but since that makes the test interfaces - // less ergonomic, this is left until the leak is an actual problem. - let fake_facs_ptr = Box::leak(fake_facs) as *mut Facs; + let fake_facs_ptr = FAKE_FACS.get(); // This PhysicalMapping is dropped when the interpreter is dropped, and if you use logging in // the handler object you'll see a call to Handler::unmap_physical_region without any diff --git a/tools/uacpi_test_adapter/Cargo.toml b/tools/uacpi_test_adapter/Cargo.toml index c6620f5b..899478b0 100644 --- a/tools/uacpi_test_adapter/Cargo.toml +++ b/tools/uacpi_test_adapter/Cargo.toml @@ -6,6 +6,9 @@ authors = ["Martin Hughes"] repository = "https://github.com/rust-osdev/acpi" description = "uACPI to aml_tester adapter" categories = ["hardware-support"] -readme = "../README.md" +readme = "../../README.md" license = "MIT/Apache-2.0" edition = "2024" + +[dependencies] +which = "8.0.2" diff --git a/tools/uacpi_test_adapter/src/main.rs b/tools/uacpi_test_adapter/src/main.rs index 9e79b9d3..48db20f8 100644 --- a/tools/uacpi_test_adapter/src/main.rs +++ b/tools/uacpi_test_adapter/src/main.rs @@ -9,7 +9,12 @@ //! complicate `aml_tester`. //! //! Usage: -//! * Set the environment variable `AML_TESTER_PATH` to the path of the `aml_tester` binary. +//! * Make sure `aml_tester` can be found. The following methods are tried, in this order of +//! precedence: +//! * Set the environment variable `AML_TESTER_PATH` to the path and filename of the `aml_tester` +//! binary, or +//! * Make sure `aml_tester` is in the system PATH, or +//! * Ensure `aml_tester` is in the same folder as `uacpi_test_adapter`. //! * Run the uACPI test suite but setting `uacpi_test_adapter` to the test runner. //! //! e.g.: from the uACPI root directory: @@ -25,8 +30,11 @@ //! results. use std::{ env, + ffi::OsString, + path::PathBuf, process::{Command, ExitCode}, }; +use which::which; fn main() -> ExitCode { // We don't support the resource tests, so just claim success to allow the main test suite to @@ -35,15 +43,15 @@ fn main() -> ExitCode { return ExitCode::SUCCESS; } - let Some(tester_path) = env::var_os("AML_TESTER_PATH") else { - eprintln!("AML_TESTER_PATH is not set"); + let Some(tester_path) = get_aml_tester_path() else { + eprintln!("aml_tester not found. Try setting AML_TESTER_PATH to the path of the aml_tester binary."); return ExitCode::FAILURE; }; let status = match Command::new(tester_path).args(env::args_os().skip(1)).status() { Ok(status) => status, Err(err) => { - eprintln!("Failed to execute AML_TESTER_PATH: {err}"); + eprintln!("Failed to execute aml_tester: {err}"); return ExitCode::FAILURE; } }; @@ -53,3 +61,55 @@ fn main() -> ExitCode { None => ExitCode::FAILURE, } } + +/// Find the path to the `aml_tester` binary. +/// +/// Uses the search order given in this executable's main documentation. +fn get_aml_tester_path() -> Option { + [get_aml_tester_from_env, get_aml_tester_from_path_env, get_aml_tester_from_binary_path] + .iter() + .find_map(|f| f()) +} + +/// If the environment variable `AML_TESTER_PATH` is set, use that. Assume it is correct without +/// checking. +fn get_aml_tester_from_env() -> Option { + env::var_os("AML_TESTER_PATH") +} + +/// If `aml_tester` is in the system PATH, use that. +fn get_aml_tester_from_path_env() -> Option { + which("aml_tester").ok().map(|path| path.into()) +} + +/// If `aml_tester` exists alongside this executable, use that. +fn get_aml_tester_from_binary_path() -> Option { + // This says: "change the name of the current executable to `aml_tester` and see if that exists." + env::current_exe().ok().map(change_exec_name).and_then(|path| which(path).ok()).map(|path| path.into()) +} + +/// Replace the filename of the given path with `aml_tester`. Preserve the extension so that +/// Windows won't have problems. +fn change_exec_name(mut path: PathBuf) -> PathBuf { + // The final `unwrap` on the following line is reasonable because if the UTF-8 conversion fails, + // the filename is probably invalid, so we don't really want to keep trying to use it! + let extension = String::from(path.extension().unwrap_or_default().to_str().unwrap()); + path.set_file_name("aml_tester"); + path.set_extension(extension); + path +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_filename_replacement() { + const PATH: &str = "/a/b/c/uacpi_test_adapter.ext"; + let original_path = PathBuf::from(PATH); + let expected_path = PathBuf::from("/a/b/c/aml_tester.ext"); + let result_path = change_exec_name(original_path); + + assert_eq!(result_path, expected_path); + } +} From 2e8640338653b66e60108047c9cf023d1c6aea25 Mon Sep 17 00:00:00 2001 From: Martin Hughes Date: Mon, 13 Apr 2026 20:39:38 +0100 Subject: [PATCH 3/4] Better error message --- tools/aml_test_tools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/aml_test_tools/src/lib.rs b/tools/aml_test_tools/src/lib.rs index 95871574..6687025c 100644 --- a/tools/aml_test_tools/src/lib.rs +++ b/tools/aml_test_tools/src/lib.rs @@ -389,7 +389,7 @@ where if result_matches(expected_result, &result) { Ok(()) } else { - let e = format!("Unexpected MAIN result: {:?}", expected_result); + let e = format!("Unexpected MAIN result: {}, expected: {:?}", *result, expected_result); error!("{}", e); Err(AmlError::HostError(e)) } From a9d878eecd39d19f64f8d3857be2f916f318bc89 Mon Sep 17 00:00:00 2001 From: Martin Hughes Date: Wed, 15 Apr 2026 09:28:46 +0100 Subject: [PATCH 4/4] Support hex constants in expected result The `parse_int` crate can handle constants with prefixes (such as `0x`) but the stabdard `parse` function cannot. --- tools/aml_tester/Cargo.toml | 1 + tools/aml_tester/src/main.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/aml_tester/Cargo.toml b/tools/aml_tester/Cargo.toml index c38f20ec..82e58f67 100644 --- a/tools/aml_tester/Cargo.toml +++ b/tools/aml_tester/Cargo.toml @@ -10,4 +10,5 @@ aml_test_tools = { path = "../aml_test_tools" } clap = "4" colored = "3.1.1" log = "0.4" +parse_int = "0.9.0" pretty_env_logger = "0.5.0" diff --git a/tools/aml_tester/src/main.rs b/tools/aml_tester/src/main.rs index cd37effd..77c7b174 100644 --- a/tools/aml_tester/src/main.rs +++ b/tools/aml_tester/src/main.rs @@ -25,6 +25,7 @@ use aml_test_tools::{ }; use clap::{Arg, ArgAction, ArgGroup}; use colored::Colorize; +use parse_int::parse; use std::{ collections::HashSet, fs::{self}, @@ -104,7 +105,7 @@ If the ASL contains a MAIN method, it will be executed.", match expect_type.as_str() { "int" => { - let parsed = expect_value.parse::().map_err(|_| { + let parsed = parse::(expect_value).map_err(|_| { println!("Invalid --expect value for type int: expected an unsigned integer"); })?; Ok(ExpectedResult::Integer(parsed))