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);
+    }
+}

Reply via email to