Skip to content

aml_tester: Add positional file arguments, in-order parsing and shared namespace #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 133 additions & 63 deletions aml_tester/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@
*/

use aml::{AmlContext, DebugVerbosity};
use clap::{Arg, ArgAction};
use clap::{Arg, ArgGroup, ArgAction};
use std::{
ffi::OsStr,
fs::{self, File},
io::{Read, Write},
ops::Not,
path::Path,
path::{Path, PathBuf},
process::Command,
collections::HashSet,
};

enum CompilationOutcome {
Ignored,
IsAml(PathBuf),
Newer(PathBuf),
NotCompiled(PathBuf),
Failed(PathBuf),
Succeeded(PathBuf),
}

fn main() -> std::io::Result<()> {
log::set_logger(&Logger).unwrap();
log::set_max_level(log::LevelFilter::Trace);
Expand All @@ -28,36 +37,102 @@ fn main() -> std::io::Result<()> {
.version("v0.1.0")
.author("Isaac Woods")
.about("Compiles and tests ASL files")
.arg(Arg::new("path").short('p').long("path").required(true).action(ArgAction::Set).value_name("DIR"))
.arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue))
.arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue).help("Don't compile asl to aml"))
.arg(Arg::new("reset").long("reset").action(ArgAction::SetTrue).help("Clear namespace after each file"))
.arg(Arg::new("path").short('p').long("path").required(false).action(ArgAction::Set).value_name("DIR"))
.arg(Arg::new("files").action(ArgAction::Append).value_name("FILE.{asl,aml}"))
.group(ArgGroup::new("files_list").args(["path", "files"]).required(true))
.get_matches();

let dir_path = Path::new(matches.get_one::<String>("path").unwrap());
println!("Running tests in directory: {:?}", dir_path);
// Get an initial list of files - may not work correctly on non-UTF8 OsString
let files: Vec<String> = if matches.contains_id("path") {
let dir_path = Path::new(matches.get_one::<String>("path").unwrap());
println!("Running tests in directory: {:?}", dir_path);
fs::read_dir(dir_path)?.filter_map(| entry | if entry.is_ok() {
Some(entry.unwrap().path().to_string_lossy().to_string())
} else {
None
}).collect()
} else {
matches.get_many::<String>("files").unwrap_or_default().map(| name | name.to_string()).collect()
};

// Make sure all files exist, propagate error if it occurs
files.iter().fold(Ok(()), | result: std::io::Result<()>, file | {
let path = Path::new(file);
if !path.is_file() {
println!("Not a regular file: {}", file);
// Get the io error if there is one
path.metadata()?;
}
result
})?;

// 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 &&
// Test if `iasl` is installed, so we can give a good error later if it's not
match Command::new("iasl").arg("-v").status() {
Ok(exit_status) if exit_status.success() => true,
Ok(exit_status) => {
panic!("`iasl` exited with unsuccessful status: {:?}", exit_status);
},
Err(_) => false,
};

let compiled_files: Vec<CompilationOutcome> = files.iter().map(| name | resolve_and_compile(name, can_compile).unwrap()).collect();

if !matches.get_flag("no_compile") {
let (passed, failed) = compile_asl_files(dir_path)?;
println!("Compiled {} ASL files: {} passed, {} failed.", passed + failed, passed, failed);
// Check if compilation should have happened but did not
if user_wants_compile && compiled_files.iter().any(| outcome | matches!(outcome, CompilationOutcome::NotCompiled(_))) {
panic!("`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`");
}
// Report compilation results
if user_wants_compile {
let (passed, failed) = compiled_files.iter()
.fold((0, 0), | (passed, failed), outcome | match outcome {
CompilationOutcome::Succeeded(_) => (passed + 1, failed),
CompilationOutcome::Failed(_) => (passed, failed + 1),
_ => (passed, failed),
});
if passed + failed > 0 {
println!("Compiled {} ASL files: {} passed, {} failed.", passed + failed, passed, failed);
}
}

/*
* Now, we find all the AML files in the directory, and try to compile them with the `aml`
* parser.
*/
let aml_files = fs::read_dir(dir_path)?
.filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("aml")))
.map(Result::unwrap);
// Make a list of the files we have processed, and skip them if we see them again
let mut dedup_list: HashSet<PathBuf> = HashSet::new();

