mirror of
https://github.com/KhronosGroup/OpenCL-CTS.git
synced 2026-03-19 06:09:01 +00:00
Fix errors in test_vulkan (#2183)
This fixes three problems in `test_vulkan`:
1. One negative test is violating the OpenCL specification. A call to
`clEnqueue{Wait,Signal}SemaphoresKHR` with an invalid semaphore should
return `CL_INVALID_SEMAPHORE_KHR` and not `CL_INVALID_VALUE`.
>
[CL_INVALID_SEMAPHORE_KHR](https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#CL_INVALID_SEMAPHORE_KHR)
if any of the semaphore objects specified by sema_objects is not valid.
2. When populating the list of supported external memory handle types
for Vulkan, the types are unconditionally added to the list, without
checking if the device supports it or not, this fix is namely for
`VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD`.
3. If a device does not support an optional extension (that is required
for a test), the test should skip, not throw an exception and fail. A
test failure should be reserved for the cases where a device claims
support for an extension but then fails to execute the test correctly.
---------
Signed-off-by: Ahmed Hesham <ahmed.hesham@arm.com>
This commit is contained in:
@@ -111,6 +111,7 @@ const char *IGetErrorString(int clErrorCode)
|
|||||||
return "CL_INVALID_SYNC_POINT_WAIT_LIST_KHR";
|
return "CL_INVALID_SYNC_POINT_WAIT_LIST_KHR";
|
||||||
case CL_INVALID_COMMAND_BUFFER_KHR:
|
case CL_INVALID_COMMAND_BUFFER_KHR:
|
||||||
return "CL_INVALID_COMMAND_BUFFER_KHR";
|
return "CL_INVALID_COMMAND_BUFFER_KHR";
|
||||||
|
case CL_INVALID_SEMAPHORE_KHR: return "CL_INVALID_SEMAPHORE_KHR";
|
||||||
default: return "(unknown)";
|
default: return "(unknown)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,8 @@
|
|||||||
VK_FUNC_DECL(vkGetPhysicalDeviceSurfaceSupportKHR) \
|
VK_FUNC_DECL(vkGetPhysicalDeviceSurfaceSupportKHR) \
|
||||||
VK_FUNC_DECL(vkImportSemaphoreFdKHR) \
|
VK_FUNC_DECL(vkImportSemaphoreFdKHR) \
|
||||||
VK_FUNC_DECL(vkGetPhysicalDeviceExternalSemaphorePropertiesKHR) \
|
VK_FUNC_DECL(vkGetPhysicalDeviceExternalSemaphorePropertiesKHR) \
|
||||||
VK_FUNC_DECL(vkGetImageSubresourceLayout)
|
VK_FUNC_DECL(vkGetImageSubresourceLayout) \
|
||||||
|
VK_FUNC_DECL(vkGetPhysicalDeviceExternalBufferProperties)
|
||||||
#define VK_WINDOWS_FUNC_LIST \
|
#define VK_WINDOWS_FUNC_LIST \
|
||||||
VK_FUNC_DECL(vkGetMemoryWin32HandleKHR) \
|
VK_FUNC_DECL(vkGetMemoryWin32HandleKHR) \
|
||||||
VK_FUNC_DECL(vkGetSemaphoreWin32HandleKHR) \
|
VK_FUNC_DECL(vkGetSemaphoreWin32HandleKHR) \
|
||||||
@@ -202,5 +203,7 @@
|
|||||||
#define vkGetSemaphoreWin32HandleKHR _vkGetSemaphoreWin32HandleKHR
|
#define vkGetSemaphoreWin32HandleKHR _vkGetSemaphoreWin32HandleKHR
|
||||||
#define vkImportSemaphoreWin32HandleKHR _vkImportSemaphoreWin32HandleKHR
|
#define vkImportSemaphoreWin32HandleKHR _vkImportSemaphoreWin32HandleKHR
|
||||||
#define vkGetImageSubresourceLayout _vkGetImageSubresourceLayout
|
#define vkGetImageSubresourceLayout _vkGetImageSubresourceLayout
|
||||||
|
#define vkGetPhysicalDeviceExternalBufferProperties \
|
||||||
|
_vkGetPhysicalDeviceExternalBufferProperties
|
||||||
|
|
||||||
#endif //_vulkan_api_list_hpp_
|
#endif //_vulkan_api_list_hpp_
|
||||||
|
|||||||
@@ -225,7 +225,8 @@ getDefaultVulkanQueueFamilyToQueueCountMap()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList()
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
const VulkanPhysicalDevice &physical_device)
|
||||||
{
|
{
|
||||||
std::vector<VulkanExternalMemoryHandleType> externalMemoryHandleTypeList;
|
std::vector<VulkanExternalMemoryHandleType> externalMemoryHandleTypeList;
|
||||||
|
|
||||||
@@ -238,8 +239,23 @@ getSupportedVulkanExternalMemoryHandleTypeList()
|
|||||||
externalMemoryHandleTypeList.push_back(
|
externalMemoryHandleTypeList.push_back(
|
||||||
VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT);
|
VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT);
|
||||||
#else
|
#else
|
||||||
externalMemoryHandleTypeList.push_back(
|
VkPhysicalDeviceExternalBufferInfo buffer_info = {};
|
||||||
VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD);
|
buffer_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO;
|
||||||
|
buffer_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
|
||||||
|
|
||||||
|
VkExternalBufferProperties buffer_properties = {};
|
||||||
|
buffer_properties.sType = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES;
|
||||||
|
|
||||||
|
vkGetPhysicalDeviceExternalBufferProperties(physical_device, &buffer_info,
|
||||||
|
&buffer_properties);
|
||||||
|
|
||||||
|
if (buffer_properties.externalMemoryProperties.externalMemoryFeatures
|
||||||
|
& VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT)
|
||||||
|
{
|
||||||
|
|
||||||
|
externalMemoryHandleTypeList.push_back(
|
||||||
|
VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return externalMemoryHandleTypeList;
|
return externalMemoryHandleTypeList;
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ const VulkanDescriptorSetLayoutList& getEmptyVulkanDescriptorSetLayoutList();
|
|||||||
const VulkanQueueFamilyToQueueCountMap&
|
const VulkanQueueFamilyToQueueCountMap&
|
||||||
getDefaultVulkanQueueFamilyToQueueCountMap();
|
getDefaultVulkanQueueFamilyToQueueCountMap();
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
const VulkanPhysicalDevice& physical_device);
|
||||||
const std::vector<VulkanExternalSemaphoreHandleType>
|
const std::vector<VulkanExternalSemaphoreHandleType>
|
||||||
getSupportedVulkanExternalSemaphoreHandleTypeList(const VulkanDevice& vkDevice);
|
getSupportedVulkanExternalSemaphoreHandleTypeList(const VulkanDevice& vkDevice);
|
||||||
std::vector<VulkanExternalSemaphoreHandleType>
|
std::vector<VulkanExternalSemaphoreHandleType>
|
||||||
|
|||||||
@@ -61,14 +61,15 @@ struct ConsistencyExternalBufferTest : public VulkanTestBase
|
|||||||
#else
|
#else
|
||||||
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
log_info("Device does not support "
|
||||||
"Device does not support "
|
"cl_khr_external_memory_opaque_fd extension \n");
|
||||||
"cl_khr_external_memory_opaque_fd extension \n");
|
return TEST_SKIPPED_ITSELF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList()[0];
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice->getPhysicalDevice())[0];
|
||||||
|
|
||||||
VulkanBuffer vkDummyBuffer(*vkDevice, 4 * 1024,
|
VulkanBuffer vkDummyBuffer(*vkDevice, 4 * 1024,
|
||||||
vkExternalMemoryHandleType);
|
vkExternalMemoryHandleType);
|
||||||
@@ -200,9 +201,9 @@ struct ConsistencyExternalImageTest : public VulkanTestBase
|
|||||||
#else
|
#else
|
||||||
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
||||||
{
|
{
|
||||||
test_fail(
|
log_info("Device does not support cl_khr_external_memory_opaque_fd "
|
||||||
"Device does not support cl_khr_external_memory_opaque_fd "
|
"extension \n");
|
||||||
"extension \n");
|
return TEST_SKIPPED_ITSELF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
uint32_t width = 256;
|
uint32_t width = 256;
|
||||||
@@ -212,7 +213,8 @@ struct ConsistencyExternalImageTest : public VulkanTestBase
|
|||||||
cl_image_format img_format = { 0 };
|
cl_image_format img_format = { 0 };
|
||||||
|
|
||||||
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList()[0];
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice->getPhysicalDevice())[0];
|
||||||
|
|
||||||
VulkanImageTiling vulkanImageTiling =
|
VulkanImageTiling vulkanImageTiling =
|
||||||
vkClExternalMemoryHandleTilingAssumption(
|
vkClExternalMemoryHandleTilingAssumption(
|
||||||
@@ -355,9 +357,9 @@ struct ConsistencyExternalSemaphoreTest : public VulkanTestBase
|
|||||||
#else
|
#else
|
||||||
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
||||||
{
|
{
|
||||||
test_fail(
|
log_info("Device does not support cl_khr_external_memory_opaque_fd "
|
||||||
"Device does not support cl_khr_external_memory_opaque_fd "
|
"extension \n");
|
||||||
"extension \n");
|
return TEST_SKIPPED_ITSELF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -484,18 +486,18 @@ struct ConsistencyExternalSemaphoreTest : public VulkanTestBase
|
|||||||
// Pass invalid semaphore object to wait
|
// Pass invalid semaphore object to wait
|
||||||
errNum = clEnqueueWaitSemaphoresKHRptr(queue, 1, NULL, NULL, 0,
|
errNum = clEnqueueWaitSemaphoresKHRptr(queue, 1, NULL, NULL, 0,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
test_failure_error(
|
test_failure_error(errNum, CL_INVALID_SEMAPHORE_KHR,
|
||||||
errNum, CL_INVALID_VALUE,
|
"clEnqueueWaitSemaphoresKHR fails with "
|
||||||
"clEnqueueWaitSemaphoresKHR fails with CL_INVALID_VALUE "
|
"CL_INVALID_SEMAPHORE_KHR "
|
||||||
"when invalid semaphore object is passed");
|
"when invalid semaphore object is passed");
|
||||||
|
|
||||||
// Pass invalid semaphore object to signal
|
// Pass invalid semaphore object to signal
|
||||||
errNum = clEnqueueSignalSemaphoresKHRptr(queue, 1, NULL, NULL, 0,
|
errNum = clEnqueueSignalSemaphoresKHRptr(queue, 1, NULL, NULL, 0,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
test_failure_error(
|
test_failure_error(errNum, CL_INVALID_SEMAPHORE_KHR,
|
||||||
errNum, CL_INVALID_VALUE,
|
"clEnqueueSignalSemaphoresKHR fails with "
|
||||||
"clEnqueueSignalSemaphoresKHR fails with CL_INVALID_VALUE"
|
"CL_INVALID_SEMAPHORE_KHR"
|
||||||
"when invalid semaphore object is passed");
|
"when invalid semaphore object is passed");
|
||||||
|
|
||||||
// Create two semaphore objects
|
// Create two semaphore objects
|
||||||
clVk2Clsemaphore = clCreateSemaphoreWithPropertiesKHRptr(
|
clVk2Clsemaphore = clCreateSemaphoreWithPropertiesKHRptr(
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ struct ConsistencyExternalImage1DTest : public VulkanTestBase
|
|||||||
#else
|
#else
|
||||||
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
log_info("Device does not support "
|
||||||
"Device does not support cl_khr_external_memory_opaque_fd "
|
"cl_khr_external_memory_opaque_fd extension \n");
|
||||||
"extension \n");
|
return TEST_SKIPPED_ITSELF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
uint32_t width = 256;
|
uint32_t width = 256;
|
||||||
@@ -70,7 +70,8 @@ struct ConsistencyExternalImage1DTest : public VulkanTestBase
|
|||||||
cl_image_format img_format = { 0 };
|
cl_image_format img_format = { 0 };
|
||||||
|
|
||||||
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList()[0];
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice->getPhysicalDevice())[0];
|
||||||
|
|
||||||
VulkanImageTiling vulkanImageTiling =
|
VulkanImageTiling vulkanImageTiling =
|
||||||
vkClExternalMemoryHandleTilingAssumption(
|
vkClExternalMemoryHandleTilingAssumption(
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ struct ConsistencyExternalImage3DTest : public VulkanTestBase
|
|||||||
#else
|
#else
|
||||||
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
if (!is_extension_available(device, "cl_khr_external_memory_opaque_fd"))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
log_info("Device does not support "
|
||||||
"Device does not support cl_khr_external_memory_opaque_fd "
|
"cl_khr_external_memory_opaque_fd extension \n");
|
||||||
"extension \n");
|
return TEST_SKIPPED_ITSELF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
uint32_t width = 256;
|
uint32_t width = 256;
|
||||||
@@ -73,7 +73,8 @@ struct ConsistencyExternalImage3DTest : public VulkanTestBase
|
|||||||
cl_image_format img_format = { 0 };
|
cl_image_format img_format = { 0 };
|
||||||
|
|
||||||
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
VulkanExternalMemoryHandleType vkExternalMemoryHandleType =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList()[0];
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice->getPhysicalDevice())[0];
|
||||||
|
|
||||||
VulkanImageTiling vulkanImageTiling =
|
VulkanImageTiling vulkanImageTiling =
|
||||||
vkClExternalMemoryHandleTilingAssumption(
|
vkClExternalMemoryHandleTilingAssumption(
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ int run_test_with_two_queue(
|
|||||||
|
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
std::shared_ptr<VulkanFence> fence = nullptr;
|
std::shared_ptr<VulkanFence> fence = nullptr;
|
||||||
@@ -447,7 +448,8 @@ int run_test_with_one_queue(
|
|||||||
|
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
std::shared_ptr<VulkanFence> fence = nullptr;
|
std::shared_ptr<VulkanFence> fence = nullptr;
|
||||||
@@ -752,7 +754,8 @@ int run_test_with_multi_import_same_ctx(
|
|||||||
|
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
std::shared_ptr<VulkanFence> fence = nullptr;
|
std::shared_ptr<VulkanFence> fence = nullptr;
|
||||||
@@ -1092,7 +1095,8 @@ int run_test_with_multi_import_diff_ctx(
|
|||||||
|
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkVk2CLSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
VulkanSemaphore vkCl2VkSemaphore(vkDevice, vkExternalSemaphoreHandleType);
|
||||||
std::shared_ptr<VulkanFence> fence = nullptr;
|
std::shared_ptr<VulkanFence> fence = nullptr;
|
||||||
|
|||||||
@@ -208,7 +208,9 @@ int run_test_with_two_queue(
|
|||||||
std::vector<VulkanFormat> vkFormatList = getSupportedVulkanFormatList();
|
std::vector<VulkanFormat> vkFormatList = getSupportedVulkanFormatList();
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
char magicValue = 0;
|
char magicValue = 0;
|
||||||
|
|
||||||
VulkanBuffer vkParamsBuffer(vkDevice, sizeof(Params));
|
VulkanBuffer vkParamsBuffer(vkDevice, sizeof(Params));
|
||||||
@@ -820,7 +822,8 @@ int run_test_with_one_queue(
|
|||||||
std::vector<VulkanFormat> vkFormatList = getSupportedVulkanFormatList();
|
std::vector<VulkanFormat> vkFormatList = getSupportedVulkanFormatList();
|
||||||
const std::vector<VulkanExternalMemoryHandleType>
|
const std::vector<VulkanExternalMemoryHandleType>
|
||||||
vkExternalMemoryHandleTypeList =
|
vkExternalMemoryHandleTypeList =
|
||||||
getSupportedVulkanExternalMemoryHandleTypeList();
|
getSupportedVulkanExternalMemoryHandleTypeList(
|
||||||
|
vkDevice.getPhysicalDevice());
|
||||||
char magicValue = 0;
|
char magicValue = 0;
|
||||||
|
|
||||||
VulkanBuffer vkParamsBuffer(vkDevice, sizeof(Params));
|
VulkanBuffer vkParamsBuffer(vkDevice, sizeof(Params));
|
||||||
|
|||||||
Reference in New Issue
Block a user