summaryrefslogtreecommitdiff
path: root/rust/tests
diff options
context:
space:
mode:
Diffstat (limited to 'rust/tests')
-rw-r--r--rust/tests/background_task.rs49
-rw-r--r--rust/tests/binary_reader.rs88
-rw-r--r--rust/tests/binary_view.rs64
-rw-r--r--rust/tests/binary_writer.rs79
-rw-r--r--rust/tests/collaboration.rs209
-rw-r--r--rust/tests/component.rs26
-rw-r--r--rust/tests/data_buffer.rs88
-rw-r--r--rust/tests/demangler.rs85
-rw-r--r--rust/tests/high_level_il.rs41
-rw-r--r--rust/tests/initialization.rs37
-rw-r--r--rust/tests/low_level_il.rs230
-rw-r--r--rust/tests/main_thread.rs29
-rw-r--r--rust/tests/medium_level_il.rs88
-rw-r--r--rust/tests/platform.rs32
-rw-r--r--rust/tests/project.rs350
-rw-r--r--rust/tests/type_archive.rs31
-rw-r--r--rust/tests/type_container.rs137
-rw-r--r--rust/tests/type_parser.rs85
-rw-r--r--rust/tests/types.rs50
-rw-r--r--rust/tests/worker_thread.rs45
-rw-r--r--rust/tests/workflow.rs93
21 files changed, 1936 insertions, 0 deletions
diff --git a/rust/tests/background_task.rs b/rust/tests/background_task.rs
new file mode 100644
index 00000000..d0c329e3
--- /dev/null
+++ b/rust/tests/background_task.rs
@@ -0,0 +1,49 @@
+use binaryninja::background_task::*;
+use binaryninja::headless::Session;
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_background_task_registered(_session: &Session) {
+ let task_progress = "test registered";
+ let task = BackgroundTask::new(task_progress, false);
+ BackgroundTask::running_tasks()
+ .iter()
+ .find(|t| t.progress_text().as_str() == task_progress)
+ .expect("Task not running");
+ task.finish();
+ let still_running = BackgroundTask::running_tasks()
+ .iter()
+ .find(|t| t.progress_text().as_str() == task_progress)
+ .is_some();
+ assert!(!still_running, "Task still running");
+}
+
+#[rstest]
+fn test_background_task_cancellable(_session: &Session) {
+ let task_progress = "test cancellable";
+ let task = BackgroundTask::new(task_progress, false);
+ BackgroundTask::running_tasks()
+ .iter()
+ .find(|t| t.progress_text().as_str() == task_progress)
+ .expect("Task not running");
+ task.cancel();
+ assert!(task.is_cancelled());
+ task.finish();
+}
+
+#[rstest]
+fn test_background_task_progress(_session: &Session) {
+ let task = BackgroundTask::new("test progress", false);
+ let first_progress = task.progress_text().to_string();
+ assert_eq!(first_progress, "test progress");
+ task.set_progress_text("new progress");
+ let second_progress = task.progress_text().to_string();
+ assert_eq!(second_progress, "new progress");
+ task.finish();
+}
diff --git a/rust/tests/binary_reader.rs b/rust/tests/binary_reader.rs
new file mode 100644
index 00000000..88017052
--- /dev/null
+++ b/rust/tests/binary_reader.rs
@@ -0,0 +1,88 @@
+use binaryninja::binary_reader::BinaryReader;
+use binaryninja::binary_view::BinaryViewBase;
+use binaryninja::headless::Session;
+use rstest::*;
+use std::io::{Read, Seek, SeekFrom};
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_binary_reader_seek(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let mut reader = BinaryReader::new(&view);
+ let end_offset = view.len();
+
+ // Test seeking to a specific position
+ reader
+ .seek(SeekFrom::Start(0))
+ .expect("Failed to seek to start");
+ assert_eq!(reader.offset(), 0, "Reader failed to seek to the start");
+
+ reader
+ .seek(SeekFrom::End(0))
+ .expect("Failed to seek to end");
+ assert_eq!(
+ reader.offset(),
+ end_offset,
+ "Reader failed to seek to the end"
+ );
+
+ // Test relative seeking
+ reader
+ .seek(SeekFrom::Start(10))
+ .expect("Failed to seek to position 10");
+ assert_eq!(reader.offset(), 10, "Reader failed to seek to position 10");
+
+ reader
+ .seek(SeekFrom::Current(-5))
+ .expect("Failed to perform relative seek");
+ assert_eq!(
+ reader.offset(),
+ 5,
+ "Reader failed to perform relative seek correctly"
+ );
+}
+
+#[rstest]
+fn test_binary_reader_read(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let mut reader = BinaryReader::new(&view);
+
+ reader
+ .seek(SeekFrom::Start(0))
+ .expect("Failed to seek to start");
+ let mut buffer = [0u8; 4];
+ reader
+ .read_exact(&mut buffer)
+ .expect("Failed to read 4 bytes");
+ assert_eq!(
+ buffer.len(),
+ 4,
+ "Failed to read the correct number of bytes"
+ );
+
+ // Validate the buffer.
+ assert_eq!(
+ &buffer,
+ &[0x4c, 0x01, 0x87, 0x00],
+ "Buffer content does not match the expected bytes"
+ );
+
+ // Test attempting to read beyond the end of the file.
+ reader
+ .seek(SeekFrom::End(0))
+ .expect("Failed to seek to end");
+ let mut eof_buffer = [0u8; 1];
+ let result = reader.read(&mut eof_buffer);
+ assert!(
+ result.is_err(),
+ "Expected an error when reading past EOF, got success instead"
+ );
+}
diff --git a/rust/tests/binary_view.rs b/rust/tests/binary_view.rs
new file mode 100644
index 00000000..65f6b45b
--- /dev/null
+++ b/rust/tests/binary_view.rs
@@ -0,0 +1,64 @@
+use binaryninja::binary_view::{AnalysisState, BinaryViewBase, BinaryViewExt};
+use binaryninja::headless::Session;
+use binaryninja::symbol::{SymbolBuilder, SymbolType};
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_binary_loading(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ assert!(view.has_initial_analysis(), "No initial analysis");
+ assert_eq!(view.analysis_progress().state, AnalysisState::IdleState);
+ assert_eq!(view.file().is_analysis_changed(), true);
+ assert_eq!(view.file().is_database_backed(), false);
+}
+
+#[rstest]
+fn test_binary_saving(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ // Verify the contents before we modify.
+ let original_contents = view.read_vec(0x1560, 4);
+ assert_eq!(original_contents, [0x00, 0xf1, 0x00, 0x00]);
+ assert_eq!(view.write(0x1560, &[0xff, 0xff, 0xff, 0xff]), 4);
+ // Verify that we modified the binary
+ let modified_contents = view.read_vec(0x1560, 4);
+ assert_eq!(modified_contents, [0xff, 0xff, 0xff, 0xff]);
+ // Save the modified file
+ assert!(view.save_to_path(out_dir.join("atox.obj.new")));
+ // Verify that the file exists and is modified.
+ let new_view =
+ binaryninja::load(out_dir.join("atox.obj.new")).expect("Failed to load new view");
+ assert_eq!(new_view.read_vec(0x1560, 4), [0xff, 0xff, 0xff, 0xff]);
+}
+
+#[rstest]
+fn test_binary_saving_database(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ // Update a symbol to verify modification
+ let entry_function = view
+ .entry_point_function()
+ .expect("Failed to get entry point function");
+ let new_entry_func_symbol =
+ SymbolBuilder::new(SymbolType::Function, "test", entry_function.start()).create();
+ view.define_user_symbol(&new_entry_func_symbol);
+ // Verify that we modified the binary
+ assert_eq!(entry_function.symbol().raw_name().as_str(), "test");
+ // Save the modified database.
+ assert!(view.file().create_database(out_dir.join("atox.obj.bndb")));
+ // Verify that the file exists and is modified.
+ let new_view =
+ binaryninja::load(out_dir.join("atox.obj.bndb")).expect("Failed to load new view");
+ let new_entry_function = new_view
+ .entry_point_function()
+ .expect("Failed to get entry point function");
+ assert_eq!(new_entry_function.symbol().raw_name().as_str(), "test");
+}
diff --git a/rust/tests/binary_writer.rs b/rust/tests/binary_writer.rs
new file mode 100644
index 00000000..bc9c2165
--- /dev/null
+++ b/rust/tests/binary_writer.rs
@@ -0,0 +1,79 @@
+use binaryninja::binary_reader::BinaryReader;
+use binaryninja::binary_view::BinaryViewBase;
+use binaryninja::binary_writer::BinaryWriter;
+use binaryninja::headless::Session;
+use rstest::*;
+use std::io::{Read, Seek, SeekFrom, Write};
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_binary_writer_seek(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let mut writer = BinaryWriter::new(&view);
+ let end_offset = view.len();
+
+ // Test seeking to a specific position
+ writer
+ .seek(SeekFrom::Start(0))
+ .expect("Failed to seek to start");
+ assert_eq!(writer.offset(), 0, "Writer failed to seek to the start");
+
+ writer
+ .seek(SeekFrom::End(0))
+ .expect("Failed to seek to end");
+ assert_eq!(
+ writer.offset(),
+ end_offset,
+ "Writer failed to seek to the end"
+ );
+
+ // Test relative seeking
+ writer
+ .seek(SeekFrom::Start(10))
+ .expect("Failed to seek to position 10");
+ assert_eq!(writer.offset(), 10, "Writer failed to seek to position 10");
+
+ writer
+ .seek(SeekFrom::Current(-5))
+ .expect("Failed to perform relative seek");
+ assert_eq!(
+ writer.offset(),
+ 5,
+ "Writer failed to perform relative seek correctly"
+ );
+}
+
+#[rstest]
+fn test_binary_writer_write(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let mut reader = BinaryReader::new(&view);
+ let mut writer = BinaryWriter::new(&view);
+
+ // Validate bytes are written.
+ let mut buffer = [5u8; 4];
+ let mut verify_buffer = [0u8; 4];
+ writer.write(&mut buffer).expect("Failed to write 4 bytes");
+ reader
+ .read_exact(&mut verify_buffer)
+ .expect("Failed to read 4 bytes");
+ assert_eq!(buffer, verify_buffer, "Failed to write the correct bytes");
+
+ // Test attempting to write beyond the end of the file.
+ writer
+ .seek(SeekFrom::End(0))
+ .expect("Failed to seek to end");
+ let mut eof_buffer = [0u8; 1];
+ let result = writer.write(&mut eof_buffer);
+ assert!(
+ result.is_err(),
+ "Expected an error when writing past EOF, got success instead"
+ );
+}
diff --git a/rust/tests/collaboration.rs b/rust/tests/collaboration.rs
new file mode 100644
index 00000000..48da859a
--- /dev/null
+++ b/rust/tests/collaboration.rs
@@ -0,0 +1,209 @@
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::collaboration::{NoNameChangeset, Remote, RemoteFileType, RemoteProject};
+use binaryninja::headless::Session;
+use binaryninja::symbol::{SymbolBuilder, SymbolType};
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+// TODO: This cannot run in CI, as headless does not have collaboration, we should gate this.
+
+fn temp_project_scope<T: Fn(&RemoteProject)>(remote: &Remote, cb: T) {
+ if !remote.is_connected() {
+ remote.connect().expect("Failed to connect to remote");
+ }
+ let project = remote
+ .create_project("Test Project", "Test project for test purposes")
+ .expect("Failed to create project");
+ project.open().expect("Failed to open project");
+ assert!(project.is_open(), "Project was not opened");
+ // Clear out all the possible entries. This is to insure a clean slate.
+ let files = project.files().expect("Failed to list files in project");
+ for file in &files {
+ project.delete_file(&file).expect("Failed to delete file");
+ }
+ let folders = project
+ .folders()
+ .expect("Failed to list folders in project");
+ for folder in &folders {
+ project
+ .delete_folder(&folder)
+ .expect("Failed to delete folder");
+ }
+ // Run task
+ cb(&project);
+ // Cleanup.
+ project.close();
+ assert!(!project.is_open(), "Project was not closed");
+ remote
+ .delete_project(&project)
+ .expect("Failed to delete project");
+}
+
+#[rstest]
+fn test_connection(_session: &Session) {
+ let remotes = binaryninja::collaboration::known_remotes();
+ if remotes.is_empty() {
+ eprintln!("No known remotes, skipping test...");
+ return;
+ }
+ let remote = remotes.iter().next().expect("No known remotes!");
+ assert!(remote.connect().is_ok(), "Failed to connect to remote");
+ remote
+ .disconnect()
+ .expect("Failed to disconnect from remote");
+ assert!(!remote.is_connected(), "Connection was not disconnected");
+}
+
+#[rstest]
+fn test_project_creation(_session: &Session) {
+ let remotes = binaryninja::collaboration::known_remotes();
+ if remotes.is_empty() {
+ eprintln!("No known remotes, skipping test...");
+ return;
+ }
+ let remote = remotes.iter().next().expect("No known remotes!");
+ temp_project_scope(&remote, |project| {
+ // Create the file than verify it by opening and checking contents.
+ let created_file = project
+ .create_file(
+ "test_file",
+ b"this is my file",
+ "test_file",
+ "",
+ None,
+ RemoteFileType::UnknownFileType,
+ )
+ .expect("Failed to create file in project");
+ let created_file_id = created_file.id();
+ assert_eq!(created_file.created_by(), remote.username());
+ project
+ .delete_file(&created_file)
+ .expect("Failed to delete file");
+ assert!(
+ !project
+ .get_file_by_id(created_file_id)
+ .is_ok_and(|f| f.is_some()),
+ "File was not deleted"
+ );
+
+ // Create a folder and verify it was created.
+ let created_folder = project
+ .create_folder("test_folder", "test_folder_desc", None)
+ .unwrap();
+ let created_folder_id = created_folder.id();
+ assert_eq!(created_folder.name().as_str(), "test_folder");
+ assert_eq!(created_folder.description().as_str(), "test_folder_desc");
+
+ // Create a file in said folder and verify it exists in it.
+ let created_folder_file = project
+ .create_file(
+ "test_folder_file",
+ b"this is my file",
+ "test_folder_file",
+ "",
+ Some(&created_folder),
+ RemoteFileType::UnknownFileType,
+ )
+ .expect("Failed to create file in project folder");
+ let created_folder_file_id = created_folder_file.id();
+ // Verify the file exists in the folder.
+ let check_folder_file = project
+ .get_file_by_id(created_folder_file_id)
+ .expect("Failed to get folder file by id")
+ .unwrap();
+ assert_eq!(check_folder_file.name().as_str(), "test_folder_file");
+ assert_eq!(
+ check_folder_file.folder().unwrap().unwrap().id(),
+ created_folder_id,
+ "Folder id does not match"
+ );
+ project
+ .delete_file(&created_folder_file)
+ .expect("Failed to delete file");
+
+ // Verify the folder can be deleted.
+ project
+ .delete_folder(&created_folder)
+ .expect("Failed to delete folder");
+ assert!(
+ !project
+ .get_folder_by_id(created_folder_id)
+ .is_ok_and(|f| f.is_some()),
+ "Folder was not deleted"
+ );
+ })
+}
+
+#[rstest]
+fn test_project_sync(_session: &Session) {
+ let remotes = binaryninja::collaboration::known_remotes();
+ if remotes.is_empty() {
+ eprintln!("No known remotes, skipping test...");
+ return;
+ }
+ let remote = remotes.iter().next().expect("No known remotes!");
+ temp_project_scope(&remote, |project| {
+ // Open a view so that we can upload it.
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let view_type = view.view_type();
+ // Save the view to local database so that we can upload it
+ assert!(
+ view.file().create_database(out_dir.join("atox.obj.bndb")),
+ "Failed to create local database"
+ );
+ // We should have a single snapshot.
+ assert_eq!(view.file().database().unwrap().snapshots().len(), 1);
+ // Update the entry function name.
+ let entry_function = view
+ .entry_point_function()
+ .expect("Failed to get entry point function");
+ let new_entry_func_symbol =
+ SymbolBuilder::new(SymbolType::Function, "test", entry_function.start()).create();
+ view.define_user_symbol(&new_entry_func_symbol);
+ // Verify that we modified the binary
+ assert_eq!(entry_function.symbol().raw_name().as_str(), "test");
+ // Make new snapshot.
+ assert!(view.file().save_auto_snapshot());
+ // We should have two snapshots.
+ assert_eq!(view.file().database().unwrap().snapshots().len(), 2);
+ // Upload database and verify its remote file stuff
+ let remote_file = project
+ .upload_database(&view.file(), None, NoNameChangeset)
+ .expect("Failed to upload database");
+ assert_eq!(remote_file.name().as_str(), "atox.obj");
+ assert_eq!(remote_file.created_by(), remote.username());
+ assert_eq!(
+ remote_file.file_type(),
+ RemoteFileType::BinaryViewAnalysisFileType
+ );
+ // Delete local database and download remote one to verify changes.
+ view.file().close();
+ drop(view);
+ // Verify that the remote file exists.
+ project
+ .get_file_by_id(remote_file.id())
+ .expect("Failed to get remote file by id");
+ // Download the remote database with our changes.
+ let downloaded_file = remote_file
+ .download_database(out_dir.join("downloaded_atox.obj.bndb"))
+ .expect("Failed to download database");
+ let downloaded_view = downloaded_file
+ .view_of_type(view_type)
+ .expect("Failed to open downloaded view");
+ // Verify the changes in the entry function.
+ let entry_function = downloaded_view
+ .entry_point_function()
+ .expect("Failed to get entry point function");
+ assert_eq!(entry_function.symbol().raw_name().as_str(), "test");
+ project
+ .delete_file(&remote_file)
+ .expect("Failed to delete file");
+ });
+}
diff --git a/rust/tests/component.rs b/rust/tests/component.rs
new file mode 100644
index 00000000..c57785ce
--- /dev/null
+++ b/rust/tests/component.rs
@@ -0,0 +1,26 @@
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::component::ComponentBuilder;
+use binaryninja::headless::Session;
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_component_creation(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let component = ComponentBuilder::new(view.clone()).name("test").finalize();
+ assert_eq!(component.name().as_str(), "test");
+ let root_component = view.root_component().unwrap();
+ // We should only have our component.
+ assert_eq!(root_component.components().len(), 1);
+ assert!(
+ root_component.contains_component(&component),
+ "Component not found in root component"
+ );
+}
diff --git a/rust/tests/data_buffer.rs b/rust/tests/data_buffer.rs
new file mode 100644
index 00000000..8480ae71
--- /dev/null
+++ b/rust/tests/data_buffer.rs
@@ -0,0 +1,88 @@
+use binaryninja::data_buffer::DataBuffer;
+
+const DUMMY_DATA_0: &[u8] = b"0123456789\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x09\xFF";
+const DUMMY_DATA_1: &[u8] = b"qwertyuiopasdfghjkl\xE7zxcvbnm\x00\x01\x00";
+
+#[test]
+fn get_slice() {
+ let data = DataBuffer::new(DUMMY_DATA_0).unwrap();
+ let slice = data.get_slice(9, 10).unwrap();
+ assert_eq!(slice.get_data(), &DUMMY_DATA_0[9..19]);
+}
+
+#[test]
+fn set_len_write() {
+ let mut data = DataBuffer::default();
+ assert_eq!(data.get_data(), &[]);
+ unsafe { data.set_len(DUMMY_DATA_0.len()) };
+ assert_eq!(data.len(), DUMMY_DATA_0.len());
+ let mut contents = DUMMY_DATA_0.to_vec();
+ data.set_data(&contents);
+ // modify the orinal contents, to make sure DataBuffer copied the data
+ // and is not using the original pointer
+ contents.as_mut_slice().fill(0x55);
+ drop(contents);
+ assert_eq!(data.get_data(), DUMMY_DATA_0);
+
+ // make sure the new len truncate the original data
+ unsafe { data.set_len(13) };
+ assert_eq!(data.get_data(), &DUMMY_DATA_0[..13]);
+
+ data.clear();
+ assert_eq!(data.get_data(), &[]);
+}
+
+#[test]
+fn assign_append() {
+ let mut dst = DataBuffer::new(DUMMY_DATA_0).unwrap();
+ let mut src = DataBuffer::new(DUMMY_DATA_1).unwrap();
+ DataBuffer::assign(&mut dst, &src);
+
+ assert_eq!(dst.get_data(), DUMMY_DATA_1);
+ assert_eq!(src.get_data(), DUMMY_DATA_1);
+ // overwrite the src, to make sure that src is copied to dst, and not
+ // moved into it
+ src.set_data(DUMMY_DATA_0);
+ assert_eq!(dst.get_data(), DUMMY_DATA_1);
+ assert_eq!(src.get_data(), DUMMY_DATA_0);
+
+ DataBuffer::append(&mut dst, &src);
+ let result: Vec<_> = DUMMY_DATA_1.iter().chain(DUMMY_DATA_0).copied().collect();
+ assert_eq!(dst.get_data(), &result);
+
+ assert_eq!(src.get_data(), DUMMY_DATA_0);
+ src.set_data(DUMMY_DATA_1);
+ assert_eq!(src.get_data(), DUMMY_DATA_1);
+ assert_eq!(dst.get_data(), &result);
+}
+
+#[test]
+fn to_from_formats() {
+ let data = DataBuffer::new(DUMMY_DATA_0).unwrap();
+ let escaped = data.to_escaped_string(false, false);
+ let unescaped = DataBuffer::from_escaped_string(&escaped);
+ drop(escaped);
+ let escaped_part = data.to_escaped_string(true, false);
+ let unescaped_part = DataBuffer::from_escaped_string(&escaped_part);
+ drop(escaped_part);
+
+ let part = &DUMMY_DATA_0[0..DUMMY_DATA_0
+ .iter()
+ .position(|x| *x == 0)
+ .unwrap_or(DUMMY_DATA_0.len())];
+ assert_eq!(data.get_data(), DUMMY_DATA_0);
+ assert_eq!(unescaped.get_data(), DUMMY_DATA_0);
+ assert_eq!(unescaped_part.get_data(), part);
+
+ let escaped = data.to_base64();
+ let unescaped = DataBuffer::from_base64(&escaped);
+ drop(escaped);
+ assert_eq!(data.get_data(), DUMMY_DATA_0);
+ assert_eq!(unescaped.get_data(), DUMMY_DATA_0);
+
+ let compressed = data.zlib_compress();
+ let decompressed = compressed.zlib_decompress();
+ drop(compressed);
+ assert_eq!(data.get_data(), DUMMY_DATA_0);
+ assert_eq!(decompressed.get_data(), DUMMY_DATA_0);
+}
diff --git a/rust/tests/demangler.rs b/rust/tests/demangler.rs
new file mode 100644
index 00000000..da57f67e
--- /dev/null
+++ b/rust/tests/demangler.rs
@@ -0,0 +1,85 @@
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::demangle::{
+ demangle_generic, demangle_gnu3, demangle_llvm, demangle_ms, CustomDemangler, Demangler,
+};
+use binaryninja::headless::Session;
+use binaryninja::rc::Ref;
+use binaryninja::types::{QualifiedName, Type};
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_demangler_simple(_session: &Session) {
+ let placeholder_arch = CoreArchitecture::by_name("x86").expect("x86 exists");
+ // Example LLVM-style mangled name
+ let llvm_mangled = "_Z3fooi"; // "foo(int)" in LLVM mangling
+ let llvm_demangled = demangle_llvm(llvm_mangled, true).unwrap();
+ assert_eq!(llvm_demangled, "foo(int)".into());
+
+ // Example GNU-style mangled name
+ let gnu_mangled = "_Z3bari"; // "bar(int)" in GNU mangling
+ let (gnu_demangled_name, gnu_demangled_type) =
+ demangle_gnu3(&placeholder_arch, gnu_mangled, true).unwrap();
+ assert_eq!(gnu_demangled_name, "bar".into());
+ // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1
+ assert_eq!(
+ gnu_demangled_type.unwrap().to_string(),
+ "int32_t(int32_t)".to_string()
+ );
+
+ // Example MSVC-style mangled name
+ let msvc_mangled = "?baz@@YAHH@Z"; // "int __cdecl baz(int)" in MSVC mangling
+ let (msvc_demangled_name, msvc_demangled_type) =
+ demangle_ms(&placeholder_arch, msvc_mangled, true).unwrap();
+ assert_eq!(msvc_demangled_name, "baz".into());
+ // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1
+ assert_eq!(
+ msvc_demangled_type.unwrap().to_string(),
+ "int32_t __cdecl(int32_t)".to_string()
+ );
+}
+
+#[rstest]
+fn test_custom_demangler(_session: &Session) {
+ struct TestDemangler;
+
+ impl CustomDemangler for TestDemangler {
+ fn is_mangled_string(&self, name: &str) -> bool {
+ name == "test_name" || name == "test_name2"
+ }
+
+ fn demangle(
+ &self,
+ _arch: &CoreArchitecture,
+ name: &str,
+ _view: Option<Ref<BinaryView>>,
+ ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
+ match name {
+ "test_name" => Some((QualifiedName::from(vec!["test_name"]), Some(Type::bool()))),
+ "test_name2" => Some((QualifiedName::from(vec!["test_name2", "aaa"]), None)),
+ _ => None,
+ }
+ }
+ }
+
+ Demangler::register("Test", TestDemangler);
+
+ let placeholder_arch = CoreArchitecture::by_name("x86_64").expect("x86_64 exists");
+
+ let demangled = demangle_generic(&placeholder_arch, "test_name", None, true).unwrap();
+ assert_eq!(
+ demangled,
+ (QualifiedName::from(vec!["test_name"]), Some(Type::bool()))
+ );
+ let demangled2 = demangle_generic(&placeholder_arch, "test_name2", None, true).unwrap();
+ assert_eq!(
+ demangled2,
+ (QualifiedName::from(vec!["test_name2", "aaa"]), None)
+ );
+}
diff --git a/rust/tests/high_level_il.rs b/rust/tests/high_level_il.rs
new file mode 100644
index 00000000..3a2b7209
--- /dev/null
+++ b/rust/tests/high_level_il.rs
@@ -0,0 +1,41 @@
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::headless::Session;
+use binaryninja::high_level_il::{HighLevelILInstructionKind, HighLevelInstructionIndex};
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_hlil_info(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+
+ let entry_function = view.entry_point_function().unwrap();
+ let hlil_function = entry_function.high_level_il(false).unwrap();
+ let hlil_basic_blocks = hlil_function.basic_blocks();
+ let mut hlil_basic_block_iter = hlil_basic_blocks.iter();
+ let first_basic_block = hlil_basic_block_iter.next().unwrap();
+ let mut hlil_instr_iter = first_basic_block.iter();
+
+ // NOTE: I guess this is right?
+ // 00025f10 (HLIL_BLOCK
+ // 00025f22 (HLIL_RET return (
+ // 00025f22 HLIL_CALL (HLIL_CONST_PTR.d __crt_interlocked_read_32)((HLIL_VAR.d arg1))))
+ // 00025f10 )
+ let instr_0 = hlil_instr_iter.next().unwrap();
+ assert_eq!(instr_0.expr_index, HighLevelInstructionIndex(5));
+ assert_eq!(instr_0.address, 0x00025f22);
+ println!("{:?}", instr_0.kind);
+ match instr_0.kind {
+ HighLevelILInstructionKind::Ret(op) => {
+ assert_eq!(op.first_src, 4);
+ assert_eq!(op.num_srcs, 1);
+ }
+ _ => panic!("Expected Ret"),
+ }
+}
diff --git a/rust/tests/initialization.rs b/rust/tests/initialization.rs
new file mode 100644
index 00000000..35de73ee
--- /dev/null
+++ b/rust/tests/initialization.rs
@@ -0,0 +1,37 @@
+use binaryninja::binary_view::BinaryView;
+use binaryninja::enterprise::release_license;
+use binaryninja::file_metadata::FileMetadata;
+use binaryninja::headless::{
+ init, init_with_opts, shutdown, InitializationError, InitializationOptions,
+};
+use binaryninja::set_license;
+use rstest::rstest;
+
+// NOTE: Do not add any tests here, behavior will change (i.e. a failure might pass) if we initialize
+// NOTE: The core in another test. The only test here should be `test_license_validation`.
+
+#[rstest]
+fn test_license_validation() {
+ // Release floating license if we already have one, otherwise the failure will succeed.
+ release_license();
+ // Make sure we properly report invalid license.
+ let options = InitializationOptions::default()
+ .with_checkout_license(false)
+ .with_license("blah blag");
+ match init_with_opts(options) {
+ Ok(_) => panic!("Expected license validation to fail, but it succeeded!"),
+ Err(InitializationError::InvalidLicense) => {}
+ Err(e) => panic!("Unexpected error: {:?}", e),
+ }
+ // Reset the license so that it actually can validate license.
+ set_license::<String>(None);
+ // Actually make sure we can initialize.
+ init().expect("Failed to initialize, make sure you have a license before trying to run tests!");
+ // Open an empty binary and make sure it succeeds.
+ let view = BinaryView::from_data(&FileMetadata::new(), &[]);
+ assert!(
+ view.is_ok(),
+ "Failed to open empty binary, core initialization failed!"
+ );
+ shutdown();
+}
diff --git a/rust/tests/low_level_il.rs b/rust/tests/low_level_il.rs
new file mode 100644
index 00000000..bbd31fc9
--- /dev/null
+++ b/rust/tests/low_level_il.rs
@@ -0,0 +1,230 @@
+use binaryninja::architecture::Register;
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::headless::Session;
+use binaryninja::low_level_il::expression::{
+ ExpressionHandler, LowLevelExpressionIndex, LowLevelILExpressionKind,
+};
+use binaryninja::low_level_il::instruction::{
+ InstructionHandler, LowLevelILInstructionKind, LowLevelInstructionIndex,
+};
+use binaryninja::low_level_il::{LowLevelILRegister, VisitorAction};
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_llil_info(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+
+ let entry_function = view.entry_point_function().unwrap();
+ let llil_function = entry_function.low_level_il().unwrap();
+ let llil_basic_blocks = llil_function.basic_blocks();
+ let mut llil_basic_block_iter = llil_basic_blocks.iter();
+ let first_basic_block = llil_basic_block_iter.next().unwrap();
+ let mut llil_instr_iter = first_basic_block.iter();
+
+ // 0 @ 00025f10 (LLIL_SET_REG.d edi = (LLIL_REG.d edi))
+ let instr_0 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_0.index, LowLevelInstructionIndex(0));
+ assert_eq!(instr_0.address(), 0x00025f10);
+ println!("{:?}", instr_0);
+ println!("{:?}", instr_0.kind());
+ match instr_0.kind() {
+ LowLevelILInstructionKind::SetReg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.dest_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "edi"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ assert_eq!(op.source_expr().index, LowLevelExpressionIndex(0));
+ }
+ _ => panic!("Expected SetReg"),
+ }
+ // 1 @ 00025f12 (LLIL_PUSH.d push((LLIL_REG.d ebp)))
+ let instr_1 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_1.index, LowLevelInstructionIndex(1));
+ assert_eq!(instr_1.address(), 0x00025f12);
+ println!("{:?}", instr_1.kind());
+ match instr_1.kind() {
+ LowLevelILInstructionKind::Push(op) => {
+ assert_eq!(op.size(), 4);
+ assert_eq!(op.operand().index, LowLevelExpressionIndex(2));
+ println!("{:?}", op.operand().kind());
+ match op.operand().kind() {
+ LowLevelILExpressionKind::Reg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.source_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ }
+ _ => panic!("Expected Reg"),
+ }
+ }
+ _ => panic!("Expected Push"),
+ }
+ // 2 @ 00025f13 (LLIL_SET_REG.d ebp = (LLIL_REG.d esp) {__saved_ebp})
+ let instr_2 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_2.index, LowLevelInstructionIndex(2));
+ assert_eq!(instr_2.address(), 0x00025f13);
+ println!("{:?}", instr_2.kind());
+ match instr_2.kind() {
+ LowLevelILInstructionKind::SetReg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.dest_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ assert_eq!(op.source_expr().index, LowLevelExpressionIndex(4));
+ }
+ _ => panic!("Expected SetReg"),
+ }
+ // 3 @ 00025f15 (LLIL_SET_REG.d eax = (LLIL_LOAD.d [(LLIL_ADD.d (LLIL_REG.d ebp) + (LLIL_CONST.d 8)) {arg1}].d))
+ let instr_3 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_3.index, LowLevelInstructionIndex(3));
+ assert_eq!(instr_3.address(), 0x00025f15);
+ println!("{:?}", instr_3.kind());
+ match instr_3.kind() {
+ LowLevelILInstructionKind::SetReg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.dest_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "eax"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ assert_eq!(op.source_expr().index, LowLevelExpressionIndex(9));
+ }
+ _ => panic!("Expected SetReg"),
+ }
+ // 4 @ 00025f18 (LLIL_PUSH.d push((LLIL_REG.d eax)))
+ let instr_4 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_4.index, LowLevelInstructionIndex(4));
+ assert_eq!(instr_4.address(), 0x00025f18);
+ println!("{:?}", instr_4.kind());
+ match instr_4.kind() {
+ LowLevelILInstructionKind::Push(op) => {
+ assert_eq!(op.size(), 4);
+ assert_eq!(op.operand().index, LowLevelExpressionIndex(11));
+ }
+ _ => panic!("Expected Push"),
+ }
+ // 5 @ 00025f19 (LLIL_CALL call((LLIL_CONST_PTR.d __crt_interlocked_read_32)))
+ let instr_5 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_5.index, LowLevelInstructionIndex(5));
+ assert_eq!(instr_5.address(), 0x00025f19);
+ println!("{:?}", instr_5.kind());
+ match instr_5.kind() {
+ LowLevelILInstructionKind::Call(op) => {
+ assert_eq!(op.target().index, LowLevelExpressionIndex(13));
+ }
+ _ => panic!("Expected Call"),
+ }
+ // 6 @ 00025f1e (LLIL_SET_REG.d esp = (LLIL_ADD.d (LLIL_REG.d esp) + (LLIL_CONST.d 4)))
+ let instr_6 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_6.index, LowLevelInstructionIndex(6));
+ assert_eq!(instr_6.address(), 0x00025f1e);
+ println!("{:?}", instr_6.kind());
+ match instr_6.kind() {
+ LowLevelILInstructionKind::SetReg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.dest_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "esp"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ assert_eq!(op.source_expr().index, LowLevelExpressionIndex(17));
+ }
+ _ => panic!("Expected SetReg"),
+ }
+ // 7 @ 00025f21 (LLIL_SET_REG.d ebp = (LLIL_POP.d pop))
+ let instr_7 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_7.index, LowLevelInstructionIndex(7));
+ assert_eq!(instr_7.address(), 0x00025f21);
+ println!("{:?}", instr_7.kind());
+ match instr_7.kind() {
+ LowLevelILInstructionKind::SetReg(op) => {
+ assert_eq!(op.size(), 4);
+ match op.dest_reg() {
+ LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"),
+ _ => panic!("Expected Register::ArchReg"),
+ }
+ assert_eq!(op.source_expr().index, LowLevelExpressionIndex(19));
+ }
+ _ => panic!("Expected SetReg"),
+ }
+ // 8 @ 00025f22 (LLIL_RET <return> jump((LLIL_POP.d pop)))
+ let instr_8 = llil_instr_iter.next().unwrap();
+ assert_eq!(instr_8.index, LowLevelInstructionIndex(8));
+ assert_eq!(instr_8.address(), 0x00025f22);
+ println!("{:?}", instr_8.kind());
+ match instr_8.kind() {
+ LowLevelILInstructionKind::Ret(op) => {
+ assert_eq!(op.target().index, LowLevelExpressionIndex(21));
+ }
+ _ => panic!("Expected Ret"),
+ }
+}
+
+#[rstest]
+fn test_llil_visitor(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let platform = view.default_platform().unwrap();
+
+ // Sample function: __crt_strtox::c_string_character_source<char>::validate
+ let sample_function = view.function_at(&platform, 0x2bd80).unwrap();
+ let llil_function = sample_function.low_level_il().unwrap();
+ let llil_basic_blocks = llil_function.basic_blocks();
+ let llil_basic_block_iter = llil_basic_blocks.iter();
+
+ let mut basic_blocks_visited = 0;
+ let mut instructions_visited: Vec<LowLevelInstructionIndex> = vec![];
+ let mut expressions_visited: Vec<LowLevelExpressionIndex> = vec![];
+ for basic_block in llil_basic_block_iter {
+ basic_blocks_visited += 1;
+ for instr in basic_block.iter() {
+ instructions_visited.push(instr.index);
+ expressions_visited.push(instr.expr_idx());
+ instr.visit_tree(&mut |expr| {
+ expressions_visited.push(expr.index);
+ VisitorAction::Descend
+ });
+ }
+ }
+
+ assert_eq!(basic_blocks_visited, 10);
+ // This is a flag instruction removed in LLIL.
+ instructions_visited.push(LowLevelInstructionIndex(38));
+ for instr_idx in 0..41 {
+ if instructions_visited
+ .iter()
+ .find(|x| x.0 == instr_idx)
+ .is_none()
+ {
+ panic!("Instruction with index {:?} not visited", instr_idx);
+ };
+ }
+ // These are NOP's
+ expressions_visited.push(LowLevelExpressionIndex(24));
+ expressions_visited.push(LowLevelExpressionIndex(54));
+ expressions_visited.push(LowLevelExpressionIndex(62));
+ expressions_visited.push(LowLevelExpressionIndex(87));
+ // These are some flag things
+ expressions_visited.push(LowLevelExpressionIndex(114));
+ expressions_visited.push(LowLevelExpressionIndex(115));
+ expressions_visited.push(LowLevelExpressionIndex(116));
+ expressions_visited.push(LowLevelExpressionIndex(121));
+ for expr_idx in 0..127 {
+ if expressions_visited
+ .iter()
+ .find(|x| x.0 == expr_idx)
+ .is_none()
+ {
+ panic!("Expression with index {:?} not visited", expr_idx);
+ };
+ }
+}
diff --git a/rust/tests/main_thread.rs b/rust/tests/main_thread.rs
new file mode 100644
index 00000000..f74414d5
--- /dev/null
+++ b/rust/tests/main_thread.rs
@@ -0,0 +1,29 @@
+use binaryninja::headless::Session;
+use rstest::*;
+
+// TODO: Add a test for MainThreadHandler
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_not_main_thread(_session: &Session) {
+ // We should never be the main thread.
+ assert!(!binaryninja::is_main_thread())
+}
+
+#[rstest]
+fn test_main_thread_different(_session: &Session) {
+ let calling_thread = std::thread::current();
+ binaryninja::main_thread::execute_on_main_thread_and_wait(move || {
+ let main_thread = std::thread::current();
+ assert_ne!(
+ calling_thread.id(),
+ main_thread.id(),
+ "Expected calling thread to be the different from the main thread"
+ )
+ });
+}
diff --git a/rust/tests/medium_level_il.rs b/rust/tests/medium_level_il.rs
new file mode 100644
index 00000000..7d38797b
--- /dev/null
+++ b/rust/tests/medium_level_il.rs
@@ -0,0 +1,88 @@
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::headless::Session;
+use binaryninja::medium_level_il::{MediumLevelILInstructionKind, MediumLevelInstructionIndex};
+use rstest::*;
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_mlil_info(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+
+ let entry_function = view.entry_point_function().unwrap();
+ let mlil_function = entry_function.medium_level_il().unwrap();
+ let mlil_basic_blocks = mlil_function.basic_blocks();
+ let mut mlil_basic_block_iter = mlil_basic_blocks.iter();
+ let first_basic_block = mlil_basic_block_iter.next().unwrap();
+ let mut mlil_instr_iter = first_basic_block.iter();
+
+ // 0 @ 00025f10 (MLIL_SET_VAR.d edi_1 = (MLIL_VAR.d edi))
+ let instr_0 = mlil_instr_iter.next().unwrap();
+ assert_eq!(instr_0.expr_index, MediumLevelInstructionIndex(1));
+ assert_eq!(instr_0.address, 0x00025f10);
+ println!("{:?}", instr_0.kind);
+ match instr_0.kind {
+ MediumLevelILInstructionKind::SetVar(op) => {
+ assert_eq!(op.dest.index, 524288);
+ assert_eq!(op.src, 0);
+ }
+ _ => panic!("Expected SetVar"),
+ }
+ // 1 @ 00025f15 (MLIL_SET_VAR.d eax = (MLIL_VAR.d arg1))
+ let instr_1 = mlil_instr_iter.next().unwrap();
+ assert_eq!(instr_1.expr_index, MediumLevelInstructionIndex(3));
+ assert_eq!(instr_1.address, 0x00025f15);
+ println!("{:?}", instr_1.kind);
+ match instr_1.kind {
+ MediumLevelILInstructionKind::SetVar(op) => {
+ assert_eq!(op.dest.index, 5);
+ assert_eq!(op.src, 2);
+ }
+ _ => panic!("Expected SetVar"),
+ }
+ // 2 @ 00025f18 (MLIL_SET_VAR.d var_8 = (MLIL_VAR.d eax))
+ let instr_2 = mlil_instr_iter.next().unwrap();
+ assert_eq!(instr_2.expr_index, MediumLevelInstructionIndex(5));
+ assert_eq!(instr_2.address, 0x00025f18);
+ println!("{:?}", instr_2.kind);
+ match instr_2.kind {
+ MediumLevelILInstructionKind::SetVar(op) => {
+ assert_eq!(op.dest.index, 8);
+ assert_eq!(op.src, 4);
+ }
+ _ => panic!("Expected SetVar"),
+ }
+ // 3 @ 00025f19 (MLIL_CALL eax_1 = (MLIL_CONST_PTR.d __crt_interlocked_read_32)((MLIL_VAR.d var_8)))
+ let instr_3 = mlil_instr_iter.next().unwrap();
+ assert_eq!(instr_3.expr_index, MediumLevelInstructionIndex(10));
+ assert_eq!(instr_3.address, 0x00025f19);
+ println!("{:?}", instr_3.kind);
+ match instr_3.kind {
+ MediumLevelILInstructionKind::Call(op) => {
+ assert_eq!(op.first_output, 8);
+ assert_eq!(op.num_outputs, 1);
+ assert_eq!(op.dest, 7);
+ assert_eq!(op.first_param, 9);
+ assert_eq!(op.num_params, 1);
+ }
+ _ => panic!("Expected Call"),
+ }
+ // 4 @ 00025f22 (MLIL_RET return (MLIL_VAR.d eax_1))
+ let instr_4 = mlil_instr_iter.next().unwrap();
+ assert_eq!(instr_4.expr_index, MediumLevelInstructionIndex(13));
+ assert_eq!(instr_4.address, 0x00025f22);
+ println!("{:?}", instr_4.kind);
+ match instr_4.kind {
+ MediumLevelILInstructionKind::Ret(op) => {
+ assert_eq!(op.first_operand, 12);
+ assert_eq!(op.num_operands, 1);
+ }
+ _ => panic!("Expected Ret"),
+ }
+}
diff --git a/rust/tests/platform.rs b/rust/tests/platform.rs
new file mode 100644
index 00000000..c6c861fb
--- /dev/null
+++ b/rust/tests/platform.rs
@@ -0,0 +1,32 @@
+use binaryninja::headless::Session;
+use binaryninja::platform::Platform;
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_platform_lifetime(_session: &Session) {
+ let platform_0 = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let platform_types_0 = platform_0.types();
+ let platform_1 = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let platform_types_1 = platform_1.types();
+ assert_eq!(platform_types_0.len(), platform_types_1.len());
+}
+
+#[rstest]
+fn test_platform_types(_session: &Session) {
+ let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let platform_types = platform.types();
+ // windows-x86_64 has a few thousand, not zero.
+ assert_ne!(platform_types.len(), 0);
+}
+
+#[rstest]
+fn test_platform_calling_conventions(_session: &Session) {
+ let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ assert_eq!(platform.calling_conventions().len(), 1);
+}
diff --git a/rust/tests/project.rs b/rust/tests/project.rs
new file mode 100644
index 00000000..cc41c11e
--- /dev/null
+++ b/rust/tests/project.rs
@@ -0,0 +1,350 @@
+use binaryninja::headless::Session;
+use binaryninja::metadata::Metadata;
+use binaryninja::project::Project;
+use binaryninja::rc::Ref;
+use rstest::*;
+use std::time::SystemTime;
+
+// TODO: We should use tempdir to manage the project directory.
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+fn unique_project(name: &str) -> String {
+ format!("{}/{}", std::env::temp_dir().to_str().unwrap(), name)
+}
+
+#[rstest]
+fn create_delete_empty(_session: &Session) {
+ use std::fs::canonicalize;
+
+ let project_name = "create_delete_empty_project";
+ let project_path = unique_project(project_name);
+ // create the project
+ let project = Project::create(&project_path, project_name).expect("Failed to create project");
+ project.open().unwrap();
+ assert!(project.is_open());
+
+ // check project data
+ let project_path_received = project.path();
+ assert_eq!(
+ canonicalize(&project_path).unwrap(),
+ canonicalize(project_path_received.to_string()).unwrap()
+ );
+ let project_name_received = project.name();
+ assert_eq!(project_name, project_name_received.as_str());
+
+ // close the project
+ project.close().unwrap();
+ assert!(!project.is_open());
+ drop(project);
+
+ // delete the project
+ std::fs::remove_dir_all(project_path).unwrap();
+}
+
+#[rstest]
+fn create_close_open_close(_session: &Session) {
+ let project_name = "create_close_open_close";
+ let project_path = unique_project(project_name);
+ // create the project
+ let project = Project::create(&project_path, project_name).expect("Failed to create project");
+ project.open().unwrap();
+
+ // get the project id
+ let id = project.id();
+
+ // close the project
+ project.close().unwrap();
+ drop(project);
+
+ let project = Project::open_project(&project_path).expect("Failed to open project");
+ // assert same id
+ let new_id = project.id();
+ assert_eq!(id, new_id);
+
+ // close the project
+ project.close().unwrap();
+ drop(project);
+
+ // delete the project
+ std::fs::remove_dir_all(project_path).unwrap();
+}
+
+#[rstest]
+fn modify_project(_session: &Session) {
+ let project_name = "modify_project_project";
+ let project_path = unique_project(project_name);
+ // create the project
+ let project = Project::create(&project_path, project_name).expect("Failed to create project");
+ project.open().unwrap();
+
+ // get project id
+ let id = project.id();
+
+ // create data and verify that data was created
+ let data_1: Ref<Metadata> = "data1".into();
+ let data_2: Ref<Metadata> = "data2".into();
+ assert!(project.store_metadata("key", data_1.as_ref()));
+ assert_eq!(
+ data_1.get_string().unwrap(),
+ project.query_metadata("key").get_string().unwrap()
+ );
+ project.remove_metadata("key");
+ assert!(project.store_metadata("key", data_2.as_ref()));
+ assert_eq!(
+ data_2.get_string().unwrap(),
+ project.query_metadata("key").get_string().unwrap()
+ );
+
+ // create file that will be imported to the project
+ let tmp_folder_1_name = format!(
+ "tmp_folder_{}",
+ SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_millis()
+ );
+ let tmp_folder_2_name = format!("{tmp_folder_1_name }_2");
+ let tmp_folder_1 = format!(
+ "{}/{tmp_folder_1_name}",
+ std::env::temp_dir().to_str().unwrap()
+ );
+ let tmp_folder_2 = format!(
+ "{}/{tmp_folder_2_name}",
+ std::env::temp_dir().to_str().unwrap()
+ );
+ std::fs::create_dir(&tmp_folder_1).unwrap();
+ std::fs::create_dir(&tmp_folder_2).unwrap();
+ let input_file_1 = format!("{tmp_folder_2}/input_1");
+ let input_file_2 = format!("{tmp_folder_2}/input_2");
+ let input_file_1_data = b"input_1_data";
+ let input_file_2_data = b"input_1_data";
+ std::fs::write(&input_file_1, input_file_1_data).unwrap();
+ std::fs::write(&input_file_2, input_file_2_data).unwrap();
+
+ // create and delete folders
+ let folder_1_desc = "desc_folder_1";
+ let folder_1 = project
+ .create_folder(None, "folder_1", folder_1_desc)
+ .unwrap();
+ let folder_2_desc = "AAAAA";
+ let folder_2_id = "1717416787371";
+ let folder_2 = unsafe {
+ project
+ .create_folder_unsafe(Some(&folder_1), "folder_2", folder_2_desc, folder_2_id)
+ .unwrap()
+ };
+ let folder_3_desc = ""; // TODO "çàáÁÀ";
+ let folder_3 = project
+ .create_folder_from_path(&tmp_folder_1, None, folder_3_desc)
+ .unwrap();
+ let folder_4_desc = "";
+ let _folder_4 = project
+ .create_folder_from_path_with_progress(
+ &tmp_folder_2,
+ Some(&folder_3),
+ folder_4_desc,
+ |_, _| true,
+ )
+ .unwrap();
+ let folder_5 = project
+ .create_folder(None, "deleted_folder", folder_4_desc)
+ .unwrap();
+
+ assert_eq!(project.folders().unwrap().len(), 5);
+ let last_folder = project.folder_by_id(folder_5.id()).unwrap();
+ project.delete_folder(&last_folder).unwrap();
+ assert_eq!(project.folders().unwrap().len(), 4);
+ drop(folder_5);
+
+ // create, import and delete file
+ let file_1_data = b"data_1";
+ let file_1_desc = "desc_file_1";
+ let _file_1 = project
+ .create_file(file_1_data, None, "file_1", file_1_desc)
+ .unwrap();
+ let file_2_data = b"data_2";
+ let file_2_desc = "my desc";
+ let file_2_id = "12334545";
+ let _file_2 = unsafe {
+ project.create_file_unsafe(
+ file_2_data,
+ Some(&folder_2),
+ "file_2",
+ file_2_desc,
+ file_2_id,
+ SystemTime::UNIX_EPOCH,
+ )
+ }
+ .unwrap();
+ let file_3_data = b"data\x023";
+ let file_3_desc = "!";
+ let _file_3 = project
+ .create_file_with_progress(
+ file_3_data,
+ Some(&folder_1),
+ "file_3",
+ file_3_desc,
+ |_, _| true,
+ )
+ .unwrap();
+ let file_4_time = SystemTime::now();
+ let file_4_data = b"data_4\x00_4";
+ let file_4_desc = "";
+ let file_4_id = "123123123";
+ let _file_4 = unsafe {
+ project.create_file_unsafe_with_progress(
+ file_4_data,
+ Some(&folder_3),
+ "file_4",
+ file_4_desc,
+ file_4_id,
+ file_4_time,
+ |_, _| true,
+ )
+ }
+ .unwrap();
+ let file_5_desc = "desc";
+ let _file_5 = project
+ .create_file_from_path(&input_file_1, None, "file_5", file_5_desc)
+ .unwrap();
+ let file_6_time = SystemTime::now();
+ let file_6_desc = "de";
+ let file_6_id = "90218347";
+ let _file_6 = unsafe {
+ project.create_file_from_path_unsafe(
+ &input_file_2,
+ Some(&folder_3),
+ "file_6",
+ file_6_desc,
+ file_6_id,
+ file_6_time,
+ )
+ }
+ .unwrap();
+ let file_7 = project
+ .create_file_from_path_with_progress(
+ &input_file_2,
+ Some(&folder_2),
+ "file_7",
+ "no",
+ |_, _| true,
+ )
+ .unwrap();
+ let file_8 = unsafe {
+ project.create_file_from_path_unsafe_with_progress(
+ &input_file_1,
+ None,
+ "file_7",
+ "no",
+ "92736528",
+ SystemTime::now(),
+ |_, _| true,
+ )
+ }
+ .unwrap();
+
+ assert_eq!(project.files().len(), 10);
+ let file_a = project.file_by_id(file_8.id()).unwrap();
+ let file_b = project.file_by_path(file_7.path_on_disk()).unwrap();
+ project.delete_file(&file_a);
+ project.delete_file(&file_b);
+ assert_eq!(project.files().len(), 8);
+ drop(file_8);
+ drop(file_7);
+
+ project.set_name("project_name");
+ project.set_description("project_description");
+
+ // close the project
+ project.close().unwrap();
+ drop(project);
+ drop(folder_1);
+ drop(folder_2);
+ drop(folder_3);
+
+ // reopen the project and verify the information store on it
+ let project = Project::open_project(&project_path).expect("Failed to open project");
+
+ // assert same id
+ assert_eq!(id, project.id());
+
+ // verify metadata
+ assert_eq!(
+ data_2.get_string().unwrap(),
+ project.query_metadata("key").get_string().unwrap()
+ );
+
+ // check folders
+ let folders = [
+ ("folder_1", None),
+ ("folder_2", Some(folder_2_id)),
+ (&tmp_folder_1_name, None),
+ (&tmp_folder_2_name, None),
+ ];
+ for folder in project.folders().unwrap().iter() {
+ let found = folders
+ .iter()
+ .find(|f| folder.name().as_str() == f.0)
+ .unwrap();
+ if let Some(id) = found.1 {
+ assert_eq!(folder.id().as_str(), id);
+ }
+ }
+
+ // check files
+ let files = [
+ ("file_1", &file_1_data[..], None, None),
+ ("file_2", &file_2_data[..], Some(file_2_id), None),
+ ("file_3", &file_3_data[..], None, None),
+ (
+ "file_4",
+ &file_4_data[..],
+ Some(file_4_id),
+ Some(file_4_time),
+ ),
+ ("file_5", &input_file_1_data[..], None, None),
+ (
+ "file_6",
+ &input_file_2_data[..],
+ Some(file_6_id),
+ Some(file_6_time),
+ ),
+ ("input_1", &input_file_1_data[..], None, None),
+ ("input_2", &input_file_2_data[..], None, None),
+ ];
+ for file in project.files().iter() {
+ let found = files.iter().find(|f| file.name().as_str() == f.0).unwrap();
+ if let Some(id) = found.2 {
+ assert_eq!(file.id().as_str(), id);
+ }
+ if let Some(time) = found.3 {
+ assert_eq!(
+ file.creation_time()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs(),
+ time.duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs()
+ );
+ }
+ let content = std::fs::read(file.path_on_disk().as_str()).unwrap();
+ assert_eq!(content, found.1);
+ }
+
+ assert_eq!(project.name().as_str(), "project_name");
+ assert_eq!(project.description().as_str(), "project_description");
+
+ // close the project
+ project.close().unwrap();
+
+ // delete the project
+ std::fs::remove_dir_all(project_path).unwrap();
+ std::fs::remove_dir_all(tmp_folder_1).unwrap();
+ std::fs::remove_dir_all(tmp_folder_2).unwrap();
+}
diff --git a/rust/tests/type_archive.rs b/rust/tests/type_archive.rs
new file mode 100644
index 00000000..dafb7178
--- /dev/null
+++ b/rust/tests/type_archive.rs
@@ -0,0 +1,31 @@
+use binaryninja::binary_view::BinaryView;
+use binaryninja::file_metadata::FileMetadata;
+use binaryninja::headless::Session;
+use binaryninja::platform::Platform;
+use binaryninja::rc::Ref;
+use binaryninja::type_archive::TypeArchive;
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[fixture]
+#[once]
+fn empty_view() -> Ref<BinaryView> {
+ BinaryView::from_data(&FileMetadata::new(), &[]).expect("Failed to create view")
+}
+
+#[rstest]
+fn test_create_archive(_session: &Session) {
+ let placeholder_platform = Platform::by_name("x86_64").expect("Failed to get platform");
+
+ let temp_dir = tempfile::tempdir().unwrap();
+ let type_archive_path = temp_dir.path().with_file_name("type_archive_0");
+ let type_archive = TypeArchive::create(type_archive_path, &placeholder_platform).unwrap();
+ println!("{:?}", type_archive);
+ // TODO: It seems that type archives have to be closed.
+ type_archive.close();
+}
diff --git a/rust/tests/type_container.rs b/rust/tests/type_container.rs
new file mode 100644
index 00000000..d5a72031
--- /dev/null
+++ b/rust/tests/type_container.rs
@@ -0,0 +1,137 @@
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::file_metadata::FileMetadata;
+use binaryninja::headless::Session;
+use binaryninja::platform::Platform;
+use binaryninja::rc::Ref;
+use binaryninja::types::Type;
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[fixture]
+#[once]
+fn platform() -> Ref<Platform> {
+ // TODO: Because some behavior might be platform specific we might need to move this back into each test.
+ // TODO: See test_parse_type
+ Platform::by_name("windows-x86_64").expect("windows-x86_64 exists")
+}
+
+#[fixture]
+#[once]
+fn empty_view() -> Ref<BinaryView> {
+ BinaryView::from_data(&FileMetadata::new(), &[]).expect("Failed to create view")
+}
+
+#[rstest]
+fn test_types(_session: &Session, platform: &Platform) {
+ let type_container = platform.type_container();
+ let types = type_container.types().unwrap();
+ // windows-x86_64 has a few thousand, not zero.
+ assert_eq!(types.len(), platform.types().len());
+}
+
+#[rstest]
+fn test_type_id(_session: &Session, platform: &Platform) {
+ let type_container = platform.type_container();
+ let type_ids = type_container.type_ids().unwrap();
+ let first_type_id = type_ids.iter().next().unwrap();
+ let found_type = type_container
+ .type_by_id(first_type_id)
+ .expect("Type ID not valid!");
+ let found_type_name = type_container
+ .type_name(first_type_id)
+ .expect("Type name not found for Type ID");
+ let found_type_for_type_name = type_container
+ .type_by_name(found_type_name)
+ .expect("Found type name not valid!");
+ // These _should_ be the same type.
+ assert_eq!(found_type, found_type_for_type_name);
+}
+
+#[rstest]
+fn test_add_delete_type(_session: &Session, empty_view: &BinaryView) {
+ let view_type_container = empty_view.type_container();
+ let test_type = Type::int(4, true);
+ assert!(
+ view_type_container.add_types([("mytype", test_type)]),
+ "Failed to add types!"
+ );
+ let my_type_id = view_type_container
+ .type_id("mytype")
+ .expect("mytype not found");
+ assert!(
+ view_type_container.delete_type(my_type_id),
+ "Type was deleted!"
+ );
+ // There should be no type ids if the type was actually deleted
+ assert_eq!(view_type_container.type_ids().unwrap().len(), 0)
+}
+
+#[rstest]
+fn test_immutable_container(_session: &Session, platform: &Platform) {
+ // Platform type containers are immutable, so we shouldn't be able to delete/add/rename types.
+ let plat_type_container = platform.type_container();
+ assert!(
+ !plat_type_container.is_mutable(),
+ "Platform should NOT be mutable!"
+ );
+ assert_ne!(
+ platform.types().len(),
+ 0,
+ "Something deleted all the platform types!"
+ );
+ let type_ids = plat_type_container.type_ids().unwrap();
+ let first_type_id = type_ids.iter().next().unwrap();
+ // Platform type containers are immutable so these should be false!
+ assert!(
+ !plat_type_container.delete_type(first_type_id),
+ "Type was deleted!"
+ );
+ assert!(
+ !plat_type_container.add_types([("new_type", Type::int(4, true))]),
+ "Type was added!"
+ );
+ assert!(
+ !plat_type_container.rename_type(first_type_id, "renamed_type"),
+ "Type was renamed!"
+ );
+}
+
+#[rstest]
+fn test_parse_type(_session: &Session, platform: &Platform) {
+ let type_container = platform.type_container();
+ // HANDLE will be pulled in from the platform, which is `windows-x86_64`.
+ let parsed_type = type_container
+ .parse_type_string("typedef HANDLE test;", false)
+ .map_err(|e| e.to_vec())
+ .expect("Failed to parse type");
+ assert_eq!(parsed_type.name, "test".into());
+ assert_eq!(parsed_type.ty.to_string(), "HANDLE");
+}
+
+#[rstest]
+fn test_container_lifetime(_session: &Session, platform: &Platform, empty_view: &BinaryView) {
+ let plat_type_container_dropped = platform.type_container();
+ let view_type_container_dropped = empty_view.type_container();
+ let _plat_types_dropped = plat_type_container_dropped.types();
+ let _view_types_dropped = view_type_container_dropped.types();
+ drop(plat_type_container_dropped);
+ drop(view_type_container_dropped);
+ let plat_type_container_0 = platform.type_container();
+ let view_type_container_0 = empty_view.type_container();
+ let test_type = Type::int(4, true);
+ view_type_container_0.add_types([("mytype", test_type)]);
+ let plat_types_0 = plat_type_container_0.types();
+ let view_types_0 = view_type_container_0.types();
+ let plat_type_container_1 = platform.type_container();
+ let view_type_container_1 = empty_view.type_container();
+ let plat_types_1 = plat_type_container_1.types();
+ let view_types_1 = view_type_container_1.types();
+ // If the types do not equal the container is being freed from the first set of calls.
+ assert_eq!(plat_types_0, plat_types_1);
+ assert_eq!(view_types_0, view_types_1);
+}
diff --git a/rust/tests/type_parser.rs b/rust/tests/type_parser.rs
new file mode 100644
index 00000000..5726fb0a
--- /dev/null
+++ b/rust/tests/type_parser.rs
@@ -0,0 +1,85 @@
+use binaryninja::headless::Session;
+use binaryninja::platform::Platform;
+use binaryninja::type_parser::{CoreTypeParser, TypeParser, TypeParserError};
+use binaryninja::types::Type;
+use binaryninjacore_sys::BNTypeParserErrorSeverity::ErrorSeverity;
+use rstest::*;
+
+const TEST_TYPES: &str = r#"
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef float float_t;
+typedef double double_t;
+typedef char char_t;
+typedef unsigned char uchar_t;
+typedef short short_t;
+typedef unsigned short ushort_t;
+typedef long long_t;
+typedef unsigned long ulong_t;
+typedef long long longlong_t;
+typedef unsigned long long ulonglong_t;
+typedef void* void_ptr_t;
+typedef int (*function_type)(int arg1, float arg2);
+// This should be 2 types
+typedef struct {
+ int a;
+ float b;
+} struct_type;
+"#;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_string_to_type(_session: &Session) {
+ let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let plat_type_container = platform.type_container();
+ let parser = CoreTypeParser::default();
+ let parsed_type = parser
+ .parse_type_string("int32_t", &platform, &plat_type_container)
+ .expect("Parsed int32_t");
+ let test_type = Type::int(4, true);
+ assert_eq!(test_type, parsed_type.ty);
+}
+
+#[rstest]
+fn test_string_to_types(_session: &Session) {
+ let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let plat_type_container = platform.type_container();
+ let parser = CoreTypeParser::default();
+ let parsed_type = parser
+ .parse_types_from_source(
+ TEST_TYPES,
+ "test_file.h",
+ &platform,
+ &plat_type_container,
+ &[],
+ &[],
+ "",
+ )
+ .expect("Parsed types");
+ assert_eq!(14, parsed_type.types.len());
+}
+
+#[rstest]
+fn test_parse_error(_session: &Session) {
+ let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
+ let plat_type_container = platform.type_container();
+ let parser = CoreTypeParser::default();
+ let parser_error = parser
+ .parse_type_string("AAAAA", &platform, &plat_type_container)
+ .expect_err("Parsing should fail!");
+ assert_eq!(
+ parser_error,
+ vec![TypeParserError {
+ severity: ErrorSeverity,
+ message: "a type specifier is required for all declarations".to_string(),
+ file_name: "string.hpp".to_string(),
+ line: 1,
+ column: 1
+ }]
+ );
+}
diff --git a/rust/tests/types.rs b/rust/tests/types.rs
new file mode 100644
index 00000000..f745de0c
--- /dev/null
+++ b/rust/tests/types.rs
@@ -0,0 +1,50 @@
+use binaryninja::headless::Session;
+use binaryninja::types::{MemberAccess, MemberScope, StructureBuilder, StructureMember, Type};
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_type_to_string(_session: &Session) {
+ let test_type = Type::int(4, true);
+ assert_eq!(test_type.to_string(), "int32_t".to_string());
+}
+
+#[rstest]
+fn test_structure_builder(_session: &Session) {
+ let mut builder = StructureBuilder::new();
+ builder.insert(
+ &Type::int(4, true),
+ "field_1",
+ 0,
+ false,
+ MemberAccess::PrivateAccess,
+ MemberScope::FriendScope,
+ );
+ builder.insert(
+ &Type::float(8),
+ "field_2",
+ 4,
+ false,
+ MemberAccess::PublicAccess,
+ MemberScope::NoScope,
+ );
+
+ let structure = builder.finalize();
+ let members = structure.members();
+ assert_eq!(members.len(), 2);
+ assert_eq!(
+ members[0],
+ StructureMember {
+ name: "field_1".to_string(),
+ ty: Type::int(4, true).into(),
+ offset: 0,
+ access: MemberAccess::PrivateAccess,
+ scope: MemberScope::FriendScope,
+ }
+ );
+}
diff --git a/rust/tests/worker_thread.rs b/rust/tests/worker_thread.rs
new file mode 100644
index 00000000..666e85ba
--- /dev/null
+++ b/rust/tests/worker_thread.rs
@@ -0,0 +1,45 @@
+use binaryninja::headless::Session;
+use rstest::*;
+use std::sync::{Arc, Barrier};
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_setting_worker_thread(_session: &Session) {
+ let original_count = binaryninja::worker_thread::worker_thread_count();
+ binaryninja::worker_thread::set_worker_thread_count(original_count - 1);
+ assert_eq!(
+ binaryninja::worker_thread::worker_thread_count(),
+ original_count - 1
+ );
+ binaryninja::worker_thread::set_worker_thread_count(original_count);
+ assert_eq!(
+ binaryninja::worker_thread::worker_thread_count(),
+ original_count
+ );
+}
+
+#[rstest]
+fn test_worker_thread_different(_session: &Session) {
+ let calling_thread = std::thread::current();
+
+ // We need both (2) threads to synchronize
+ let barrier = Arc::new(Barrier::new(2));
+ let barrier_clone = Arc::clone(&barrier);
+ binaryninja::worker_thread::execute_on_worker_thread("test", move || {
+ let worker_thread = std::thread::current();
+ assert_ne!(
+ calling_thread.id(),
+ worker_thread.id(),
+ "Expected calling thread to be different from the worker thread"
+ );
+ barrier_clone.wait();
+ });
+
+ // Wait until worker thread has finished.
+ barrier.wait();
+}
diff --git a/rust/tests/workflow.rs b/rust/tests/workflow.rs
new file mode 100644
index 00000000..fcede59c
--- /dev/null
+++ b/rust/tests/workflow.rs
@@ -0,0 +1,93 @@
+use binaryninja::headless::Session;
+use binaryninja::settings::Settings;
+use binaryninja::workflow::Workflow;
+use rstest::*;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+// TODO: Test running a workflow activity
+// TODO: Test activity insertion and removal
+
+#[rstest]
+fn test_workflow_clone(_session: &Session) {
+ let original_workflow = Workflow::new("core.function.baseAnalysis");
+ let mut cloned_workflow = original_workflow.clone("clone_workflow");
+
+ assert_eq!(cloned_workflow.name().as_str(), "clone_workflow");
+ assert_eq!(
+ cloned_workflow.configuration(),
+ original_workflow.configuration()
+ );
+
+ cloned_workflow = original_workflow.clone("");
+ assert_eq!(
+ cloned_workflow.name().as_str(),
+ "core.function.baseAnalysis"
+ );
+}
+
+#[rstest]
+fn test_workflow_registration(_session: &Session) {
+ // Validate NULL workflows cannot be registered
+ let workflow = Workflow::new("null");
+ assert_eq!(workflow.name().as_str(), "null");
+ assert!(!workflow.registered());
+ workflow
+ .register()
+ .expect_err("Re-registration of null is allowed");
+
+ // Validate new workflows can be registered
+ let test_workflow = Workflow::instance("core.function.baseAnalysis").clone("test_workflow");
+
+ assert_eq!(test_workflow.name().as_str(), "test_workflow");
+ assert!(!test_workflow.registered());
+ test_workflow
+ .register()
+ .expect("Failed to register workflow");
+ assert!(test_workflow.registered());
+ assert_eq!(
+ test_workflow.size(),
+ Workflow::instance("core.function.baseAnalysis").size()
+ );
+ Workflow::list()
+ .iter()
+ .find(|w| w.name() == test_workflow.name())
+ .expect("Workflow not found in list");
+ Settings::new()
+ .get_property_string_list("analysis.workflows.functionWorkflow", "enum")
+ .iter()
+ .find(|&w| w == "test_workflow")
+ .expect("Workflow not found in settings");
+
+ // Validate that registered workflows are immutable
+ let immutable_workflow = Workflow::instance("test_workflow");
+ assert!(!immutable_workflow.clear());
+ assert!(immutable_workflow.contains("core.function.advancedAnalysis"));
+ assert!(!immutable_workflow.remove("core.function.advancedAnalysis"));
+ assert!(!Workflow::instance("core.function.baseAnalysis").clear());
+
+ // Validate re-registration of baseAnalysis is not allowed
+ let base_workflow_clone = Workflow::instance("core.function.baseAnalysis").clone("");
+
+ assert_eq!(
+ base_workflow_clone.name().as_str(),
+ "core.function.baseAnalysis"
+ );
+ assert!(!base_workflow_clone.registered());
+ base_workflow_clone
+ .register()
+ .expect_err("Re-registration of baseAnalysis is allowed");
+ assert_eq!(
+ base_workflow_clone.size(),
+ Workflow::instance("core.function.baseAnalysis").size()
+ );
+ assert_eq!(
+ base_workflow_clone.configuration(),
+ Workflow::instance("core.function.baseAnalysis").configuration()
+ );
+ assert!(!base_workflow_clone.registered());
+}