// Filter down to the final list of AML files
let aml_files = compiled_files.iter()
.filter_map(| outcome | match outcome {
CompilationOutcome::IsAml(path) => Some(path.clone()),
CompilationOutcome::Newer(path) => Some(path.clone()),
CompilationOutcome::Succeeded(path) => Some(path.clone()),
CompilationOutcome::Ignored | CompilationOutcome::Failed(_) | CompilationOutcome::NotCompiled(_) => None,
})
.filter(| path | if dedup_list.contains(path) {
false
} else {
dedup_list.insert(path.clone());
true
});

let user_wants_reset = matches.get_flag("reset");
let mut context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);

let (passed, failed) = aml_files.fold((0, 0), |(passed, failed), file_entry| {
print!("Testing AML file: {:?}... ", file_entry.path());
print!("Testing AML file: {:?}... ", file_entry);
std::io::stdout().flush().unwrap();

let mut file = File::open(file_entry.path()).unwrap();
let mut file = File::open(file_entry).unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();

const AML_TABLE_HEADER_LENGTH: usize = 36;
let mut context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);

if user_wants_reset {
context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);
}

match context.parse_table(&contents[AML_TABLE_HEADER_LENGTH..]) {
Ok(()) => {
Expand All @@ -78,58 +153,53 @@ fn main() -> std::io::Result<()> {
Ok(())
}

fn compile_asl_files(dir_path: &Path) -> std::io::Result<(u32, u32)> {
let mut asl_files = fs::read_dir(dir_path)?
.filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("asl")))
.map(Result::unwrap)
.peekable();
/// Determine what to do with this file - ignore, compile and parse, or just parse.
/// If ".aml" does not exist, or if ".asl" is newer, compiles the file.
/// If the ".aml" file is newer, indicate it is ready to parse.
fn resolve_and_compile(name: &str, can_compile: bool) -> std::io::Result<CompilationOutcome> {
let path = PathBuf::from(name);

if !asl_files.peek().is_none() {
// Test if `iasl` is installed, so we can give a good error if it's not
match Command::new("iasl").arg("-v").status() {
Ok(exit_status) => if exit_status.success().not() {
panic!("`iasl` exited with unsuccessfull status: {:?}", exit_status);
},
Err(_) => panic!("`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`"),
}
// If this file is aml and it exists, it's ready for parsing
// metadata() will error if the file does not exist
if path.extension() == Some(OsStr::new("aml")) && path.metadata()?.is_file() {
return Ok(CompilationOutcome::IsAml(path));
}

let mut passed = 0;
let mut failed = 0;

for file in asl_files {
let aml_path = file.path().with_extension(OsStr::new("aml"));
// If this file is not asl, it's not interesting. Error if the file does not exist.
if path.extension() != Some(OsStr::new("asl")) || !path.metadata()?.is_file() {
return Ok(CompilationOutcome::Ignored);
}

/*
* Check if an AML path with a matching last-modified date exists. If it
* does, we don't need to compile the ASL file again.
*/
if aml_path.is_file() {
let asl_last_modified = file.metadata()?.modified()?;
let aml_last_modified = aml_path.metadata()?.modified()?;
let aml_path = path.with_extension("aml");

if asl_last_modified <= aml_last_modified {
continue;
}
if aml_path.is_file() {
let asl_last_modified = path.metadata()?.modified()?;
let aml_last_modified = aml_path.metadata()?.modified()?;
// If the aml is more recent than the asl, use the existing aml
// Otherwise continue to compilation
if asl_last_modified <= aml_last_modified {
return Ok(CompilationOutcome::Newer(aml_path))
}
}

// Compile the ASL file using `iasl`
println!("Compiling file: {}", file.path().to_str().unwrap());
let output = Command::new("iasl").arg(file.path()).output()?;

if output.status.success() {
passed += 1;
} else {
failed += 1;
println!(
"Failed to compile ASL file: {}. Output from iasl:\n {}",
file.path().to_str().unwrap(),
String::from_utf8_lossy(&output.stderr)
);
}
if !can_compile {
return Ok(CompilationOutcome::NotCompiled(path));
}

// Compile the ASL file using `iasl`
println!("Compiling file: {}", name);
let output = Command::new("iasl").arg(name).output()?;

Ok((passed, failed))
if !output.status.success() {
println!(
"Failed to compile ASL file: {}. Output from iasl:\n {}",
name,
String::from_utf8_lossy(&output.stderr)
);
Ok(CompilationOutcome::Failed(path))
} else {
Ok(CompilationOutcome::Succeeded(aml_path))
}
}

struct Logger;
Expand Down