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;