mirror of
https://github.com/HDFGroup/hdf5.git
synced 2026-09-25 04:09:44 +03:00
FFM build requires Java 25, Jextract 25. Generates FFM bindings during configure. JNI is default when the requirements are not met or can be forced. Presets added for maven and FFM - JNI is default selection. Enhanced Maven options will work with either JNI or FFM New Workflows for testing and maven uploads. Extensive documentation changes for java.
488 lines
19 KiB
Java
488 lines
19 KiB
Java
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* Copyright by The HDF Group. *
|
|
* All rights reserved. *
|
|
* *
|
|
* This file is part of HDF5. The full HDF5 copyright notice, including *
|
|
* terms governing use, modification, and redistribution, is contained in *
|
|
* the LICENSE file, which can be found at the root of the source code *
|
|
* distribution tree, or in https://www.hdfgroup.org/licenses. *
|
|
* If you do not have access to either file, you may request a copy from *
|
|
* help@hdfgroup.org. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
package jtest;
|
|
|
|
import static org.junit.Assert.*;
|
|
|
|
import static jtest.FfmTestSupport.*;
|
|
|
|
import java.lang.foreign.Arena;
|
|
import java.lang.foreign.MemorySegment;
|
|
|
|
import org.hdfgroup.javahdf5.hdf5_h;
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Rule;
|
|
import org.junit.Test;
|
|
import org.junit.rules.TestName;
|
|
|
|
/**
|
|
* FFM-only tests for HDF5 Error (H5E) operations.
|
|
*
|
|
* This test class uses direct FFM bindings without the hdf.hdf5lib wrapper layer.
|
|
*/
|
|
public class TestH5Effm {
|
|
@Rule
|
|
public TestName testname = new TestName();
|
|
|
|
long hdf_java_classid = hdf5_h.H5I_INVALID_HID();
|
|
long current_stackid = hdf5_h.H5I_INVALID_HID();
|
|
|
|
@Before
|
|
public void setupErrorClass()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Register custom error class
|
|
MemorySegment cls_name = stringToSegment(arena, "HDF-Java-FFM-Error");
|
|
MemorySegment lib_name = stringToSegment(arena, "hdf-java-ffm");
|
|
MemorySegment version = stringToSegment(arena, "2.0");
|
|
|
|
hdf_java_classid = hdf5_h.H5Eregister_class(cls_name, lib_name, version);
|
|
assertTrue("H5Eregister_class failed", isValidId(hdf_java_classid));
|
|
|
|
// Get current error stack
|
|
current_stackid = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("H5Eget_current_stack failed", isValidId(current_stackid));
|
|
}
|
|
}
|
|
|
|
@After
|
|
public void cleanup()
|
|
{
|
|
if (isValidId(hdf_java_classid)) {
|
|
int result = hdf5_h.H5Eunregister_class(hdf_java_classid);
|
|
assertTrue("H5Eunregister_class failed", isSuccess(result));
|
|
hdf_java_classid = hdf5_h.H5I_INVALID_HID();
|
|
}
|
|
|
|
if (isValidId(current_stackid)) {
|
|
closeQuietly(current_stackid, hdf5_h::H5Eclose_stack);
|
|
current_stackid = hdf5_h.H5I_INVALID_HID();
|
|
}
|
|
|
|
System.out.println();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 1: Error Class and Message Operations
|
|
// ============================================================================
|
|
|
|
@Test
|
|
public void testH5Eregister_unregister_class()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Register a new error class
|
|
MemorySegment cls_name = stringToSegment(arena, "Test-Error-Class");
|
|
MemorySegment lib_name = stringToSegment(arena, "test-lib");
|
|
MemorySegment version = stringToSegment(arena, "1.0");
|
|
|
|
long class_id = hdf5_h.H5Eregister_class(cls_name, lib_name, version);
|
|
assertTrue("H5Eregister_class failed", isValidId(class_id));
|
|
|
|
// Get class name back
|
|
long name_size = hdf5_h.H5Eget_class_name(class_id, MemorySegment.NULL, 0);
|
|
assertTrue("H5Eget_class_name size query failed", name_size > 0);
|
|
|
|
MemorySegment nameBuffer = arena.allocate(name_size + 1);
|
|
long actual_size = hdf5_h.H5Eget_class_name(class_id, nameBuffer, name_size + 1);
|
|
assertTrue("H5Eget_class_name failed", actual_size > 0);
|
|
|
|
String retrieved_name = nameBuffer.getString(0);
|
|
assertEquals("Class name should match", "Test-Error-Class", retrieved_name);
|
|
|
|
// Unregister class
|
|
int result = hdf5_h.H5Eunregister_class(class_id);
|
|
assertTrue("H5Eunregister_class failed", isSuccess(result));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Ecreate_close_msg()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Create major error message
|
|
MemorySegment major_msg = stringToSegment(arena, "Test major error");
|
|
long maj_err_id = hdf5_h.H5Ecreate_msg(hdf_java_classid, hdf5_h.H5E_MAJOR(), major_msg);
|
|
assertTrue("H5Ecreate_msg major failed", isValidId(maj_err_id));
|
|
|
|
// Get message back
|
|
MemorySegment typePtr = allocateInt(arena);
|
|
long msg_size = hdf5_h.H5Eget_msg(maj_err_id, typePtr, MemorySegment.NULL, 0);
|
|
assertTrue("H5Eget_msg size query failed", msg_size > 0);
|
|
|
|
MemorySegment msgBuffer = arena.allocate(msg_size + 1);
|
|
long actual_size = hdf5_h.H5Eget_msg(maj_err_id, typePtr, msgBuffer, msg_size + 1);
|
|
assertTrue("H5Eget_msg failed", actual_size > 0);
|
|
|
|
String retrieved_msg = msgBuffer.getString(0);
|
|
assertEquals("Message should match", "Test major error", retrieved_msg);
|
|
|
|
int msg_type = getInt(typePtr);
|
|
assertEquals("Message type should be MAJOR", hdf5_h.H5E_MAJOR(), msg_type);
|
|
|
|
// Close message
|
|
int result = hdf5_h.H5Eclose_msg(maj_err_id);
|
|
assertTrue("H5Eclose_msg failed", isSuccess(result));
|
|
|
|
// Create minor error message
|
|
MemorySegment minor_msg = stringToSegment(arena, "Test minor error");
|
|
long min_err_id = hdf5_h.H5Ecreate_msg(hdf_java_classid, hdf5_h.H5E_MINOR(), minor_msg);
|
|
assertTrue("H5Ecreate_msg minor failed", isValidId(min_err_id));
|
|
|
|
// Close minor message
|
|
result = hdf5_h.H5Eclose_msg(min_err_id);
|
|
assertTrue("H5Eclose_msg minor failed", isSuccess(result));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_major_minor()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Trigger an error to get real error numbers
|
|
MemorySegment filename = stringToSegment(arena, "nonexistent_for_error_test.h5");
|
|
long file_id = hdf5_h.H5Fopen(filename, hdf5_h.H5F_ACC_RDONLY(), hdf5_h.H5P_DEFAULT());
|
|
if (isValidId(file_id)) {
|
|
hdf5_h.H5Fclose(file_id);
|
|
}
|
|
|
|
// Get error stack with actual errors
|
|
long stack_id = hdf5_h.H5Eget_current_stack();
|
|
if (isValidId(stack_id)) {
|
|
long num_errors = hdf5_h.H5Eget_num(stack_id);
|
|
|
|
if (num_errors > 0) {
|
|
// We have errors, test H5Eget_major/minor with error numbers from stack
|
|
// For now, just verify the functions can be called with value 0
|
|
// (H5Eget_major/minor require actual error numbers which are internal)
|
|
|
|
// Test that functions return non-null for valid inputs
|
|
// Note: We can't easily get actual major/minor error numbers without
|
|
// walking the stack, which requires H5Ewalk callback (not yet implemented)
|
|
}
|
|
|
|
hdf5_h.H5Eclose_stack(stack_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 2: Error Stack Operations
|
|
// ============================================================================
|
|
|
|
@Test
|
|
public void testH5Ecreate_close_stack()
|
|
{
|
|
// Create new error stack
|
|
long stack_id = hdf5_h.H5Ecreate_stack();
|
|
assertTrue("H5Ecreate_stack failed", isValidId(stack_id));
|
|
|
|
// Verify it's empty (new stack has no errors)
|
|
long num_errors = hdf5_h.H5Eget_num(stack_id);
|
|
assertEquals("New stack should have 0 errors", 0L, num_errors);
|
|
|
|
// Close stack
|
|
int result = hdf5_h.H5Eclose_stack(stack_id);
|
|
assertTrue("H5Eclose_stack failed", isSuccess(result));
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_current_set_stack()
|
|
{
|
|
// Get current stack
|
|
long stack1 = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("H5Eget_current_stack failed", isValidId(stack1));
|
|
|
|
// Create a new empty stack
|
|
long stack2 = hdf5_h.H5Ecreate_stack();
|
|
assertTrue("H5Ecreate_stack failed", isValidId(stack2));
|
|
|
|
// Set new stack as current
|
|
int result = hdf5_h.H5Eset_current_stack(stack2);
|
|
assertTrue("H5Eset_current_stack failed", isSuccess(result));
|
|
|
|
// Note: Setting current stack transfers ownership, so we don't close stack2
|
|
// Restore original stack
|
|
result = hdf5_h.H5Eset_current_stack(stack1);
|
|
assertTrue("H5Eset_current_stack restore failed", isSuccess(result));
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_num_pop()
|
|
{
|
|
// Trigger an error by trying to open non-existent file
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
MemorySegment filename = stringToSegment(arena, "nonexistent_file_for_test.h5");
|
|
long file_id = hdf5_h.H5Fopen(filename, hdf5_h.H5F_ACC_RDONLY(), hdf5_h.H5P_DEFAULT());
|
|
// File open will fail, but that's expected
|
|
if (isValidId(file_id)) {
|
|
hdf5_h.H5Fclose(file_id);
|
|
}
|
|
}
|
|
|
|
// Get current error stack (should have errors from failed open)
|
|
long stack_id = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("H5Eget_current_stack failed", isValidId(stack_id));
|
|
|
|
// Get number of errors
|
|
long num_errors = hdf5_h.H5Eget_num(stack_id);
|
|
assertTrue("Stack should have errors after failed open", num_errors > 0);
|
|
|
|
long saved_num = num_errors;
|
|
|
|
// Pop one error
|
|
int result = hdf5_h.H5Epop(stack_id, 1);
|
|
assertTrue("H5Epop failed", isSuccess(result));
|
|
|
|
// Verify count decreased
|
|
num_errors = hdf5_h.H5Eget_num(stack_id);
|
|
assertEquals("Error count should decrease by 1", saved_num - 1, num_errors);
|
|
|
|
// Clean up
|
|
hdf5_h.H5Eclose_stack(stack_id);
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eappend_stack()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Create two stacks with errors
|
|
// Stack 1: trigger error
|
|
MemorySegment filename1 = stringToSegment(arena, "nonexistent1.h5");
|
|
long file_id1 = hdf5_h.H5Fopen(filename1, hdf5_h.H5F_ACC_RDONLY(), hdf5_h.H5P_DEFAULT());
|
|
if (isValidId(file_id1))
|
|
hdf5_h.H5Fclose(file_id1);
|
|
|
|
long stack1 = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("H5Eget_current_stack stack1 failed", isValidId(stack1));
|
|
|
|
long num1 = hdf5_h.H5Eget_num(stack1);
|
|
|
|
// Stack 2: trigger another error
|
|
MemorySegment filename2 = stringToSegment(arena, "nonexistent2.h5");
|
|
long file_id2 = hdf5_h.H5Fopen(filename2, hdf5_h.H5F_ACC_RDONLY(), hdf5_h.H5P_DEFAULT());
|
|
if (isValidId(file_id2))
|
|
hdf5_h.H5Fclose(file_id2);
|
|
|
|
long stack2 = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("H5Eget_current_stack stack2 failed", isValidId(stack2));
|
|
|
|
long num2 = hdf5_h.H5Eget_num(stack2);
|
|
|
|
// Append stack2 to stack1 (close_source = false)
|
|
int result = hdf5_h.H5Eappend_stack(stack1, stack2, false);
|
|
assertTrue("H5Eappend_stack failed", isSuccess(result));
|
|
|
|
// Verify stack1 now has combined errors
|
|
long combined_num = hdf5_h.H5Eget_num(stack1);
|
|
assertTrue("Combined stack should have more errors", combined_num >= num1);
|
|
|
|
// Clean up (both stacks need closing since close_source was false)
|
|
hdf5_h.H5Eclose_stack(stack1);
|
|
hdf5_h.H5Eclose_stack(stack2);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 3: Stack Pause/Resume Operations
|
|
// ============================================================================
|
|
|
|
@Test
|
|
public void testH5Epause_resume_stack()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Create a stack
|
|
long stack_id = hdf5_h.H5Ecreate_stack();
|
|
assertTrue("H5Ecreate_stack failed", isValidId(stack_id));
|
|
|
|
// Check if paused (should not be paused initially)
|
|
MemorySegment isPausedPtr = allocateInt(arena); // Using int for boolean
|
|
int result = hdf5_h.H5Eis_paused(stack_id, isPausedPtr);
|
|
assertTrue("H5Eis_paused failed", isSuccess(result));
|
|
|
|
boolean is_paused = (getInt(isPausedPtr) != 0);
|
|
assertFalse("New stack should not be paused", is_paused);
|
|
|
|
// Pause the stack
|
|
result = hdf5_h.H5Epause_stack(stack_id);
|
|
assertTrue("H5Epause_stack failed", isSuccess(result));
|
|
|
|
// Verify it's paused
|
|
result = hdf5_h.H5Eis_paused(stack_id, isPausedPtr);
|
|
assertTrue("H5Eis_paused after pause failed", isSuccess(result));
|
|
|
|
is_paused = (getInt(isPausedPtr) != 0);
|
|
assertTrue("Stack should be paused", is_paused);
|
|
|
|
// Resume the stack
|
|
result = hdf5_h.H5Eresume_stack(stack_id);
|
|
assertTrue("H5Eresume_stack failed", isSuccess(result));
|
|
|
|
// Verify it's resumed (not paused)
|
|
result = hdf5_h.H5Eis_paused(stack_id, isPausedPtr);
|
|
assertTrue("H5Eis_paused after resume failed", isSuccess(result));
|
|
|
|
is_paused = (getInt(isPausedPtr) != 0);
|
|
assertFalse("Stack should not be paused after resume", is_paused);
|
|
|
|
// Clean up
|
|
hdf5_h.H5Eclose_stack(stack_id);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 4: Comprehensive Workflow Tests
|
|
// ============================================================================
|
|
|
|
@Test
|
|
public void testH5E_complete_workflow()
|
|
{
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// 1. Register error class
|
|
MemorySegment cls_name = stringToSegment(arena, "Workflow-Test-Class");
|
|
MemorySegment lib_name = stringToSegment(arena, "workflow-lib");
|
|
MemorySegment version = stringToSegment(arena, "1.0");
|
|
|
|
long class_id = hdf5_h.H5Eregister_class(cls_name, lib_name, version);
|
|
assertTrue("Register class failed", isValidId(class_id));
|
|
|
|
// 2. Create error messages
|
|
MemorySegment major_msg = stringToSegment(arena, "Workflow major error");
|
|
long maj_id = hdf5_h.H5Ecreate_msg(class_id, hdf5_h.H5E_MAJOR(), major_msg);
|
|
assertTrue("Create major message failed", isValidId(maj_id));
|
|
|
|
MemorySegment minor_msg = stringToSegment(arena, "Workflow minor error");
|
|
long min_id = hdf5_h.H5Ecreate_msg(class_id, hdf5_h.H5E_MINOR(), minor_msg);
|
|
assertTrue("Create minor message failed", isValidId(min_id));
|
|
|
|
// 3. Create and manipulate error stack
|
|
long stack_id = hdf5_h.H5Ecreate_stack();
|
|
assertTrue("Create stack failed", isValidId(stack_id));
|
|
|
|
// Verify stack starts empty
|
|
long num = hdf5_h.H5Eget_num(stack_id);
|
|
assertEquals("New stack should be empty", 0L, num);
|
|
|
|
// 4. Get class name
|
|
long name_size = hdf5_h.H5Eget_class_name(class_id, MemorySegment.NULL, 0);
|
|
assertTrue("Get class name size failed", name_size > 0);
|
|
|
|
// 5. Clean up in reverse order
|
|
hdf5_h.H5Eclose_stack(stack_id);
|
|
hdf5_h.H5Eclose_msg(min_id);
|
|
hdf5_h.H5Eclose_msg(maj_id);
|
|
|
|
int result = hdf5_h.H5Eunregister_class(class_id);
|
|
assertTrue("Unregister class failed", isSuccess(result));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_num()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Get number of errors on default stack
|
|
long num_errors = hdf5_h.H5Eget_num(hdf5_h.H5E_DEFAULT());
|
|
assertTrue("Number of errors should be >= 0", num_errors >= 0);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eclear()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Clear error stack
|
|
int result = hdf5_h.H5Eclear2(hdf5_h.H5E_DEFAULT());
|
|
assertTrue("H5Eclear2 failed", isSuccess(result));
|
|
|
|
// Verify stack is empty
|
|
long num_errors = hdf5_h.H5Eget_num(hdf5_h.H5E_DEFAULT());
|
|
assertEquals("Stack should be empty", 0L, num_errors);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_current_stack()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Get current error stack
|
|
long stack_id = hdf5_h.H5Eget_current_stack();
|
|
assertTrue("Stack ID should be valid", isValidId(stack_id));
|
|
|
|
// Close stack
|
|
int result = hdf5_h.H5Eclose_stack(stack_id);
|
|
assertTrue("H5Eclose_stack failed", isSuccess(result));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Epop()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Clear stack first
|
|
hdf5_h.H5Eclear2(hdf5_h.H5E_DEFAULT());
|
|
|
|
// Pop errors (should succeed even if empty)
|
|
int result = hdf5_h.H5Epop(hdf5_h.H5E_DEFAULT(), 1);
|
|
assertTrue("H5Epop should succeed", isSuccess(result));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testH5Eget_msg()
|
|
{
|
|
System.out.print(testname.getMethodName());
|
|
|
|
try (Arena arena = Arena.ofConfined()) {
|
|
// Create error class
|
|
MemorySegment cls_name = stringToSegment(arena, "TestClass");
|
|
MemorySegment lib_name = stringToSegment(arena, "TestLib");
|
|
MemorySegment version = stringToSegment(arena, "1.0");
|
|
|
|
long class_id = hdf5_h.H5Eregister_class(cls_name, lib_name, version);
|
|
assertTrue("Register class failed", isValidId(class_id));
|
|
|
|
// Create error message
|
|
MemorySegment msg_text = stringToSegment(arena, "Test error message");
|
|
long msg_id = hdf5_h.H5Ecreate_msg(class_id, hdf5_h.H5E_MAJOR(), msg_text);
|
|
assertTrue("Create message failed", isValidId(msg_id));
|
|
|
|
// Get message
|
|
MemorySegment type = allocateIntArray(arena, 1);
|
|
long msg_size = hdf5_h.H5Eget_msg(msg_id, type, MemorySegment.NULL, 0);
|
|
assertTrue("Message size should be > 0", msg_size > 0);
|
|
|
|
MemorySegment msg_buf = arena.allocate(msg_size + 1);
|
|
long actual_size = hdf5_h.H5Eget_msg(msg_id, type, msg_buf, msg_size + 1);
|
|
assertTrue("Actual size should match", actual_size > 0);
|
|
|
|
String retrieved_msg = segmentToString(msg_buf);
|
|
assertEquals("Message should match", "Test error message", retrieved_msg);
|
|
|
|
// Cleanup
|
|
hdf5_h.H5Eclose_msg(msg_id);
|
|
hdf5_h.H5Eunregister_class(class_id);
|
|
}
|
|
}
|
|
}
|