This is an automated email from the ASF dual-hosted git repository. guanmingchiu pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/mahout.git
commit 6585ae14afbbb8a46d9b05e0b3c497fd3a0a5489 Author: KUAN-HAO HUANG <[email protected]> AuthorDate: Sun Nov 30 02:22:39 2025 +0800 [QDP] add integration test and introduction (#666) --- qdp/docs/test/README.md | 46 +++++++++++++ qdp/qdp-core/tests/api_workflow.rs | 74 +++++++++++++++++++++ qdp/qdp-core/tests/memory_safety.rs | 128 ++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) diff --git a/qdp/docs/test/README.md b/qdp/docs/test/README.md new file mode 100644 index 000000000..ac7d16994 --- /dev/null +++ b/qdp/docs/test/README.md @@ -0,0 +1,46 @@ +# QDP Core Test Suite + +Unit tests for QDP core library covering input validation, API workflows, and memory safety. + +## Test Files + +### `validation.rs` - Input Validation + +- Invalid encoder strategy rejection +- Qubit size validation (mismatch, zero, max limit 30) +- Empty and zero-norm data rejection +- Error type formatting +- Non-Linux platform graceful failure + +### `api_workflow.rs` - API Workflow + +- Engine initialization +- Amplitude encoding with DLPack pointer management + +### `memory_safety.rs` - Memory Safety + +- Memory leak detection (100 encode/free cycles) +- Concurrent state vector management +- DLPack tensor metadata validation + +### `common/mod.rs` - Test Utilities + +- `create_test_data(size)`: Generates normalized test data + +## Running Tests + +```bash +# Run all tests +cargo test --package qdp-core + +# Run specific test file +cargo test --package qdp-core --test validation +cargo test --package qdp-core --test api_workflow +cargo test --package qdp-core --test memory_safety +``` + +## Requirements + +- Linux OS (tests skip on other platforms) +- CUDA-capable GPU (tests skip if unavailable) +- Rust toolchain with CUDA support diff --git a/qdp/qdp-core/tests/api_workflow.rs b/qdp/qdp-core/tests/api_workflow.rs new file mode 100644 index 000000000..f88b2eb83 --- /dev/null +++ b/qdp/qdp-core/tests/api_workflow.rs @@ -0,0 +1,74 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// API workflow tests: Engine initialization and encoding + +use qdp_core::QdpEngine; + +mod common; + +#[test] +#[cfg(target_os = "linux")] +fn test_engine_initialization() { + println!("Testing QdpEngine initialization..."); + + let engine = QdpEngine::new(0); + + match engine { + Ok(_) => println!("PASS: Engine initialized successfully"), + Err(e) => { + println!("SKIP: CUDA initialization failed (no GPU available): {:?}", e); + return; + } + } + + assert!(engine.is_ok()); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_amplitude_encoding_workflow() { + println!("Testing amplitude encoding workflow..."); + + let engine = match QdpEngine::new(0) { + Ok(e) => e, + Err(_) => { + println!("SKIP: No GPU available"); + return; + } + }; + + let data = common::create_test_data(1024); + println!("Created test data: {} elements", data.len()); + + let result = engine.encode(&data, 10, "amplitude"); + assert!(result.is_ok(), "Encoding should succeed"); + + let dlpack_ptr = result.unwrap(); + assert!(!dlpack_ptr.is_null(), "DLPack pointer should not be null"); + println!("PASS: Encoding succeeded, DLPack pointer valid"); + + // Simulate PyTorch behavior: manually call deleter to free GPU memory + unsafe { + let managed = &mut *dlpack_ptr; + assert!(managed.deleter.is_some(), "Deleter must be present"); + + println!("Calling deleter to free GPU memory"); + let deleter = managed.deleter.take().expect("Deleter function pointer is missing!"); + deleter(dlpack_ptr); + println!("PASS: Memory freed successfully"); + } +} diff --git a/qdp/qdp-core/tests/memory_safety.rs b/qdp/qdp-core/tests/memory_safety.rs new file mode 100644 index 000000000..833190c48 --- /dev/null +++ b/qdp/qdp-core/tests/memory_safety.rs @@ -0,0 +1,128 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Memory safety tests: DLPack lifecycle, RAII, Arc reference counting + +use qdp_core::QdpEngine; + +mod common; + +#[test] +#[cfg(target_os = "linux")] +fn test_memory_pressure() { + println!("Testing memory pressure (leak detection)"); + println!("Running 100 iterations of encode + free"); + + let engine = match QdpEngine::new(0) { + Ok(e) => e, + Err(_) => { + println!("SKIP: No GPU available"); + return; + } + }; + + let data = common::create_test_data(1024); + + for i in 0..100 { + let ptr = engine.encode(&data, 10, "amplitude") + .expect("Encoding should succeed"); + + unsafe { + let managed = &mut *ptr; + let deleter = managed.deleter.take().expect("Deleter missing in pressure test!"); + deleter(ptr); + } + + if (i + 1) % 25 == 0 { + println!("Completed {} iterations", i + 1); + } + } + + println!("PASS: Memory pressure test completed (no OOM, no leaks)"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_multiple_concurrent_states() { + println!("Testing multiple concurrent state vectors..."); + + let engine = match QdpEngine::new(0) { + Ok(e) => e, + Err(_) => return, + }; + + let data1 = common::create_test_data(256); + let data2 = common::create_test_data(512); + let data3 = common::create_test_data(1024); + + let ptr1 = engine.encode(&data1, 8, "amplitude").unwrap(); + let ptr2 = engine.encode(&data2, 9, "amplitude").unwrap(); + let ptr3 = engine.encode(&data3, 10, "amplitude").unwrap(); + + println!("PASS: Created 3 concurrent state vectors"); + + // Free in different order to test Arc reference counting + unsafe { + println!("Freeing in order: 2, 1, 3"); + (&mut *ptr2).deleter.take().expect("Deleter missing!")(ptr2); + (&mut *ptr1).deleter.take().expect("Deleter missing!")(ptr1); + (&mut *ptr3).deleter.take().expect("Deleter missing!")(ptr3); + } + + println!("PASS: All states freed successfully"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_dlpack_tensor_metadata() { + println!("Testing DLPack tensor metadata..."); + + let engine = match QdpEngine::new(0) { + Ok(e) => e, + Err(_) => return, + }; + + let data = common::create_test_data(1024); + let ptr = engine.encode(&data, 10, "amplitude").unwrap(); + + unsafe { + let managed = &mut *ptr; + let tensor = &managed.dl_tensor; + + assert_eq!(tensor.ndim, 1, "Should be 1D tensor"); + assert!(!tensor.data.is_null(), "Data pointer should be valid"); + assert!(!tensor.shape.is_null(), "Shape pointer should be valid"); + assert!(!tensor.strides.is_null(), "Strides pointer should be valid"); + + let shape = *tensor.shape; + assert_eq!(shape, 1024, "Shape should be 1024 (2^10)"); + + let stride = *tensor.strides; + assert_eq!(stride, 1, "Stride for 1D contiguous array should be 1"); + + assert_eq!(tensor.dtype.code, 5, "Should be complex type (code=5)"); + assert_eq!(tensor.dtype.bits, 128, "Should be 128 bits (2x64-bit floats)"); + + println!("PASS: DLPack metadata verified"); + println!(" ndim: {}", tensor.ndim); + println!(" shape: {}", shape); + println!(" stride: {}", stride); + println!(" dtype: code={}, bits={}", tensor.dtype.code, tensor.dtype.bits); + + let deleter = managed.deleter.take().expect("Deleter missing in metadata test!"); + deleter(ptr); + } +}
