tests: Close CUDA dma-buf FDs after MR registration

Close the caller-owned CUDA DMA-BUF file descriptors after MR
registration in the test resources, including when registration fails.
Create the DMAHandle before exporting the dma-buf, so an unsupported
DMAHandle skips the test without leaving a descriptor open.

Fixes: 5a2c958a31e7 ("tests: Add RDMA traffic over CUDA DMABUF")
Fixes: 5e4b222e6dd6 ("tests: Add tests for MREX and DMAHandle")
Fixes: 3ef8fd711e4a ("tests: Add tests to cover new DV dmabuf API")
Signed-off-by: Dezhen Lu <dezhen.lu@student.uni-tuebingen.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/tests/test_cuda_dmabuf.py b/tests/test_cuda_dmabuf.py
index 24e487b..6cc76f6 100644
--- a/tests/test_cuda_dmabuf.py
+++ b/tests/test_cuda_dmabuf.py
@@ -3,6 +3,7 @@
 
 import unittest
 import errno
+import os
 
 from pyverbs.pyverbs_error import PyverbsRDMAError
 from tests.base import RCResources, RDMATestCase
@@ -59,6 +60,8 @@
             if ex.error_code == errno.EOPNOTSUPP:
                 raise unittest.SkipTest('Registering DMABUF with DmaBufMR is not supported')
             raise ex
+        finally:
+            os.close(dmabuf_fd)
 
     def create_qp_attr(self):
         qp_attr = QPAttr(port_num=self.ib_port)
@@ -75,9 +78,12 @@
     @u.skip_unsupported
     def create_mr(self):
         dmabuf_fd = self.dmabuf_cuda_init()
-        self.mr = MREx(self.pd, self.msg_size, self.mr_access,
-                       comp_mask=ibv_mr_init_attr_mask.IBV_REG_MR_MASK_FD | \
-                       ibv_mr_init_attr_mask.IBV_REG_MR_MASK_IOVA, fd=dmabuf_fd, iova=0)
+        try:
+            self.mr = MREx(self.pd, self.msg_size, self.mr_access,
+                           comp_mask=ibv_mr_init_attr_mask.IBV_REG_MR_MASK_FD | \
+                           ibv_mr_init_attr_mask.IBV_REG_MR_MASK_IOVA, fd=dmabuf_fd, iova=0)
+        finally:
+            os.close(dmabuf_fd)
 
 
 @cu.set_mem_io_cuda_methods
@@ -86,7 +92,6 @@
 
     @u.skip_unsupported
     def create_mr(self):
-        dmabuf_fd = self.dmabuf_cuda_init()
         try:
             attr = u.create_dmah_init_attr()
             dmah = DMAHandle(self.ctx, attr)
@@ -95,9 +100,13 @@
                 raise unittest.SkipTest('DMAHandle is not supported')
             raise ex
 
-        self.mr = MREx(self.pd, self.msg_size, self.mr_access,
-                       comp_mask=ibv_mr_init_attr_mask.IBV_REG_MR_MASK_FD | \
-                       ibv_mr_init_attr_mask.IBV_REG_MR_MASK_IOVA, fd=dmabuf_fd, iova=0, dmah=dmah)
+        dmabuf_fd = self.dmabuf_cuda_init()
+        try:
+            self.mr = MREx(self.pd, self.msg_size, self.mr_access,
+                           comp_mask=ibv_mr_init_attr_mask.IBV_REG_MR_MASK_FD | \
+                           ibv_mr_init_attr_mask.IBV_REG_MR_MASK_IOVA, fd=dmabuf_fd, iova=0, dmah=dmah)
+        finally:
+            os.close(dmabuf_fd)
 
 @cu.set_init_cuda_methods
 class DmabufCudaTest(RDMATestCase):
diff --git a/tests/test_mlx5_dmabuf.py b/tests/test_mlx5_dmabuf.py
index 825e9d9..621be7a 100644
--- a/tests/test_mlx5_dmabuf.py
+++ b/tests/test_mlx5_dmabuf.py
@@ -2,6 +2,7 @@
 # Copyright (c) 2024 Nvidia Inc. All rights reserved. See COPYING file
 
 from os import strerror
+import os
 import unittest
 import errno
 
@@ -86,6 +87,8 @@
             if ex.error_code == errno.EOPNOTSUPP:
                 raise unittest.SkipTest('Registering DV DMABUF MR is not supported')
             raise ex
+        finally:
+            os.close(dmabuf_fd)
 
     def create_qp_attr(self):
         qp_attr = QPAttr(port_num=self.ib_port)