[GA] Enable win workflow (#19646)
* Enable win workflow and cpu parallel tests * removed test code * update cache naming * extended logs collections * Revert "extended logs collections" This reverts commit0dd6620832
. * revert lost code during the merge * missed dependencies * enabled push trigger * changed the cache key name * skipped failed test * fixed github action condition and added comments * Update src/core/tests/check.cpp * cache generation fix * Apply suggestions from code review * fixed python test configuration * Revert "cache generation fix" This reverts commit0feab650fe
. * debug parallel tests * Revert "Revert "cache generation fix"" This reverts commite385b04410
. * Revert "debug parallel tests" This reverts commite4459472a7
. * fixed steps conditions * concurrency updated * fixed test skip condition on win * review changes * collect debug logs * overwrite test list * debug commit * Revert "debug commit" This reverts commit8720b87c8f
.
This commit is contained in:
parent
46dc704e3f
commit
a79c07b3a0
100
.github/workflows/windows.yml
vendored
100
.github/workflows/windows.yml
vendored
@ -1,27 +1,28 @@
|
|||||||
name: Tests on Windows (VS 2022, Python 3.11)
|
name: Tests on Windows (VS 2022, Python 3.11)
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
# pull_request:
|
pull_request:
|
||||||
# paths-ignore:
|
paths-ignore:
|
||||||
# - '**/docs/**'
|
- '**/docs/**'
|
||||||
# - 'docs/**'
|
- 'docs/**'
|
||||||
# - '**/**.md'
|
- '**/**.md'
|
||||||
# - '**.md'
|
- '**.md'
|
||||||
# - '**/layer_tests_summary/**'
|
- '**/layer_tests_summary/**'
|
||||||
# - '**/conformance/**'
|
- '**/conformance/**'
|
||||||
# push:
|
push:
|
||||||
# paths-ignore:
|
paths-ignore:
|
||||||
# - '**/docs/**'
|
- '**/docs/**'
|
||||||
# - 'docs/**'
|
- 'docs/**'
|
||||||
# - '**/**.md'
|
- '**/**.md'
|
||||||
# - '**.md'
|
- '**.md'
|
||||||
# - '**/layer_tests_summary/**'
|
- '**/layer_tests_summary/**'
|
||||||
# - '**/conformance/**'
|
- '**/conformance/**'
|
||||||
# branches:
|
branches:
|
||||||
# - master
|
- master
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.head_ref || github.run_id }}-windows
|
# github.ref is not unique in post-commit
|
||||||
|
group: ${{ github.event_name == 'push' && github.run_id || github.ref }}-windows
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@ -37,7 +38,7 @@ env:
|
|||||||
LAYER_TESTS_INSTALL_DIR: "${{ github.workspace }}\\install\\tests\\layer_tests"
|
LAYER_TESTS_INSTALL_DIR: "${{ github.workspace }}\\install\\tests\\layer_tests"
|
||||||
BUILD_DIR: "${{ github.workspace }}\\build"
|
BUILD_DIR: "${{ github.workspace }}\\build"
|
||||||
OV_TEMP: "${{ github.workspace }}\\openvino_temp"
|
OV_TEMP: "${{ github.workspace }}\\openvino_temp"
|
||||||
PYTHON_STATIC_ARGS: -m "not dynamic_library and not template_plugin"
|
PYTHON_STATIC_ARGS: -m "not dynamic_library"
|
||||||
VCVARSPATH: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat"
|
VCVARSPATH: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -124,9 +125,10 @@ jobs:
|
|||||||
# Should save cache only if run in the master branch of the base repo
|
# Should save cache only if run in the master branch of the base repo
|
||||||
# github.ref_name is 'ref/PR_#' in case of the PR, and 'branch_name' when executed on push
|
# github.ref_name is 'ref/PR_#' in case of the PR, and 'branch_name' when executed on push
|
||||||
save: ${{ github.ref_name == 'master' && 'true' || 'false' }}
|
save: ${{ github.ref_name == 'master' && 'true' || 'false' }}
|
||||||
key: ${{ github.job }}-windows
|
append-timestamp: true
|
||||||
|
key: ${{ github.job }}-${{ runner.os }}-common
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ github.job }}-windows
|
${{ github.job }}-${{ runner.os }}-common
|
||||||
|
|
||||||
- name: CMake configure
|
- name: CMake configure
|
||||||
run: |
|
run: |
|
||||||
@ -222,7 +224,6 @@ jobs:
|
|||||||
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && ${{ env.INSTALL_TEST_DIR }}/ov_onnx_frontend_tests --gtest_print_time=1 --gtest_filter=-*IE_GPU* --gtest_output=xml:${{ env.INSTALL_TEST_DIR }}/TEST-ONNXFrontend.xml
|
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && ${{ env.INSTALL_TEST_DIR }}/ov_onnx_frontend_tests --gtest_print_time=1 --gtest_filter=-*IE_GPU* --gtest_output=xml:${{ env.INSTALL_TEST_DIR }}/TEST-ONNXFrontend.xml
|
||||||
|
|
||||||
- name: List installed files
|
- name: List installed files
|
||||||
if: ${{ always() }}
|
|
||||||
run: |
|
run: |
|
||||||
Get-ChildItem -Recurse -Directory ${{ env.INSTALL_DIR }}
|
Get-ChildItem -Recurse -Directory ${{ env.INSTALL_DIR }}
|
||||||
|
|
||||||
@ -316,6 +317,8 @@ jobs:
|
|||||||
# For running Paddle frontend unit tests
|
# For running Paddle frontend unit tests
|
||||||
python3 -m pip install -r ${{ env.OPENVINO_REPO }}/src/frontends/paddle/tests/requirements.txt
|
python3 -m pip install -r ${{ env.OPENVINO_REPO }}/src/frontends/paddle/tests/requirements.txt
|
||||||
|
|
||||||
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
|
|
||||||
- name: Install MO dependencies
|
- name: Install MO dependencies
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.OPENVINO_REPO }}/tools/mo/requirements_mxnet.txt `
|
python3 -m pip install -r ${{ env.OPENVINO_REPO }}/tools/mo/requirements_mxnet.txt `
|
||||||
@ -377,7 +380,6 @@ jobs:
|
|||||||
# TEST_DEVICE: CPU
|
# TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: TensorFlow 1 Layer Tests - TF FE
|
- name: TensorFlow 1 Layer Tests - TF FE
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -389,7 +391,6 @@ jobs:
|
|||||||
TEST_DEVICE: CPU
|
TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: TensorFlow 2 Layer Tests - TF FE
|
- name: TensorFlow 2 Layer Tests - TF FE
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -401,7 +402,6 @@ jobs:
|
|||||||
TEST_DEVICE: CPU
|
TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: TensorFlow 1 Layer Tests - Legacy FE
|
- name: TensorFlow 1 Layer Tests - Legacy FE
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -411,7 +411,6 @@ jobs:
|
|||||||
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && python3 -m pytest ${{ env.LAYER_TESTS_INSTALL_DIR }}/tensorflow_tests/test_tf_Roll.py --ir_version=10 --junitxml=${{ env.INSTALL_TEST_DIR }}/TEST-tf_Roll.xml
|
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && python3 -m pytest ${{ env.LAYER_TESTS_INSTALL_DIR }}/tensorflow_tests/test_tf_Roll.py --ir_version=10 --junitxml=${{ env.INSTALL_TEST_DIR }}/TEST-tf_Roll.xml
|
||||||
|
|
||||||
- name: TensorFlow 2 Layer Tests - Legacy FE
|
- name: TensorFlow 2 Layer Tests - Legacy FE
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -423,7 +422,6 @@ jobs:
|
|||||||
TEST_DEVICE: CPU
|
TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: TensorFlow Lite Layer Tests - TFL FE
|
- name: TensorFlow Lite Layer Tests - TFL FE
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -442,7 +440,6 @@ jobs:
|
|||||||
TEST_DEVICE: CPU
|
TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: MO Python API Tests
|
- name: MO Python API Tests
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -454,7 +451,6 @@ jobs:
|
|||||||
TEST_DEVICE: CPU
|
TEST_DEVICE: CPU
|
||||||
|
|
||||||
- name: Python Frontend tests
|
- name: Python Frontend tests
|
||||||
if: ${{ always() }}
|
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
python3 -m pip install -r ${{ env.LAYER_TESTS_INSTALL_DIR }}/requirements.txt
|
||||||
@ -465,7 +461,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: test-results-python
|
name: test-results-python
|
||||||
path: ${{ env.INSTALL_TEST_DIR }}/TEST*.xml
|
path: ${{ env.INSTALL_TEST_DIR }}/TEST*.xml
|
||||||
@ -636,7 +632,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: test-results-cpp
|
name: test-results-cpp
|
||||||
path: ${{ env.INSTALL_TEST_DIR }}/TEST*.xml
|
path: ${{ env.INSTALL_TEST_DIR }}/TEST*.xml
|
||||||
@ -647,10 +643,12 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest-8-cores
|
||||||
env:
|
env:
|
||||||
INSTALL_DIR: "${{ github.workspace }}\\install"
|
INSTALL_DIR: "${{ github.workspace }}\\install"
|
||||||
INSTALL_TEST_DIR: "${{ github.workspace }}\\install\\tests"
|
INSTALL_TEST_DIR: "${{ github.workspace }}\\install\\tests"
|
||||||
|
PARALLEL_TEST_SCRIPT: "${{ github.workspace }}\\install\\tests\\functional_test_utils\\run_parallel.py"
|
||||||
|
PARALLEL_TEST_CACHE: "${{ github.workspace }}\\install\\tests\\test_cache.lst"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Create Directories
|
- name: Create Directories
|
||||||
@ -685,15 +683,43 @@ jobs:
|
|||||||
ls "${{ env.INSTALL_DIR }}"
|
ls "${{ env.INSTALL_DIR }}"
|
||||||
ls "${{ env.INSTALL_TEST_DIR }}"
|
ls "${{ env.INSTALL_TEST_DIR }}"
|
||||||
|
|
||||||
- name: Intel CPU plugin func tests
|
- name: Install python dependencies
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && ${{ env.INSTALL_TEST_DIR }}/ov_cpu_func_tests --gtest_print_time=1 --gtest_filter=*smoke* --gtest_output=xml:${{ env.INSTALL_TEST_DIR }}/TEST-CPUFuncTests.xml
|
python3 -m pip install --upgrade pip
|
||||||
|
python3 -m pip install -r ${{ github.workspace }}\install\tests\functional_test_utils\requirements.txt
|
||||||
|
|
||||||
|
- name: Restore tests execution time
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.PARALLEL_TEST_CACHE }}
|
||||||
|
key: ${{ runner.os }}-tests-functional-cpu-stamp-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-tests-functional-cpu-stamp
|
||||||
|
|
||||||
|
- name: Intel CPU plugin func tests (parallel)
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "${{ env.INSTALL_DIR }}\\setupvars.bat" && python3 ${{ env.PARALLEL_TEST_SCRIPT }} -e ${{ env.INSTALL_TEST_DIR }}\ov_cpu_func_tests.exe -c ${{ env.PARALLEL_TEST_CACHE }} -w ${{ env.INSTALL_TEST_DIR }} -s suite -- --gtest_filter=*smoke*"
|
||||||
|
timeout-minutes: 45
|
||||||
|
|
||||||
|
- name: Save tests execution time
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
if: github.ref_name == 'master'
|
||||||
|
with:
|
||||||
|
path: ${{ env.PARALLEL_TEST_CACHE }}
|
||||||
|
key: ${{ runner.os }}-tests-functional-cpu-stamp-${{ github.sha }}
|
||||||
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: test-results-functional-cpu
|
name: test-results-functional-cpu
|
||||||
path: ${{ env.INSTALL_TEST_DIR }}/TEST*.xml
|
path: |
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/temp/*.log
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/logs/failed/*.log
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/logs/crashed/*.log
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/logs/hanged/*.log
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/logs/interapted/*.log
|
||||||
|
${{ env.INSTALL_TEST_DIR }}/logs/*.log
|
||||||
if-no-files-found: 'error'
|
if-no-files-found: 'error'
|
||||||
|
@ -388,9 +388,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|||||||
ov_add_compiler_flags(/wd4275)
|
ov_add_compiler_flags(/wd4275)
|
||||||
|
|
||||||
# Enable __FILE__ trim, use path with forward and backward slash as directory separator
|
# Enable __FILE__ trim, use path with forward and backward slash as directory separator
|
||||||
add_compile_options(
|
# github actions use sccache which doesn't support /d1trimfile compile option
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:/d1trimfile:${OV_NATIVE_PROJECT_ROOT_DIR}\\>"
|
if(NOT DEFINED ENV{GITHUB_ACTIONS})
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:/d1trimfile:${OpenVINO_SOURCE_DIR}/>")
|
add_compile_options(
|
||||||
|
"$<$<COMPILE_LANGUAGE:CXX>:/d1trimfile:${OV_NATIVE_PROJECT_ROOT_DIR}\\>"
|
||||||
|
"$<$<COMPILE_LANGUAGE:CXX>:/d1trimfile:${OpenVINO_SOURCE_DIR}/>")
|
||||||
|
endif()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Debug information flags, by default CMake adds /Zi option
|
# Debug information flags, by default CMake adds /Zi option
|
||||||
|
@ -98,6 +98,7 @@ def test_node_factory_validate_missing_arguments():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.template_extension()
|
@pytest.mark.template_extension()
|
||||||
|
@pytest.mark.dynamic_library()
|
||||||
def test_extension_added_from_library():
|
def test_extension_added_from_library():
|
||||||
if platform == "win32":
|
if platform == "win32":
|
||||||
library_path = "openvino_template_extension.dll"
|
library_path = "openvino_template_extension.dll"
|
||||||
|
@ -344,6 +344,7 @@ def test_unload_plugin(device):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.template_extension()
|
@pytest.mark.template_extension()
|
||||||
|
@pytest.mark.dynamic_library()
|
||||||
def test_add_extension_template_extension(device):
|
def test_add_extension_template_extension(device):
|
||||||
core, model = get_model_with_template_extension()
|
core, model = get_model_with_template_extension()
|
||||||
assert isinstance(model, Model)
|
assert isinstance(model, Model)
|
||||||
|
@ -49,6 +49,10 @@ TEST(check, check_with_explanation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(check, ov_throw_exception_check_relative_path_to_source) {
|
TEST(check, ov_throw_exception_check_relative_path_to_source) {
|
||||||
|
// github actions use sccache which doesn't support /d1trimfile compile option
|
||||||
|
if (std::getenv("GITHUB_ACTIONS")) {
|
||||||
|
GTEST_SKIP();
|
||||||
|
}
|
||||||
using namespace testing;
|
using namespace testing;
|
||||||
const auto path = ov::util::path_join({"src", "core", "tests", "check.cpp"});
|
const auto path = ov::util::path_join({"src", "core", "tests", "check.cpp"});
|
||||||
const auto exp_native_slash = "Exception from " + path + ":";
|
const auto exp_native_slash = "Exception from " + path + ":";
|
||||||
|
@ -305,7 +305,7 @@ class TestParallelRunner:
|
|||||||
os.remove(test_list_file_name)
|
os.remove(test_list_file_name)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.warning(f"Imposible to remove {test_list_file_name}. Error: {err}")
|
logger.warning(f"Imposible to remove {test_list_file_name}. Error: {err}")
|
||||||
command_to_get_test_list = self._command + f' --gtest_list_tests >> {test_list_file_name}'
|
command_to_get_test_list = self._command + f' --gtest_list_tests > {test_list_file_name}'
|
||||||
logger.info(f"Get test list using command: {command_to_get_test_list}")
|
logger.info(f"Get test list using command: {command_to_get_test_list}")
|
||||||
run_res = run(command_to_get_test_list, check=True, shell=True)
|
run_res = run(command_to_get_test_list, check=True, shell=True)
|
||||||
if run_res.stderr != "" and run_res.stderr != None:
|
if run_res.stderr != "" and run_res.stderr != None:
|
||||||
|
@ -1224,13 +1224,13 @@ class PathCheckerFunctions(unittest.TestCase):
|
|||||||
self.assertEqual(__class__.WRITABLE_DIR, writable_dir(__class__.WRITABLE_DIR))
|
self.assertEqual(__class__.WRITABLE_DIR, writable_dir(__class__.WRITABLE_DIR))
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir")
|
@unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir")
|
||||||
@unittest.skipIf(os.geteuid() == 0, "root user does not support not writable dir")
|
@unittest.skipIf(sys.platform.startswith("lin") and os.geteuid() == 0, "root user does not support not writable dir")
|
||||||
def test_single_non_writable_dir(self):
|
def test_single_non_writable_dir(self):
|
||||||
with self.assertRaises(Error) as cm:
|
with self.assertRaises(Error) as cm:
|
||||||
writable_dir(__class__.NOT_WRITABLE_DIR)
|
writable_dir(__class__.NOT_WRITABLE_DIR)
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir")
|
@unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir")
|
||||||
@unittest.skipIf(os.geteuid() == 0, "root user does not support not writable dir")
|
@unittest.skipIf(sys.platform.startswith("lin") and os.geteuid() == 0, "root user does not support not writable dir")
|
||||||
def test_single_non_writable_sub_dir(self):
|
def test_single_non_writable_sub_dir(self):
|
||||||
with self.assertRaises(Error) as cm:
|
with self.assertRaises(Error) as cm:
|
||||||
writable_dir(__class__.NOT_WRITABLE_SUB_DIR)
|
writable_dir(__class__.NOT_WRITABLE_SUB_DIR)
|
||||||
|
Loading…
Reference in New Issue
Block a user