This is an automated email from the ASF dual-hosted git repository.
guanmingchiu pushed a commit to branch dev-qdp
in repository https://gitbox.apache.org/repos/asf/mahout.git
The following commit(s) were added to refs/heads/dev-qdp by this push:
new 7f7891733 [QDP] add integration test and introduction (#666)
7f7891733 is described below
commit 7f7891733b305c7d86cb571c5a07e5d2c8099bf1
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);
+ }
+}