From 545c0d50867efbecd20040ce8eec506bbe27fa98 Mon Sep 17 00:00:00 2001
From: hky1999 <976929993@qq.com>
Date: Fri, 30 Jan 2026 14:33:58 +0800
Subject: [PATCH] feat: add comprehensive unit tests and update CI workflows
---
.github/workflows/check.yml | 42 +++++
.github/workflows/ci.yml | 75 ---------
.github/workflows/deploy.yml | 68 +++++++++
.github/workflows/release.yml | 163 ++++++++++++++++++++
.github/workflows/test.yml | 32 ++++
src/lib.rs | 2 +
src/tests/general_registers.rs | 271 +++++++++++++++++++++++++++++++++
src/tests/guest_page_walk.rs | 149 ++++++++++++++++++
src/tests/mod.rs | 6 +
9 files changed, 733 insertions(+), 75 deletions(-)
create mode 100644 .github/workflows/check.yml
delete mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/deploy.yml
create mode 100644 .github/workflows/release.yml
create mode 100644 .github/workflows/test.yml
create mode 100644 src/tests/general_registers.rs
create mode 100644 src/tests/guest_page_walk.rs
create mode 100644 src/tests/mod.rs
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 0000000..be52ffa
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,42 @@
+name: Quality Checks
+
+on:
+ workflow_call:
+
+jobs:
+ check:
+ name: Quality Checks
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ target:
+ - x86_64-unknown-none
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@nightly
+ with:
+ toolchain: nightly-2025-05-20
+ components: rust-src, clippy, rustfmt
+ targets: ${{ matrix.target }}
+
+ - name: Check rust version
+ run: rustc --version --verbose
+
+ - name: Check code format
+ run: cargo fmt --all -- --check
+
+ - name: Build
+ run: cargo build --target ${{ matrix.target }} --all-features
+
+ - name: Run clippy
+ run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings -A clippy::new_without_default
+
+ - name: Build documentation
+ env:
+ RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
+ run: cargo doc --no-deps --target ${{ matrix.target }} --all-features
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 1e836df..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: CI
-
-on: [push, pull_request]
-
-jobs:
- ci:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- rust-toolchain: [nightly-2025-05-20, nightly]
- targets: [x86_64-unknown-none]
- steps:
- - uses: actions/checkout@v4
- - uses: dtolnay/rust-toolchain@nightly
- with:
- toolchain: ${{ matrix.rust-toolchain }}
- components: rust-src, clippy, rustfmt
- targets: ${{ matrix.targets }}
- - name: Check rust version
- run: rustc --version --verbose
- - name: Check code format
- continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }}
- run: cargo fmt --all -- --check
- - name: Clippy
- continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }}
- run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- - name: Build
- continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }}
- run: cargo build --target ${{ matrix.targets }} --all-features
-
- unit_test:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- rust-toolchain: [nightly-2025-05-20, nightly]
- targets: [x86_64-unknown-linux-gnu]
- steps:
- - uses: actions/checkout@v4
- - uses: dtolnay/rust-toolchain@nightly
- with:
- toolchain: ${{ matrix.rust-toolchain }}
- components: rust-src
- - name: Check rust version
- run: rustc --version --verbose
- - name: Unit test
- run: cargo test --target ${{ matrix.targets }} --all-features -- --nocapture
-
- doc:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- permissions:
- contents: write
- env:
- default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
- RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
- steps:
- - uses: actions/checkout@v4
- - uses: dtolnay/rust-toolchain@nightly
- with:
- toolchain: nightly-2025-05-20
- - name: Build docs
- continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
- run: |
- cargo doc --no-deps --all-features
- printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
- - name: Deploy to Github Pages
- if: ${{ github.ref == env.default-branch }}
- uses: JamesIves/github-pages-deploy-action@v4
- with:
- single-commit: true
- branch: gh-pages
- folder: target/doc
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..0abd462
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,68 @@
+name: Deploy
+
+on:
+ push:
+ branches:
+ - '**'
+ tags-ignore:
+ - 'v*'
+ - 'v*-pre.*'
+ pull_request:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: 'pages'
+ cancel-in-progress: false
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ quality-check:
+ uses: ./.github/workflows/check.yml
+
+ test:
+ uses: ./.github/workflows/test.yml
+
+ build-doc:
+ name: Build documentation
+ runs-on: ubuntu-latest
+ needs: quality-check
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@nightly
+ with:
+ toolchain: nightly-2025-05-20
+
+ - name: Build docs
+ env:
+ RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
+ run: |
+ cargo doc --no-deps --all-features
+ printf '' > target/doc/index.html
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: target/doc
+
+ deploy-doc:
+ name: Deploy to GitHub Pages
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build-doc
+ if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..a48f1ed
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,163 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+ - 'v*.*.*-pre.*'
+
+permissions:
+ contents: write
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+ RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
+
+jobs:
+ check:
+ uses: ./.github/workflows/check.yml
+
+ test:
+ uses: ./.github/workflows/test.yml
+ needs: check
+
+ create-release:
+ name: Create GitHub Release
+ runs-on: ubuntu-latest
+ needs: check
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Validate tag and branch (HEAD-based)
+ shell: bash
+ run: |
+ set -e
+
+ TAG="${{ github.ref_name }}"
+ TAG_COMMIT=$(git rev-list -n 1 "$TAG")
+
+ git fetch origin master dev || git fetch origin master
+
+ MASTER_HEAD=$(git rev-parse origin/master)
+ DEV_HEAD=$(git rev-parse origin/dev 2>/dev/null || echo "")
+
+ echo "Tag: $TAG"
+ echo "Tag commit: $TAG_COMMIT"
+ echo "master HEAD: $MASTER_HEAD"
+ echo "dev HEAD: $DEV_HEAD"
+
+ if [[ "$TAG" == *-pre.* ]]; then
+ if [ -n "$DEV_HEAD" ] && [ "$TAG_COMMIT" != "$DEV_HEAD" ]; then
+ echo "❌ prerelease tag must be created from dev HEAD"
+ exit 1
+ fi
+ echo "✅ prerelease tag validated"
+ else
+ if [ "$TAG_COMMIT" != "$MASTER_HEAD" ]; then
+ echo "❌ stable release tag must be created from master HEAD"
+ exit 1
+ fi
+ echo "✅ stable release tag validated on master"
+ fi
+
+ - name: Verify version consistency
+ run: |
+ # Extract version from git tag (remove 'v' prefix)
+ TAG_VERSION="${{ github.ref_name }}"
+ TAG_VERSION="${TAG_VERSION#v}"
+ # Extract version from Cargo.toml
+ CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/')
+ echo "Git tag version: $TAG_VERSION"
+ echo "Cargo.toml version: $CARGO_VERSION"
+ if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
+ echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)"
+ exit 1
+ fi
+ echo "Version check passed!"
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ draft: false
+ prerelease: ${{ contains(github.ref_name, '-pre.') }}
+ body: |
+ ## ${{ github.ref_name }}
+
+ - [Documentation](https://docs.rs/x86_vcpu)
+ - [crates.io](https://crates.io/crates/x86_vcpu)
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ publish-crates:
+ name: Publish to crates.io
+ runs-on: ubuntu-latest
+ needs: check
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Validate tag and branch (HEAD-based)
+ shell: bash
+ run: |
+ set -e
+
+ TAG="${{ github.ref_name }}"
+ TAG_COMMIT=$(git rev-list -n 1 "$TAG")
+
+ git fetch origin master dev || git fetch origin master
+
+ MASTER_HEAD=$(git rev-parse origin/master)
+ DEV_HEAD=$(git rev-parse origin/dev 2>/dev/null || echo "")
+
+ echo "Tag: $TAG"
+ echo "Tag commit: $TAG_COMMIT"
+ echo "master HEAD: $MASTER_HEAD"
+ echo "dev HEAD: $DEV_HEAD"
+
+ if [[ "$TAG" == *-pre.* ]]; then
+ if [ -n "$DEV_HEAD" ] && [ "$TAG_COMMIT" != "$DEV_HEAD" ]; then
+ echo "❌ prerelease tag must be created from dev HEAD"
+ exit 1
+ fi
+ echo "✅ prerelease tag validated"
+ else
+ if [ "$TAG_COMMIT" != "$MASTER_HEAD" ]; then
+ echo "❌ stable release tag must be created from master HEAD"
+ exit 1
+ fi
+ echo "✅ stable release tag validated on master"
+ fi
+
+ - name: Verify version consistency
+ run: |
+ # Extract version from git tag (remove 'v' prefix)
+ TAG_VERSION="${{ github.ref_name }}"
+ TAG_VERSION="${TAG_VERSION#v}"
+ # Extract version from Cargo.toml
+ CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/')
+ echo "Git tag version: $TAG_VERSION"
+ echo "Cargo.toml version: $CARGO_VERSION"
+ if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
+ echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)"
+ exit 1
+ fi
+ echo "Version check passed!"
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@nightly
+ with:
+ toolchain: nightly-2025-05-20
+
+ - name: Dry run publish
+ run: cargo publish --dry-run
+
+ - name: Publish to crates.io
+ run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..8f1ee42
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,32 @@
+name: Test
+
+on:
+ workflow_call:
+
+jobs:
+ test:
+ name: Unit Test
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ rust-toolchain: [nightly-2025-05-20, nightly]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@nightly
+ with:
+ toolchain: ${{ matrix.rust-toolchain }}
+ components: rust-src
+
+ - name: Check rust version
+ run: rustc --version --verbose
+
+ - name: Run unit tests
+ run: cargo test --target x86_64-unknown-linux-gnu --all-features -- --nocapture
+
+ - name: Run doc tests
+ run: cargo test --doc --all-features
diff --git a/src/lib.rs b/src/lib.rs
index 79d3e6c..7956715 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,6 +9,8 @@ extern crate alloc;
#[cfg(test)]
mod test_utils;
+#[cfg(test)]
+mod tests;
pub(crate) mod msr;
#[macro_use]
diff --git a/src/tests/general_registers.rs b/src/tests/general_registers.rs
new file mode 100644
index 0000000..a942fcc
--- /dev/null
+++ b/src/tests/general_registers.rs
@@ -0,0 +1,271 @@
+//! Tests for GeneralRegisters structure.
+
+use crate::regs::GeneralRegisters;
+
+#[test]
+fn test_general_registers_default() {
+ let regs = GeneralRegisters::default();
+ assert_eq!(regs.rax, 0);
+ assert_eq!(regs.rcx, 0);
+ assert_eq!(regs.rdx, 0);
+ assert_eq!(regs.rbx, 0);
+ assert_eq!(regs.rbp, 0);
+ assert_eq!(regs.rsi, 0);
+ assert_eq!(regs.rdi, 0);
+ assert_eq!(regs.r8, 0);
+ assert_eq!(regs.r9, 0);
+ assert_eq!(regs.r10, 0);
+ assert_eq!(regs.r11, 0);
+ assert_eq!(regs.r12, 0);
+ assert_eq!(regs.r13, 0);
+ assert_eq!(regs.r14, 0);
+ assert_eq!(regs.r15, 0);
+}
+
+#[test]
+fn test_general_registers_clone() {
+ let mut regs1 = GeneralRegisters::default();
+ regs1.rax = 0x1234;
+ regs1.rbx = 0x5678;
+
+ let regs2 = regs1.clone();
+ assert_eq!(regs1.rax, regs2.rax);
+ assert_eq!(regs1.rbx, regs2.rbx);
+}
+
+#[test]
+fn test_general_registers_copy() {
+ let mut regs1 = GeneralRegisters::default();
+ regs1.rax = 0xabcd;
+
+ let regs2 = regs1; // Copy
+ assert_eq!(regs1.rax, regs2.rax);
+}
+
+#[test]
+fn test_general_registers_eq() {
+ let regs1 = GeneralRegisters::default();
+ let regs2 = GeneralRegisters::default();
+ assert_eq!(regs1, regs2);
+
+ let mut regs3 = GeneralRegisters::default();
+ regs3.rax = 1;
+ assert_ne!(regs1, regs3);
+}
+
+#[test]
+fn test_register_names() {
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[0], "rax");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[1], "rcx");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[2], "rdx");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[3], "rbx");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[4], "rsp");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[5], "rbp");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[6], "rsi");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[7], "rdi");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[8], "r8");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[9], "r9");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[10], "r10");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[11], "r11");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[12], "r12");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[13], "r13");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[14], "r14");
+ assert_eq!(GeneralRegisters::REGISTER_NAMES[15], "r15");
+}
+
+#[test]
+fn test_register_name_function() {
+ assert_eq!(GeneralRegisters::register_name(0), "rax");
+ assert_eq!(GeneralRegisters::register_name(8), "r8");
+ assert_eq!(GeneralRegisters::register_name(15), "r15");
+}
+
+#[test]
+fn test_get_reg_of_index() {
+ let mut regs = GeneralRegisters::default();
+ regs.rax = 0x100;
+ regs.rcx = 0x101;
+ regs.rdx = 0x102;
+ regs.rbx = 0x103;
+ regs.rbp = 0x105;
+ regs.rsi = 0x106;
+ regs.rdi = 0x107;
+ regs.r8 = 0x108;
+ regs.r9 = 0x109;
+ regs.r10 = 0x10a;
+ regs.r11 = 0x10b;
+ regs.r12 = 0x10c;
+ regs.r13 = 0x10d;
+ regs.r14 = 0x10e;
+ regs.r15 = 0x10f;
+
+ assert_eq!(regs.get_reg_of_index(0), 0x100);
+ assert_eq!(regs.get_reg_of_index(1), 0x101);
+ assert_eq!(regs.get_reg_of_index(2), 0x102);
+ assert_eq!(regs.get_reg_of_index(3), 0x103);
+ assert_eq!(regs.get_reg_of_index(5), 0x105);
+ assert_eq!(regs.get_reg_of_index(6), 0x106);
+ assert_eq!(regs.get_reg_of_index(7), 0x107);
+ assert_eq!(regs.get_reg_of_index(8), 0x108);
+ assert_eq!(regs.get_reg_of_index(9), 0x109);
+ assert_eq!(regs.get_reg_of_index(10), 0x10a);
+ assert_eq!(regs.get_reg_of_index(11), 0x10b);
+ assert_eq!(regs.get_reg_of_index(12), 0x10c);
+ assert_eq!(regs.get_reg_of_index(13), 0x10d);
+ assert_eq!(regs.get_reg_of_index(14), 0x10e);
+ assert_eq!(regs.get_reg_of_index(15), 0x10f);
+}
+
+#[test]
+fn test_set_reg_of_index() {
+ let mut regs = GeneralRegisters::default();
+
+ regs.set_reg_of_index(0, 0x1000);
+ assert_eq!(regs.rax, 0x1000);
+
+ regs.set_reg_of_index(8, 0x8000);
+ assert_eq!(regs.r8, 0x8000);
+
+ regs.set_reg_of_index(15, 0xf000);
+ assert_eq!(regs.r15, 0xf000);
+}
+
+#[test]
+fn test_get_edx_eax() {
+ let mut regs = GeneralRegisters::default();
+ regs.rax = 0x12345678;
+ regs.rdx = 0xabcdef00;
+
+ let combined = regs.get_edx_eax();
+ // edx:eax = (edx << 32) | eax
+ assert_eq!(combined, 0xabcdef0012345678);
+}
+
+#[test]
+fn test_32bit_register_accessors() {
+ let mut regs = GeneralRegisters::default();
+
+ // Set 32-bit value - should clear upper 32 bits
+ regs.rax = 0xffffffff_ffffffff;
+ regs.set_eax(0x12345678);
+ assert_eq!(regs.rax, 0x12345678);
+ assert_eq!(regs.eax(), 0x12345678);
+}
+
+#[test]
+fn test_16bit_register_accessors() {
+ let mut regs = GeneralRegisters::default();
+
+ // Set 16-bit value - should NOT clear other bits
+ regs.rax = 0xfedcba9876543210;
+ regs.set_ax(0xabcd);
+ assert_eq!(regs.rax, 0xfedcba987654abcd);
+ assert_eq!(regs.ax(), 0xabcd);
+}
+
+#[test]
+fn test_8bit_register_accessors() {
+ let mut regs = GeneralRegisters::default();
+
+ // Set 8-bit low value - should NOT clear other bits
+ regs.rax = 0xfedcba9876543210;
+ regs.set_al(0xef);
+ assert_eq!(regs.rax, 0xfedcba98765432ef);
+ assert_eq!(regs.al(), 0xef);
+}
+
+#[test]
+fn test_8bit_high_register_accessors() {
+ let mut regs = GeneralRegisters::default();
+
+ // Set 8-bit high value (ah, bh, ch, dh)
+ regs.rax = 0xfedcba9876543210;
+ regs.set_ah(0xab);
+ assert_eq!(regs.rax, 0xfedcba987654ab10);
+ assert_eq!(regs.ah(), 0xab);
+}
+
+#[test]
+fn test_debug_format() {
+ let mut regs = GeneralRegisters::default();
+ regs.rax = 0x1234;
+ let debug_str = alloc::format!("{:?}", regs);
+ // Just verify the debug string is not empty and contains the struct name
+ assert!(!debug_str.is_empty());
+ assert!(debug_str.contains("GeneralRegisters"));
+}
+
+#[test]
+fn test_all_r8_to_r15_registers() {
+ let mut regs = GeneralRegisters::default();
+
+ // Test 64-bit access
+ regs.r8 = 0x0808080808080808;
+ regs.r9 = 0x0909090909090909;
+ regs.r10 = 0x1010101010101010;
+ regs.r11 = 0x1111111111111111;
+ regs.r12 = 0x1212121212121212;
+ regs.r13 = 0x1313131313131313;
+ regs.r14 = 0x1414141414141414;
+ regs.r15 = 0x1515151515151515;
+
+ // Test 32-bit access
+ assert_eq!(regs.r8d(), 0x08080808);
+ assert_eq!(regs.r9d(), 0x09090909);
+ assert_eq!(regs.r10d(), 0x10101010);
+ assert_eq!(regs.r11d(), 0x11111111);
+ assert_eq!(regs.r12d(), 0x12121212);
+ assert_eq!(regs.r13d(), 0x13131313);
+ assert_eq!(regs.r14d(), 0x14141414);
+ assert_eq!(regs.r15d(), 0x15151515);
+
+ // Test 16-bit access
+ assert_eq!(regs.r8w(), 0x0808);
+ assert_eq!(regs.r9w(), 0x0909);
+ assert_eq!(regs.r10w(), 0x1010);
+ assert_eq!(regs.r11w(), 0x1111);
+ assert_eq!(regs.r12w(), 0x1212);
+ assert_eq!(regs.r13w(), 0x1313);
+ assert_eq!(regs.r14w(), 0x1414);
+ assert_eq!(regs.r15w(), 0x1515);
+
+ // Test 8-bit access
+ assert_eq!(regs.r8b(), 0x08);
+ assert_eq!(regs.r9b(), 0x09);
+ assert_eq!(regs.r10b(), 0x10);
+ assert_eq!(regs.r11b(), 0x11);
+ assert_eq!(regs.r12b(), 0x12);
+ assert_eq!(regs.r13b(), 0x13);
+ assert_eq!(regs.r14b(), 0x14);
+ assert_eq!(regs.r15b(), 0x15);
+}
+
+#[test]
+#[should_panic(expected = "Illegal index")]
+fn test_get_reg_invalid_index_high() {
+ let regs = GeneralRegisters::default();
+ let _ = regs.get_reg_of_index(16);
+}
+
+#[test]
+#[should_panic(expected = "Illegal index")]
+fn test_get_reg_invalid_index_rsp() {
+ let regs = GeneralRegisters::default();
+ // Index 4 is RSP which is unused
+ let _ = regs.get_reg_of_index(4);
+}
+
+#[test]
+#[should_panic(expected = "Illegal index")]
+fn test_set_reg_invalid_index_high() {
+ let mut regs = GeneralRegisters::default();
+ regs.set_reg_of_index(16, 0);
+}
+
+#[test]
+#[should_panic(expected = "Illegal index")]
+fn test_set_reg_invalid_index_rsp() {
+ let mut regs = GeneralRegisters::default();
+ // Index 4 is RSP which is unused
+ regs.set_reg_of_index(4, 0);
+}
diff --git a/src/tests/guest_page_walk.rs b/src/tests/guest_page_walk.rs
new file mode 100644
index 0000000..6f498e7
--- /dev/null
+++ b/src/tests/guest_page_walk.rs
@@ -0,0 +1,149 @@
+//! Tests for GuestPageWalkInfo structure.
+
+use crate::ept::GuestPageWalkInfo;
+
+#[test]
+fn test_guest_page_walk_info_debug() {
+ let info = GuestPageWalkInfo {
+ top_entry: 0x1000,
+ level: 4,
+ width: 64,
+ is_user_mode_access: false,
+ is_write_access: true,
+ is_inst_fetch: false,
+ pse: true,
+ wp: true,
+ nxe: true,
+ is_smap_on: false,
+ is_smep_on: false,
+ };
+
+ let debug_str = alloc::format!("{:?}", info);
+ assert!(debug_str.contains("GuestPageWalkInfo"));
+ assert!(debug_str.contains("top_entry"));
+ assert!(debug_str.contains("level"));
+}
+
+#[test]
+fn test_guest_page_walk_info_fields() {
+ let info = GuestPageWalkInfo {
+ top_entry: 0x12345000,
+ level: 4,
+ width: 48,
+ is_user_mode_access: true,
+ is_write_access: false,
+ is_inst_fetch: true,
+ pse: false,
+ wp: false,
+ nxe: false,
+ is_smap_on: true,
+ is_smep_on: true,
+ };
+
+ assert_eq!(info.top_entry, 0x12345000);
+ assert_eq!(info.level, 4);
+ assert_eq!(info.width, 48);
+ assert!(info.is_user_mode_access);
+ assert!(!info.is_write_access);
+ assert!(info.is_inst_fetch);
+ assert!(!info.pse);
+ assert!(!info.wp);
+ assert!(!info.nxe);
+ assert!(info.is_smap_on);
+ assert!(info.is_smep_on);
+}
+
+#[test]
+fn test_guest_page_walk_info_4level_paging() {
+ // Test typical 4-level paging configuration
+ let info = GuestPageWalkInfo {
+ top_entry: 0x100000,
+ level: 4,
+ width: 48,
+ is_user_mode_access: false,
+ is_write_access: false,
+ is_inst_fetch: false,
+ pse: true, // Always true for 4-level paging
+ wp: true,
+ nxe: true,
+ is_smap_on: false,
+ is_smep_on: false,
+ };
+
+ assert_eq!(info.level, 4);
+ assert!(info.pse); // PSE is always true for 4-level paging
+}
+
+#[test]
+fn test_guest_page_walk_info_pae_paging() {
+ // Test PAE paging configuration
+ let info = GuestPageWalkInfo {
+ top_entry: 0x200000,
+ level: 3,
+ width: 52,
+ is_user_mode_access: false,
+ is_write_access: false,
+ is_inst_fetch: false,
+ pse: true, // Always true for PAE paging
+ wp: false,
+ nxe: false,
+ is_smap_on: false,
+ is_smep_on: false,
+ };
+
+ assert_eq!(info.level, 3);
+}
+
+#[test]
+fn test_guest_page_walk_info_32bit_paging() {
+ // Test 32-bit paging configuration
+ let info = GuestPageWalkInfo {
+ top_entry: 0x300000,
+ level: 2,
+ width: 32,
+ is_user_mode_access: true,
+ is_write_access: true,
+ is_inst_fetch: false,
+ pse: false, // CR4.PSE dependent for 32-bit paging
+ wp: true,
+ nxe: false, // NXE not available in 32-bit paging
+ is_smap_on: false,
+ is_smep_on: false,
+ };
+
+ assert_eq!(info.level, 2);
+ assert_eq!(info.width, 32);
+}
+
+#[test]
+fn test_guest_page_walk_info_access_combinations() {
+ // Test different access combinations
+ let combinations = [
+ (false, false, false), // Read, supervisor, no fetch
+ (true, false, false), // Read, user, no fetch
+ (false, true, false), // Write, supervisor, no fetch
+ (true, true, false), // Write, user, no fetch
+ (false, false, true), // Fetch, supervisor
+ (true, false, true), // Fetch, user
+ ];
+
+ for (user, write, fetch) in combinations {
+ let info = GuestPageWalkInfo {
+ top_entry: 0x1000,
+ level: 4,
+ width: 48,
+ is_user_mode_access: user,
+ is_write_access: write,
+ is_inst_fetch: fetch,
+ pse: true,
+ wp: true,
+ nxe: true,
+ is_smap_on: false,
+ is_smep_on: false,
+ };
+
+ assert_eq!(info.is_user_mode_access, user);
+ assert_eq!(info.is_write_access, write);
+ assert_eq!(info.is_inst_fetch, fetch);
+ }
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
new file mode 100644
index 0000000..dd45c92
--- /dev/null
+++ b/src/tests/mod.rs
@@ -0,0 +1,6 @@
+//! Unit tests for x86_vcpu crate.
+//!
+//! This module contains comprehensive unit tests for the x86 virtual CPU implementation.
+
+mod general_registers;
+mod guest_page_walk;