diff options
Diffstat (limited to 'tests')
29 files changed, 2675 insertions, 0 deletions
diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..a5fa708 --- /dev/null +++ b/tests/README @@ -0,0 +1,98 @@ +nvmetests +========= + + This contains a NVMe tests framework. The purpose of this framework + to use nvme cli and test various supported commands and scenarios for + NVMe device. + + In current implementation this framework uses nvme-cli to + interact with underlying controller/namespace. + + Note these tests expect to run against real hardware and will + read and write data to /dev/nvme0! + + DO NOT RUN THEM IF YOU DO NOT KNOW WHAT YOU ARE DOING! + + You have been warned. + +1. Common Package Dependencies +------------------------------ + + 1. Python(>= 3.3) + 2. nose2 (Installation guide http://nose2.readthedocs.io/) + 3. flake8 (https://pypi.python.org/pypi/flake8) + 4. mypy (https://pypi.org/project/mypy/) + 5. autopep8 (https://pypi.org/project/autopep8/) + 6. isort (https://pypi.org/project/isort/) + + Python package management system pip can be used to install most of the + listed packages(https://pip.pypa.io/en/stable/installing/) :- + $ pip install nose2 flake8 mypy autopep8 isort + +2. Overview +----------- + + This framework follows simple class hierarchy. Each test file contains + one test. Each test is direct subclass or indirect subclass of TestNVMe + class which represents one testcase. To write a new testcase one can copy + existing template "nvme_simple_template_test.py" and start adding new + testcase specific functionality. For detailed information please look into + section 3. + + For more information about tests, class hierarchy and code please refer :- + + 1. Documentation :- html/ + 2. Class Index :- html/index.html + 3. Class Hierarchy :- html/class-tree.html + + For each testcase it will create log directory mentioned in + configuration file. This directory will be used for a temporary files + and storing execution logs of each testcases. Current implementation stores + stdout and stderr for each testcase under log directory, e.g. :- + + $ tree nvmetests/ + nvmetests/ + ├── TestNVMeAttachDetachNSCmd + │ ├── stderr.log + │ └── stdout.log + ├── TestNVMeFlushCmd + │ ├── stderr.log + │ └── stdout.log + └── TestNVMeFormatCmd + ├── stderr.log + └── stdout.log + . + . + . + +3. Walk-Through Example for writing a new testcase +-------------------------------------------------- + 1. Copy simple test template file from current directory + with appropriate name, replace "simple_template" with testcase name + in new file name. Update config.json if necessary. + 2. Write a testcase main function, make sure its name is starting with + test_*. + 3. Based on the requirement one can inherit TestNVMe or TestNVMeIO + class. + 4. Write test precondition code into setUp. Make sure you are calling + super class setUp. + 5. Write test post condition code into tearDown. Make sure you are calling + super class tearDown. + 6. Before writing a new function have a look into TestNVMe to see if it + can be reused. + 7. Once testcase is ready make sure :- + a. Run flake8, mypy, autopep8 and isort on the testcase and fix + errors/warnings. + - Example "$ ninja -C .build lint-python" will run flake8 and + mypy on all the python files in current directory. + - Example "$ ninja -C .build format-python" will run autopep8 and + isort on all the python files in the current directory. + +4. Running testcases with framework +----------------------------------- + 1. Running single testcase (in the source tree) with nose2 :- + $ nose2 --verbose --start-dir tests nvme_writezeros_test + $ nose2 --verbose --start-dir tests nvme_read_write_test + + 2. Running all the testcases (in the build root directory) with ninja :- + $ ninja test -C .build diff --git a/tests/TODO b/tests/TODO new file mode 100644 index 0000000..69806a9 --- /dev/null +++ b/tests/TODO @@ -0,0 +1,14 @@ +nvmetests TODO List +=================== + +Feature list (with priority):- +------------------------------ + 1. PRE and POST section improvements :- + a. Add functionality to load and unload driver. + b. Make sure default namespace is present, if not create one before + any test begins. Read the default namespace size from config file. + 2. Add system statistics collection in PRE and POST section of testcase. + 3. Create execution summary file under log directory at the end of each + run. + 4. Add tracing functionality to track overall and current progress of the + testcase. diff --git a/tests/config.json b/tests/config.json new file mode 100644 index 0000000..098fba8 --- /dev/null +++ b/tests/config.json @@ -0,0 +1,5 @@ +{ + "controller" : "/dev/nvme0", + "ns1": "/dev/nvme0n1", + "log_dir": "nvmetests/" +} diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..f8c6aa2 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +infra = [ + 'config.json', + 'nvme_test.py', + 'nvme_test_io.py', + 'nvme_test_logger.py', + 'nvme_simple_template_test.py', +] + +tests = [ + 'nvme_attach_detach_ns_test.py', + 'nvme_compare_test.py', + 'nvme_create_max_ns_test.py', + 'nvme_error_log_test.py', + 'nvme_flush_test.py', + 'nvme_format_test.py', + 'nvme_fw_log_test.py', + 'nvme_get_features_test.py', + 'nvme_id_ctrl_test.py', + 'nvme_id_ns_test.py', + 'nvme_read_write_test.py', + 'nvme_smart_log_test.py', + 'nvme_writeuncor_test.py', + 'nvme_writezeros_test.py', + 'nvme_copy_test.py', + 'nvme_dsm_test.py', + 'nvme_verify_test.py', + 'nvme_lba_status_log_test.py', + 'nvme_get_lba_status_test.py', + 'nvme_ctrl_reset_test.py', +] + +runtests = find_program('nose2', required : false) + +if meson.version().version_compare('>= 0.56') + nvmecli_path = meson.project_build_root() +else + nvmecli_path = meson.build_root() +endif + +if runtests.found() + foreach file : infra + tests + configure_file( + input: file, + output: file, + copy: true) + endforeach + + foreach t : tests + t_name = t.split('.')[0] + test(t_name, runtests, + args: ['--verbose', '--start-dir', meson.current_build_dir(), t_name], + env: ['PATH=' + nvmecli_path + ':/usr/bin:/usr/sbin'], + timeout: 500) + endforeach +endif + +python_module = import('python') + +python = python_module.find_installation('python3') + +mypy = find_program( + 'mypy', + required : false, +) +flake8 = find_program( + 'flake8', + required : false, +) +linter_script = files('run_py_linters.py') + +if mypy.found() and flake8.found() + run_target( + 'lint-python', + command : [python, linter_script, 'lint'], + ) +else + message('Mypy or Flake8 not found. Python linting disabled') +endif + + +autopep8 = find_program( + 'autopep8', + required : false, +) +isort = find_program( + 'isort', + required : false, +) + +if autopep8.found() and isort.found() + run_target( + 'format-python', + command : [python, linter_script, 'format'], + ) +else + message('autopep8 or isort not found. Python formatting disabled') +endif diff --git a/tests/nvme_attach_detach_ns_test.py b/tests/nvme_attach_detach_ns_test.py new file mode 100644 index 0000000..075f211 --- /dev/null +++ b/tests/nvme_attach_detach_ns_test.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Namespace Management Testcase:- + + 1. Create Namespace and validate. + 2. Attach Namespace to controller. + 3. Run IOs on Namespace under test. + 4. Detach Namespace from controller. + 5. Delete Namespace. +""" + +import time + +from nvme_test import TestNVMe + + +class TestNVMeAttachDetachNSCmd(TestNVMe): + + """ + Represents Attach, Detach namespace testcase. + + - Attributes: + - dps : data protection information. + - flabs : LBA format information. + - nsze : namespace size. + - ncap : namespace capacity. + - ctrl_id : controller id. + """ + + def setUp(self): + """ Pre Section for TestNVMeAttachDetachNSCmd """ + super().setUp() + self.dps = 0 + self.flbas = 0 + self.nsze = 0x1400000 + self.ncap = 0x1400000 + self.setup_log_dir(self.__class__.__name__) + self.ctrl_id = self.get_ctrl_id() + self.delete_all_ns() + time.sleep(1) + + def tearDown(self): + """ + Post Section for TestNVMeAttachDetachNSCmd + + - Create primary namespace. + - Attach it to controller. + - Call super class's destructor. + """ + self.assertEqual(self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps), 0) + self.attach_ns(self.ctrl_id, self.default_nsid) + super().tearDown() + + def test_attach_detach_ns(self): + """ Testcase main """ + err = self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps) + self.assertEqual(err, 0) + self.assertEqual(self.attach_ns(self.ctrl_id, self.default_nsid), 0) + + self.run_ns_io(self.default_nsid, 0) + + self.assertEqual(self.detach_ns(self.ctrl_id, self.default_nsid), 0) + self.assertEqual(self.delete_and_validate_ns(self.default_nsid), 0) + self.nvme_reset_ctrl() diff --git a/tests/nvme_compare_test.py b/tests/nvme_compare_test.py new file mode 100644 index 0000000..8dfce04 --- /dev/null +++ b/tests/nvme_compare_test.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Compare Command Testcase:- + + 1. Create a data file 1 with pattern 1515 to write. + 2. Create a data file 2 with pattern 2525 to compare with. + 3. Write a block of data pattern using data file1. + 4. Compare written block to data file 2's pattern; shall fail. + 5. Compare written block to data file1's pattern; shall pass. + +""" + +from nvme_test_io import TestNVMeIO + + +class TestNVMeCompareCmd(TestNVMeIO): + + """ + Represents Compare Testcase. Inherits TestNVMeIO class. + + - Attributes: + - data_size : data size to perform IO. + - start_block : starting block of to perform IO. + - compare_file : data file to use in nvme compare command. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeCompareCmd """ + super().setUp() + self.data_size = 1024 + self.start_block = 1023 + self.setup_log_dir(self.__class__.__name__) + self.compare_file = self.test_log_dir + "/" + "compare_file.txt" + self.write_file = self.test_log_dir + "/" + self.write_file + self.create_data_file(self.write_file, self.data_size, "15") + self.create_data_file(self.compare_file, self.data_size, "25") + + def tearDown(self): + """ Post Section for TestNVMeCompareCmd """ + super().tearDown() + + def nvme_compare(self, cmp_file): + """ Wrapper for nvme compare command. + - Args: + - cmp_file : data file used in nvme compare command. + - Returns: + - return code of the nvme compare command. + """ + compare_cmd = "nvme compare " + self.ns1 + " --start-block=" + \ + str(self.start_block) + " --block-count=" + \ + str(self.block_count) + " --data-size=" + \ + str(self.data_size) + " --data=" + cmp_file + return self.exec_cmd(compare_cmd) + + def test_nvme_compare(self): + """ Testcase main """ + self.assertEqual(self.nvme_write(), 0) + self.assertNotEqual(self.nvme_compare(self.compare_file), 0) + self.assertEqual(self.nvme_compare(self.write_file), 0) diff --git a/tests/nvme_copy_test.py b/tests/nvme_copy_test.py new file mode 100644 index 0000000..a547231 --- /dev/null +++ b/tests/nvme_copy_test.py @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Authors: Arunpandian J <apj.arun@samsung.com> +# Joy Gu <jgu@purestorage.com> + +""" +NVMe Copy Testcase:- + + 1. Issue copy command on set of block; shall pass. + 2. If cross-namespace copy formats are supported, enable and test + cross-namespace copy formats. + +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeCopy(TestNVMe): + + """ + Represents NVMe Copy testcase. + - Attributes: + - ocfs : optional copy formats supported + - host_behavior_data : host behavior support data to restore during teardown + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeCopy """ + super().setUp() + print("\nSetting up test...") + self.ocfs = self.get_ocfs() + cross_namespace_copy = self.ocfs & 0xc + if cross_namespace_copy: + # get host behavior support data + get_features_cmd = ["nvme", "get-feature", self.ctrl, "--feature-id=0x16", "--data-len=512", "-b"] + print("Running command:", " ".join(get_features_cmd)) + self.host_behavior_data = subprocess.check_output(get_features_cmd) + # enable cross-namespace copy formats + if self.host_behavior_data[4] & cross_namespace_copy: + # skip if already enabled + print("Cross-namespace copy already enabled, skipping set-features") + self.host_behavior_data = None + else: + data = self.host_behavior_data[:4] + cross_namespace_copy.to_bytes(2, 'little') + self.host_behavior_data[6:] + set_features_cmd = ["nvme", "set-feature", self.ctrl, "--feature-id=0x16", "--data-len=512"] + print("Running command:", " ".join(set_features_cmd)) + proc = subprocess.Popen(set_features_cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + proc.communicate(input=data) + self.assertEqual(proc.returncode, 0, "Failed to enable cross-namespace copy formats") + get_ns_id_cmd = ["nvme", "get-ns-id", self.ns1] + print("Running command:", " ".join(get_ns_id_cmd)) + output = subprocess.check_output(get_ns_id_cmd) + self.ns1_nsid = int(output.decode().strip().split(':')[-1]) + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeCopy """ + print("Tearing down test...") + if self.host_behavior_data: + # restore saved host behavior support data + set_features_cmd = ["nvme", "set-feature", self.ctrl, "--feature-id=0x16", "--data-len=512"] + print("Running command:", " ".join(set_features_cmd)) + proc = subprocess.Popen(set_features_cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + proc.communicate(input=self.host_behavior_data) + super().tearDown() + + def copy(self, sdlba, blocks, slbs, **kwargs): + """ Wrapper for nvme copy + - Args: + - sdlba : destination logical block address + - blocks : number of logical blocks (0-based) + - slbs : source range logical block address + - descriptor_format : copy descriptor format (optional) + - snsids : source namespace id (optional) + - sopts : source options (optional) + - Returns: + - None + """ + # skip if descriptor format not supported (default format is 0) + desc_format = kwargs.get("descriptor_format", 0) + if not self.ocfs & (1 << desc_format): + print(f"Skip copy because descriptor format {desc_format} is not supported") + return + # build copy command + copy_cmd = f"nvme copy {self.ns1} --format={desc_format} --sdlba={sdlba} --blocks={blocks} --slbs={slbs}" + if "snsids" in kwargs: + copy_cmd += f" --snsids={kwargs['snsids']}" + if "sopts" in kwargs: + copy_cmd += f" --sopts={kwargs['sopts']}" + # run and assert success + print("Running command:", copy_cmd) + self.assertEqual(self.exec_cmd(copy_cmd), 0) + + def test_copy(self): + """ Testcase main """ + print("Running test...") + self.copy(0, 1, 2, descriptor_format=0) + self.copy(0, 1, 2, descriptor_format=1) + self.copy(0, 1, 2, descriptor_format=2, snsids=self.ns1_nsid) + self.copy(0, 1, 2, descriptor_format=2, snsids=self.ns1_nsid, sopts=0) + self.copy(0, 1, 2, descriptor_format=3, snsids=self.ns1_nsid) + self.copy(0, 1, 2, descriptor_format=3, snsids=self.ns1_nsid, sopts=0) diff --git a/tests/nvme_create_max_ns_test.py b/tests/nvme_create_max_ns_test.py new file mode 100644 index 0000000..bda93e1 --- /dev/null +++ b/tests/nvme_create_max_ns_test.py @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Namespace Management Testcase:- + + 1. Create Maximum number of Namespaces and validate. + 2. Attach all Namespaces to controller. + 3. Run IOs on Namespace under test. + 4. Detach Maximum number of Namespaces from controller. + 5. Delete all Namespaces. +""" + +import time + +from nvme_test import TestNVMe + + +class TestNVMeCreateMaxNS(TestNVMe): + + """ + Represents Attach, Detach namespace testcase. + + - Attributes: + - dps : data protection information. + - flbas : LBA format information. + - nsze : namespace size. + - ncap : namespace capacity. + - ctrl_id : controller id. + """ + + def setUp(self): + """ Pre Section for TestNVMeAttachDetachNSCmd """ + super().setUp() + self.dps = 0 + self.flbas = 0 + self.nsze = int(self.get_ncap() / + self.get_format() / self.get_max_ns()) + self.ncap = self.nsze + self.setup_log_dir(self.__class__.__name__) + self.max_ns = self.get_max_ns() + self.ctrl_id = self.get_ctrl_id() + self.delete_all_ns() + time.sleep(1) + + def tearDown(self): + """ + Post Section for TestNVMeAttachDetachNSCmd + + - Create primary namespace. + - Attach it to controller. + - Call super class's destructor. + """ + self.assertEqual(self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps), 0) + self.attach_ns(self.ctrl_id, self.default_nsid) + super.tearDown() + + def test_attach_detach_ns(self): + """ Testcase main """ + for nsid in range(1, self.max_ns): + print("##### Creating " + str(nsid)) + err = self.create_and_validate_ns(nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps) + self.assertEqual(err, 0) + print("##### Attaching " + str(nsid)) + self.assertEqual(self.attach_ns(self.ctrl_id, nsid), 0) + print("##### Running IOs in " + str(nsid)) + self.run_ns_io(nsid, 0) + + for nsid in range(1, self.max_ns): + print("##### Detaching " + str(nsid)) + self.assertEqual(self.detach_ns(self.ctrl_id, nsid), 0) + print("#### Deleting " + str(nsid)) + self.assertEqual(self.delete_and_validate_ns(nsid), 0) + self.nvme_reset_ctrl() diff --git a/tests/nvme_ctrl_reset_test.py b/tests/nvme_ctrl_reset_test.py new file mode 100644 index 0000000..b8b3c3b --- /dev/null +++ b/tests/nvme_ctrl_reset_test.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Author: Arunpandian J <arun.j@samsung.com> + +""" +NVMe controller reset Testcase:- + + 1. Execute nvme controller reset. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeCtrlReset(TestNVMe): + + """ + Represents NVMe Controller reset testcase. + - Attributes: + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeCtrlReset """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeCtrlReset """ + super().tearDown() + + def ctrl_reset(self): + """ Wrapper for nvme controller reset + - Args: + - None + - Returns: + - return code for nvme controller reset. + """ + ctrl_reset_cmd = "nvme reset " + self.ctrl + return self.exec_cmd(ctrl_reset_cmd) + + def test_ctrl_reset(self): + """ Testcase main """ + self.assertEqual(self.ctrl_reset(), 0) diff --git a/tests/nvme_dsm_test.py b/tests/nvme_dsm_test.py new file mode 100644 index 0000000..d92bf58 --- /dev/null +++ b/tests/nvme_dsm_test.py @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Author: Arunpandian J <apj.arun@samsung.com> + +""" +NVMe DSM Testcase:- + + 1. Issue DSM command on set of block; shall pass. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeDsm(TestNVMe): + + """ + Represents NVMe Verify testcase. + - Attributes: + - start_block : starting block of to verify operation. + - range : Range of blocks for DSM operation. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeDsm """ + super().setUp() + self.start_block = 0 + self.range = 0 + self.namespace = 1 + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeDsm """ + super().tearDown() + + def dsm(self): + """ Wrapper for nvme verify + - Args: + - None + - Returns: + - return code for nvme dsm command. + """ + dsm_cmd = "nvme dsm " + self.ctrl + \ + " --namespace-id=" + str(self.namespace) + \ + " --blocks=" + str(self.range) + \ + " --slbs=" + str(self.start_block) + return self.exec_cmd(dsm_cmd) + + def test_dsm(self): + """ Testcase main """ + self.assertEqual(self.dsm(), 0) diff --git a/tests/nvme_error_log_test.py b/tests/nvme_error_log_test.py new file mode 100644 index 0000000..ba91c02 --- /dev/null +++ b/tests/nvme_error_log_test.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Smart Log Verification Testcase:- + + 1. Execute error-log on controller. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeErrorLogCmd(TestNVMe): + + """ + Represents Smart Log testcae. + + - Attributes: + """ + + def setUp(self): + """ Pre Section for TestNVMeErrorLogCmd """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ + Post Section for TestNVMeErrorLogCmd + + - Call super class's destructor. + """ + super().tearDown() + + def get_error_log_ctrl(self): + """ Wrapper for executing error-log on controller. + - Args: + - None: + - Returns: + - 0 on success, error code on failure. + """ + return self.get_error_log() + + def test_get_error_log(self): + """ Testcase main """ + self.assertEqual(self.get_error_log_ctrl(), 0) diff --git a/tests/nvme_flush_test.py b/tests/nvme_flush_test.py new file mode 100644 index 0000000..e4f127d --- /dev/null +++ b/tests/nvme_flush_test.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Flush Command Testcase:- + + 1. Execute nvme flush on controller. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeFlushCmd(TestNVMe): + + """ + Represents Flush Testcase. Inherits TestNVMe class. + + - Attributes: + """ + + def setUp(self): + """ Pre Section for TestNVMeFlushCmd """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeFlushCmd """ + super().tearDown() + + def nvme_flush(self): + """ Wrapper for nvme flush command. + - Args: + - None + - Returns: + - None + """ + flush_cmd = "nvme flush " + self.ctrl + " -n " + str(self.default_nsid) + print(flush_cmd) + return self.exec_cmd(flush_cmd) + + def test_nvme_flush(self): + """ Testcase main """ + self.assertEqual(self.nvme_flush(), 0) diff --git a/tests/nvme_format_test.py b/tests/nvme_format_test.py new file mode 100644 index 0000000..40635c1 --- /dev/null +++ b/tests/nvme_format_test.py @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +Namespace Format testcase :- + + 1. Create, attach, detach, delete primary namespace and + extract the supported format information from default namespace:- + - List of the supported format. + - List of Metadata Size per format. Based on this we calculate + data protection parameter at the time of namespace. + - List of LBA Data Size per format. + 2. Use the collected information and iterate through each supported + format:- + - Create namespace. + - Attach namespace. + - Run IOs on the namespace under test. + - Detach namespace + - Delete Namespace. +""" + +import subprocess +import time + +from nvme_test import TestNVMe + + +class TestNVMeFormatCmd(TestNVMe): + + """ + Represents Format testcase. + + - Attributes: + - dps : data protection information. + - flabs : LBA format information. + - nsze : namespace size. + - ncap : namespace capacity. + - ctrl_id : controller id. + - lba_format_list : lis of supported format. + - ms_list : list of metadat size per format. + - lbads_list : list of LBA data size per format. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeFormatCmd """ + super().setUp() + self.dps = 0 # ns data protection settings + self.flbas = 0 # ns formattes logical block settings + self.nsze = 0x1400000 # ns size + self.ncap = 0x1400000 # ns capacity + self.ctrl_id = self.get_ctrl_id() + self.lba_format_list = [] + self.ms_list = [] + self.lbads_list = [] + self.test_log_dir = self.log_dir + "/" + self.__class__.__name__ + self.setup_log_dir(self.__class__.__name__) + self.delete_all_ns() + time.sleep(1) + + def tearDown(self): + """ + Post Section for TestNVMeFormatCmd + + - Create primary namespace. + - Attach it to controller. + - Call super class's destructor. + """ + self.assertEqual(self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps), 0) + self.attach_ns(self.ctrl_id, self.default_nsid) + super().tearDown() + + def attach_detach_primary_ns(self): + """ Extract supported format information using default namespace """ + self.assertEqual(self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.flbas, + self.dps), 0) + self.assertEqual(self.attach_ns(self.ctrl_id, self.default_nsid), 0) + # read lbaf information + id_ns = "nvme id-ns " + self.ctrl + \ + " -n1 | grep ^lbaf | awk '{print $2}' | tr -s \"\\n\" \" \"" + proc = subprocess.Popen(id_ns, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + self.lba_format_list = proc.stdout.read().strip().split(" ") + if proc.wait() == 0: + # read lbads information + id_ns = "nvme id-ns " + self.ctrl + \ + " -n1 | grep ^lbaf | awk '{print $5}'" + \ + " | cut -f 2 -d ':' | tr -s \"\\n\" \" \"" + proc = subprocess.Popen(id_ns, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + self.lbads_list = proc.stdout.read().strip().split(" ") + # read metadata information + id_ns = "nvme id-ns " + self.ctrl + \ + " -n1 | grep ^lbaf | awk '{print $4}'" + \ + " | cut -f 2 -d ':' | tr -s \"\\n\" \" \"" + proc = subprocess.Popen(id_ns, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + self.ms_list = proc.stdout.read().strip().split(" ") + self.assertEqual(self.detach_ns(self.ctrl_id, self.default_nsid), 0) + self.assertEqual(self.delete_and_validate_ns(self.default_nsid), 0) + self.nvme_reset_ctrl() + + def test_format_ns(self): + """ Testcase main """ + # extract the supported format information. + self.attach_detach_primary_ns() + + # iterate through all supported format + for i in range(0, len(self.lba_format_list)): + print("\nlba format " + str(self.lba_format_list[i]) + + " lbad " + str(self.lbads_list[i]) + + " ms " + str(self.ms_list[i])) + metadata_size = 1 if self.ms_list[i] == '8' else 0 + err = self.create_and_validate_ns(self.default_nsid, + self.nsze, + self.ncap, + self.lba_format_list[i], + metadata_size) + self.assertEqual(err, 0) + self.assertEqual(self.attach_ns(self.ctrl_id, self.default_nsid), 0) + self.run_ns_io(self.default_nsid, self.lbads_list[i]) + time.sleep(5) + self.assertEqual(self.detach_ns(self.ctrl_id, self.default_nsid), 0) + self.assertEqual(self.delete_and_validate_ns(self.default_nsid), 0) + self.nvme_reset_ctrl() diff --git a/tests/nvme_fw_log_test.py b/tests/nvme_fw_log_test.py new file mode 100644 index 0000000..b670671 --- /dev/null +++ b/tests/nvme_fw_log_test.py @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Madhusudhana S.J <madhusudhana.sj@wdc.com> +# Author: Dong Ho <dong.ho@wdc.com> +# +""" +NVMe Firmware Log Testcase :- + + 1. Execute fw-log on a device. +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeFwLogCmd(TestNVMe): + + """ + Represents NVMe Firmware Log test. + """ + + def setUp(self): + """ Pre Section for TestNVMeFwLogCmd. """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ + Post Section for TestNVMeSimpleTestTemplate. + + - Call super class's destructor. + """ + super().tearDown() + + def get_fw_log(self): + """ Wrapper for executing nvme fw-log. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + err = 0 + fw_log_cmd = "nvme fw-log " + self.ctrl + proc = subprocess.Popen(fw_log_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + fw_log_output = proc.communicate()[0] + print("\n" + fw_log_output + "\n") + err = proc.wait() + return err + + def test_fw_log(self): + """ Testcase main """ + self.assertEqual(self.get_fw_log(), 0) diff --git a/tests/nvme_get_features_test.py b/tests/nvme_get_features_test.py new file mode 100644 index 0000000..974fc34 --- /dev/null +++ b/tests/nvme_get_features_test.py @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +Get Features Testcase:- + +Test the Mandatory features with get features command:- + 1. 01h M Arbitration. + 2. 02h M Power Management. + 3. 04h M Temperature Threshold. + 4. 05h M Error Recovery. + 5. 07h M Number of Queues. + 6. 08h M Interrupt Coalescing. + 7. 09h M Interrupt Vector Configuration. + 8. 0Ah M Write Atomicity Normal. + 9. 0Bh M Asynchronous Event Configuration. +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeGetMandatoryFeatures(TestNVMe): + + """ + Represents Get Features testcase. + + - Attributes: + - feature_id_list : list of the mandatory features. + - get_vector_list_cmd : vector list collection for 09h. + - vector_list_len : number of the interrupt vectors. + """ + + def setUp(self): + """ Pre Section for TestNVMeGetMandatoryFeatures """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + self.feature_id_list = ["0x01", "0x02", "0x04", "0x05", "0x07", + "0x08", "0x09", "0x0A", "0x0B"] + device = self.ctrl.split('/')[-1] + get_vector_list_cmd = "grep " + device + "q /proc/interrupts |" \ + " cut -d : -f 1 | tr -d ' ' | tr '\n' ' '" + proc = subprocess.Popen(get_vector_list_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + self.vector_list_len = len(proc.stdout.read().strip().split(" ")) + + def tearDown(self): + """ Post Section for TestNVMeGetMandatoryFeatures + + Call super class's destructor. + """ + super().tearDown() + + def get_mandatory_features(self, feature_id): + """ Wrapper for NVMe get features command + - Args: + - feature_id : feature id to be used with get feature command. + - Returns: + - None + """ + if str(feature_id) == "0x09": + for vector in range(self.vector_list_len): + get_feat_cmd = "nvme get-feature " + self.ctrl + \ + " --feature-id=" + str(feature_id) + \ + " --cdw11=" + str(vector) + " -H" + proc = subprocess.Popen(get_feat_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + feature_output = proc.communicate()[0] + print(feature_output) + self.assertEqual(proc.wait(), 0) + else: + get_feat_cmd = "nvme get-feature " + self.ctrl + \ + " --feature-id=" + str(feature_id) + " -H" + proc = subprocess.Popen(get_feat_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + feature_output = proc.communicate()[0] + print(feature_output) + self.assertEqual(proc.wait(), 0) + + def test_get_mandatory_features(self): + """ Testcase main """ + for feature_id in self.feature_id_list: + self.get_mandatory_features(feature_id) diff --git a/tests/nvme_get_lba_status_test.py b/tests/nvme_get_lba_status_test.py new file mode 100644 index 0000000..539c493 --- /dev/null +++ b/tests/nvme_get_lba_status_test.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Author: Arunpandian J <apj.arun@samsung.com> + +""" +NVMe LBA Status Log Testcase :- + + 1. Execute get-lba-status on a device. +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeGetLbaStatusCmd(TestNVMe): + + """ + Represents Get LBA Status test. + """ + + def setUp(self): + """ Pre Section for TestNVMeGetLbaStatusCmd. """ + super().setUp() + self.start_lba = 0 + self.block_count = 0 + self.namespace = 1 + self.max_dw = 1 + self.action = 11 + self.range_len = 1 + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ + Post Section for TestNVMeGetLbaStatusCmd. + + - Call super class's destructor. + """ + super().tearDown() + + def get_lba_status(self): + """ Wrapper for executing nvme get-lba-status. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + err = 0 + get_lba_status_cmd = "nvme get-lba-status " + self.ctrl + \ + " --namespace-id=" + str(self.namespace) + \ + " --start-lba=" + str(self.start_lba) + \ + " --max-dw=" + str(self.max_dw) + \ + " --action=" + str(self.action) + \ + " --range-len=" + str(self.range_len) + proc = subprocess.Popen(get_lba_status_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + get_lba_status_output = proc.communicate()[0] + print("\n" + get_lba_status_output + "\n") + err = proc.wait() + return err + + def test_get_lba_status(self): + """ Testcase main """ + self.assertEqual(self.get_lba_status(), 0) diff --git a/tests/nvme_id_ctrl_test.py b/tests/nvme_id_ctrl_test.py new file mode 100644 index 0000000..2810d94 --- /dev/null +++ b/tests/nvme_id_ctrl_test.py @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Madhusudhana S.J <madhusudhana.sj@wdc.com> +# Author: Dong Ho <dong.ho@wdc.com> +# +""" +NVMe Identify ctrl Testcase:- + + 1. Execute id-ctrl on ctrl + 2. Execute id-ctrl vendor specific on ctrl + +""" + +from nvme_test import TestNVMe + + +class TestNVMeIdctrlCmd(TestNVMe): + + """ + Represents Id ctrl testcase + """ + + def setUp(self): + """ Pre Section for TestNVMeIdctrlCmd. """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeIdctrlCmd + + Call super class's destructor. + """ + super().tearDown() + + def test_id_ctrl(self): + """ Testcase main """ + vendor = True + self.assertEqual(self.get_id_ctrl(), 0) + self.assertEqual(self.get_id_ctrl(vendor), 0) diff --git a/tests/nvme_id_ns_test.py b/tests/nvme_id_ns_test.py new file mode 100644 index 0000000..66e2f93 --- /dev/null +++ b/tests/nvme_id_ns_test.py @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Madhusudhana S.J <madhusudhana.sj@wdc.com> +# Author: Dong Ho <dong.ho@wdc.com> +# +""" +NVme Identify Namespace Testcase:- + + 1. Execute id-ns on a namespace + 2. Execute id-ns on all namespaces +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeIdentifyNamespace(TestNVMe): + + """ + Represents Identify Namesepace testcase + """ + + def setUp(self): + """ Pre Section for TestNVMeIdentifyNamespace. """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + self.ns_list = self.get_ns_list() + + def tearDown(self): + """ + Post Section for TestNVMeIdentifyNamespace + + - Call super class's destructor. + """ + super().tearDown() + + def get_id_ns(self, nsid): + """ + Wrapper for executing nvme id-ns on a namespace. + - Args: + - nsid : namespace id to get info from. + - Returns: + - 0 on success, error code on failure. + """ + err = 0 + id_ns_cmd = "nvme id-ns " + self.ctrl + "n" + str(nsid) + proc = subprocess.Popen(id_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + id_ns_output = proc.communicate()[0] + print(id_ns_output + "\n") + err = proc.wait() + return err + + def get_id_ns_all(self): + """ + Wrapper for executing nvme id-ns on all namespaces. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + err = 0 + for namespace in self.ns_list: + err = self.get_id_ns(str(namespace)) + return err + + def test_id_ns(self): + """ Testcase main """ + self.assertEqual(self.get_id_ns(1), 0) + self.assertEqual(self.get_id_ns_all(), 0) diff --git a/tests/nvme_lba_status_log_test.py b/tests/nvme_lba_status_log_test.py new file mode 100644 index 0000000..c91d1e5 --- /dev/null +++ b/tests/nvme_lba_status_log_test.py @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Author: Arunpandian J <apj.arun@samsung.com> + +""" +NVMe LBA Status Log Testcase :- + + 1. Execute lba-status-log on a device. +""" + +import subprocess + +from nvme_test import TestNVMe + + +class TestNVMeLbaStatLogCmd(TestNVMe): + + """ + Represents LBA Status Log test. + """ + + def setUp(self): + """ Pre Section for TestNVMeLbaStatLogCmd. """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ + Post Section for TestNVMeLbaStatLogCmd. + + - Call super class's destructor. + """ + super().tearDown() + + def get_lba_stat_log(self): + """ Wrapper for executing nvme lba-status-log. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + err = 0 + lba_stat_log_cmd = "nvme lba-status-log " + self.ctrl + proc = subprocess.Popen(lba_stat_log_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + lba_stat_log_output = proc.communicate()[0] + print("\n" + lba_stat_log_output + "\n") + err = proc.wait() + return err + + def test_lba_stat_log(self): + """ Testcase main """ + self.assertEqual(self.get_lba_stat_log(), 0) diff --git a/tests/nvme_read_write_test.py b/tests/nvme_read_write_test.py new file mode 100644 index 0000000..8cae140 --- /dev/null +++ b/tests/nvme_read_write_test.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Read/Write Testcae:- + + 1. Create data file with specific pattern outside of the device under test. + 2. Write data file on the namespace under test. + 3. Read the data from the namespace under test into different file. + 4. Compare file in #1 and #3. +""" + +import filecmp + +from nvme_test_io import TestNVMeIO + + +class TestNVMeReadWriteTest(TestNVMeIO): + + """ + Represents NVMe read, write testcase. + + - Attributes: + - start_block : starting block of to perform IO. + - compare_file : data file to use in nvme compare command. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeReadWriteTest """ + super().setUp() + self.start_block = 1023 + self.test_log_dir = self.log_dir + "/" + self.__class__.__name__ + self.setup_log_dir(self.__class__.__name__) + self.write_file = self.test_log_dir + "/" + self.write_file + self.read_file = self.test_log_dir + "/" + self.read_file + self.create_data_file(self.write_file, self.data_size, "15") + open(self.read_file, 'a').close() + + def tearDown(self): + """ Post Section for TestNVMeReadWriteTest """ + super().tearDown() + + def read_validate(self): + """ Validate the data file read + - Args: + - None + - Returns: + - returns 0 on success, 1 on failure. + """ + return 0 if filecmp.cmp(self.read_file, self.write_file) else 1 + + def test_nvme_write(self): + """ Testcaes main """ + self.assertEqual(self.nvme_write(), 0) + self.assertEqual(self.nvme_read(), 0) + self.assertEqual(self.read_validate(), 0) diff --git a/tests/nvme_simple_template_test.py b/tests/nvme_simple_template_test.py new file mode 100644 index 0000000..2adaeb4 --- /dev/null +++ b/tests/nvme_simple_template_test.py @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" Simple Template test example :- +""" + +from nvme_test import TestNVMe + + +class TestNVMeSimpleTestTemplate(TestNVMe): + + """ Represents Simple NVMe test """ + + def setUp(self): + """ Pre Section for TestNVMeSimpleTestTemplate. """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + # Add this test specific variables here + + def tearDown(self): + """ Post Section for TestNVMeSimpleTestTemplate + + Call super class's destructor. + """ + # Add this test specific cleanup code here + super().tearDown() + + def simple_template_test(self): + """ Wrapper for this test specific functions + - Args: + - None + - Returns: + - None + """ + pass + + def test_get_mandetory_features(self): + """ Testcase main """ + self.simple_template_test() diff --git a/tests/nvme_smart_log_test.py b/tests/nvme_smart_log_test.py new file mode 100644 index 0000000..916ef49 --- /dev/null +++ b/tests/nvme_smart_log_test.py @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Smart Log Verification Testcase:- + + 1. Execute smat-log on controller. + 2. Execute smart-log on each available namespace. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeSmartLogCmd(TestNVMe): + + """ + Represents Smart Log testcae. + + - Attributes: + """ + + def setUp(self): + """ Pre Section for TestNVMeSmartLogCmd """ + super().setUp() + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ + Post Section for TestNVMeSmartLogCmd + + - Call super class's destructor. + """ + super().tearDown() + + def get_smart_log_ctrl(self): + """ Wrapper for executing smart-log on controller. + - Args: + - None: + - Returns: + - 0 on success, error code on failure. + """ + return self.get_smart_log("0xFFFFFFFF") + + def get_smart_log_ns(self, nsid): + """ Wrapper for executing smart-log on a namespace. + - Args: + - nsid: namespace id to be used in smart-log command. + - Returns: + - 0 on success, error code on failure. + """ + return self.get_smart_log(nsid) + + def get_smart_log_all_ns(self): + """ Wrapper for executing smart-log on all the namespaces. + - Args: + - None: + - Returns: + - 0 on success, error code on failure. + """ + ns_list = self.get_ns_list() + for nsid in range(0, len(ns_list)): + self.get_smart_log_ns(ns_list[nsid]) + return 0 + + def test_smart_log(self): + """ Testcase main """ + self.assertEqual(self.get_smart_log_ctrl(), 0) + smlp = self.supp_check_id_ctrl("lpa") + if smlp & 0x1 == True: + self.assertEqual(self.get_smart_log_all_ns(), 0) diff --git a/tests/nvme_test.py b/tests/nvme_test.py new file mode 100644 index 0000000..0df3dac --- /dev/null +++ b/tests/nvme_test.py @@ -0,0 +1,504 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" Base class for all the testcases +""" + +import json +import mmap +import os +import re +import shutil +import stat +import subprocess +import sys +import time +import unittest + +from nvme_test_logger import TestNVMeLogger + + +class TestNVMe(unittest.TestCase): + + """ + Represents a testcase, each testcase should inherit this + class or appropriate subclass which is a child of this class. + + Common utility functions used in various testcases. + + - Attributes: + - ctrl : NVMe Controller. + - ns1 : default namespace. + - default_nsid : default namespace id. + - config_file : configuration file. + - clear_log_dir : default log directory. + """ + + def setUp(self): + """ Pre Section for TestNVMe. """ + # common code used in various testcases. + self.ctrl = "XXX" + self.ns1 = "XXX" + self.test_log_dir = "XXX" + self.do_validate_pci_device = True + self.default_nsid = 0x1 + self.config_file = 'tests/config.json' + + self.load_config() + if self.do_validate_pci_device: + self.validate_pci_device() + + def tearDown(self): + """ Post Section for TestNVMe. """ + if self.clear_log_dir is True: + shutil.rmtree(self.log_dir, ignore_errors=True) + + def validate_pci_device(self): + """ Validate underlying device belongs to pci subsystem. + - Args: + - None + - Returns: + - None + """ + x1, x2, dev = self.ctrl.split('/') + cmd = cmd = "find /sys/devices -name \\*" + dev + " | grep -i pci" + err = subprocess.call(cmd, shell=True) + self.assertEqual(err, 0, "ERROR : Only NVMe PCI subsystem is supported") + + def load_config(self): + """ Load Basic test configuration. + - Args: + - None + - Returns: + - None + """ + with open(self.config_file) as data_file: + config = json.load(data_file) + self.ctrl = config['controller'] + self.ns1 = config['ns1'] + self.log_dir = config['log_dir'] + self.do_validate_pci_device = config.get('do_validate_pci_device', self.do_validate_pci_device) + self.clear_log_dir = False + + if self.clear_log_dir is True: + shutil.rmtree(self.log_dir, ignore_errors=True) + + if not os.path.exists(self.log_dir): + os.makedirs(self.log_dir) + + def setup_log_dir(self, test_name): + """ Set up the log directory for a testcase + Args: + - test_name : name of the testcase. + Returns: + - None + """ + self.test_log_dir = self.log_dir + "/" + test_name + if not os.path.exists(self.test_log_dir): + os.makedirs(self.test_log_dir) + sys.stdout = TestNVMeLogger(self.test_log_dir + "/" + "stdout.log") + sys.stderr = TestNVMeLogger(self.test_log_dir + "/" + "stderr.log") + + def exec_cmd(self, cmd): + """ Wrapper for executing a shell command and return the result. """ + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + return proc.wait() + + def nvme_reset_ctrl(self): + """ Wrapper for nvme reset command. + - Args: + - None: + - Returns: + - None + """ + nvme_reset_cmd = "nvme reset " + self.ctrl + err = subprocess.call(nvme_reset_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + self.assertEqual(err, 0, "ERROR : nvme reset failed") + time.sleep(5) + rescan_cmd = "echo 1 > /sys/bus/pci/rescan" + proc = subprocess.Popen(rescan_cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding='utf-8') + time.sleep(5) + self.assertEqual(proc.wait(), 0, "ERROR : pci rescan failed") + + def get_ctrl_id(self): + """ Wrapper for extracting the controller id. + - Args: + - None + - Returns: + - controller id. + """ + get_ctrl_id = "nvme list-ctrl " + self.ctrl + proc = subprocess.Popen(get_ctrl_id, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : nvme list-ctrl failed") + line = proc.stdout.readline() + ctrl_id = line.split(":")[1].strip() + return ctrl_id + + def get_ns_list(self): + """ Wrapper for extracting the namespace list. + - Args: + - None + - Returns: + - List of the namespaces. + """ + ns_list = [] + ns_list_cmd = "nvme list-ns " + self.ctrl + proc = subprocess.Popen(ns_list_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + self.assertEqual(proc.wait(), 0, "ERROR : nvme list namespace failed") + for line in proc.stdout: + ns_list.append(line.split('x')[-1]) + + return ns_list + + def get_max_ns(self): + """ Wrapper for extracting maximum number of namespaces supported. + - Args: + - None + - Returns: + - maximum number of namespaces supported. + """ + pattern = re.compile("^nn[ ]+: [0-9]", re.IGNORECASE) + max_ns = -1 + max_ns_cmd = "nvme id-ctrl " + self.ctrl + proc = subprocess.Popen(max_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : reading maximum namespace count failed") + + for line in proc.stdout: + if pattern.match(line): + max_ns = line.split(":")[1].strip() + break + print(max_ns) + return int(max_ns) + + def get_ncap(self): + """ Wrapper for extracting capacity. + - Args: + - None + - Returns: + - maximum number of namespaces supported. + """ + pattern = re.compile("^tnvmcap[ ]+: [0-9]", re.IGNORECASE) + ncap = -1 + ncap_cmd = "nvme id-ctrl " + self.ctrl + proc = subprocess.Popen(ncap_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : reading nvm capacity failed") + + for line in proc.stdout: + if pattern.match(line): + ncap = line.split(":")[1].strip() + break + print(ncap) + return int(ncap) + + def get_ocfs(self): + """ Wrapper for extracting optional copy formats supported + - Args: + - None + - Returns: + - Optional Copy Formats Supported + """ + pattern = re.compile(r'^ocfs\s*: 0x[0-9a-fA-F]+$') + output = subprocess.check_output(["nvme", "id-ctrl", self.ctrl], encoding='utf-8') + ocfs_line = next(line for line in output.splitlines() if pattern.match(line)) + ocfs = ocfs_line.split(":")[1].strip() + return int(ocfs, 16) + + def get_format(self): + """ Wrapper for extracting format. + - Args: + - None + - Returns: + - maximum format of namespace. + """ + # defaulting to 4K + nvm_format = 4096 + nvm_format_cmd = "nvme id-ns " + self.ctrl + " -n1" + proc = subprocess.Popen(nvm_format_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : reading nvm capacity failed") + + for line in proc.stdout: + if "in use" in line: + nvm_format = 2 ** int(line.split(":")[3].split()[0]) + print(nvm_format) + return int(nvm_format) + + def delete_all_ns(self): + """ Wrapper for deleting all the namespaces. + - Args: + - None + - Returns: + - None + """ + delete_ns_cmd = "nvme delete-ns " + self.ctrl + " -n 0xFFFFFFFF" + self.assertEqual(self.exec_cmd(delete_ns_cmd), 0) + list_ns_cmd = "nvme list-ns " + self.ctrl + " --all | wc -l" + proc = subprocess.Popen(list_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + output = proc.stdout.read().strip() + self.assertEqual(output, '0', "ERROR : deleting all namespace failed") + + def create_ns(self, nsze, ncap, flbas, dps): + """ Wrapper for creating a namespace. + - Args: + - nsze : new namespace size. + - ncap : new namespace capacity. + - flbas : new namespace format. + - dps : new namespace data protection information. + - Returns: + - return code of the nvme create namespace command. + """ + create_ns_cmd = "nvme create-ns " + self.ctrl + " --nsze=" + \ + str(nsze) + " --ncap=" + str(ncap) + \ + " --flbas=" + str(flbas) + " --dps=" + str(dps) + return self.exec_cmd(create_ns_cmd) + + def create_and_validate_ns(self, nsid, nsze, ncap, flbas, dps): + """ Wrapper for creating and validating a namespace. + - Args: + - nsid : new namespace id. + - nsze : new namespace size. + - ncap : new namespace capacity. + - flbas : new namespace format. + - dps : new namespace data protection information. + - Returns: + - return 0 on success, error code on failure. + """ + err = self.create_ns(nsze, ncap, flbas, dps) + if err == 0: + time.sleep(2) + id_ns_cmd = "nvme id-ns " + self.ctrl + " -n " + str(nsid) + err = subprocess.call(id_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + return err + + def attach_ns(self, ctrl_id, ns_id): + """ Wrapper for attaching the namespace. + - Args: + - ctrl_id : controller id to which namespace to be attached. + - nsid : new namespace id. + - Returns: + - 0 on success, error code on failure. + """ + attach_ns_cmd = "nvme attach-ns " + self.ctrl + \ + " --namespace-id=" + str(ns_id) + \ + " --controllers=" + ctrl_id + err = subprocess.call(attach_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + time.sleep(5) + if err == 0: + # enumerate new namespace block device + self.nvme_reset_ctrl() + time.sleep(5) + # check if new namespace block device exists + err = 0 if stat.S_ISBLK(os.stat(self.ns1).st_mode) else 1 + return err + + def detach_ns(self, ctrl_id, nsid): + """ Wrapper for detaching the namespace. + - Args: + - ctrl_id : controller id to which namespace to be attached. + - nsid : new namespace id. + - Returns: + - 0 on success, error code on failure. + """ + detach_ns_cmd = "nvme detach-ns " + self.ctrl + \ + " --namespace-id=" + str(nsid) + \ + " --controllers=" + ctrl_id + return subprocess.call(detach_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + + def delete_and_validate_ns(self, nsid): + """ Wrapper for deleting and validating that namespace is deleted. + - Args: + - nsid : new namespace id. + - Returns: + - 0 on success, 1 on failure. + """ + # delete the namespace + delete_ns_cmd = "nvme delete-ns " + self.ctrl + " -n " + str(nsid) + err = subprocess.call(delete_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + self.assertEqual(err, 0, "ERROR : delete namespace failed") + return err + + def get_smart_log(self, nsid): + """ Wrapper for nvme smart-log command. + - Args: + - nsid : namespace id to get smart log from. + - Returns: + - 0 on success, error code on failure. + """ + smart_log_cmd = "nvme smart-log " + self.ctrl + " -n " + str(nsid) + print(smart_log_cmd) + proc = subprocess.Popen(smart_log_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : nvme smart log failed") + + for line in proc.stdout: + if "data_units_read" in line: + data_units_read = \ + line.replace(",", "", 1) + if "data_units_written" in line: + data_units_written = \ + line.replace(",", "", 1) + if "host_read_commands" in line: + host_read_commands = \ + line.replace(",", "", 1) + if "host_write_commands" in line: + host_write_commands = \ + line.replace(",", "", 1) + + print("data_units_read " + data_units_read) + print("data_units_written " + data_units_written) + print("host_read_commands " + host_read_commands) + print("host_write_commands " + host_write_commands) + return err + + def get_id_ctrl(self, vendor=False): + """ Wrapper for nvme id-ctrl command. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + if not vendor: + id_ctrl_cmd = "nvme id-ctrl " + self.ctrl + else: + id_ctrl_cmd = "nvme id-ctrl -v " + self.ctrl + print(id_ctrl_cmd) + proc = subprocess.Popen(id_ctrl_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : nvme id controller failed") + return err + + def get_error_log(self): + """ Wrapper for nvme error-log command. + - Args: + - None + - Returns: + - 0 on success, error code on failure. + """ + pattern = re.compile("^ Entry\[[ ]*[0-9]+\]") + error_log_cmd = "nvme error-log " + self.ctrl + proc = subprocess.Popen(error_log_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : nvme error log failed") + line = proc.stdout.readline() + err_log_entry_count = int(line.split(" ")[5].strip().split(":")[1]) + entry_count = 0 + for line in proc.stdout: + if pattern.match(line): + entry_count += 1 + + return 0 if err_log_entry_count == entry_count else 1 + + def run_ns_io(self, nsid, lbads): + """ Wrapper to run ios on namespace under test. + - Args: + - lbads : LBA Data size supported in power of 2 format. + - Returns: + - None + """ + block_size = mmap.PAGESIZE if int(lbads) < 9 else 2 ** int(lbads) + ns_path = self.ctrl + "n" + str(nsid) + io_cmd = "dd if=" + ns_path + " of=/dev/null" + " bs=" + \ + str(block_size) + " count=10 > /dev/null 2>&1" + print(io_cmd) + run_io = subprocess.Popen(io_cmd, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + run_io_result = run_io.communicate()[1] + self.assertEqual(run_io_result, None) + io_cmd = "dd if=/dev/zero of=" + ns_path + " bs=" + \ + str(block_size) + " count=10 > /dev/null 2>&1" + print(io_cmd) + run_io = subprocess.Popen(io_cmd, shell=True, stdout=subprocess.PIPE, + encoding='utf-8') + run_io_result = run_io.communicate()[1] + self.assertEqual(run_io_result, None) + + def supp_check_id_ctrl(self, key): + """ Wrapper for support check. + - Args: + - key : search key. + - Returns: + - value for key requested. + """ + id_ctrl = "nvme id-ctrl " + self.ctrl + print("\n" + id_ctrl) + proc = subprocess.Popen(id_ctrl, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : nvme Identify controller Data \ + structure failed") + for line in proc.stdout: + if key in line: + key = line.replace(",", "", 1) + print(key) + val = (key.split(':'))[1].strip() + return int(val, 16) diff --git a/tests/nvme_test_io.py b/tests/nvme_test_io.py new file mode 100644 index 0000000..bf30e0a --- /dev/null +++ b/tests/nvme_test_io.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" Inherit TestNVMeIO for nvme read/write operations """ + +import os + +from nvme_test import TestNVMe + + +class TestNVMeIO(TestNVMe): + + """ + Variable and Methods required to perform nvme read/write. + + - Attributes: + - data_size : data size to perform IO. + - start_block : starting block of to perform IO. + - block_count : Number of blocks to use in IO. + - write_file : data file to use in nvme write command. + - read_file : data file to use in nvme read command. + """ + + def setUp(self): + """ Pre Section for TestNVMeIO """ + super().setUp() + # common code used in various testcases. + self.data_size = 512 + self.start_block = 0 + self.block_count = 0 + self.write_file = "write_file.txt" + self.read_file = "read_file.txt" + + def tearDown(self): + """ Post Section for TestNVMeIO """ + super().tearDown() + + def create_data_file(self, pathname, data_size, pattern): + """ Creates data file with specific pattern + - Args: + - pathname : data file path name. + - data_size : total size of the data. + - pattern : data pattern to create file. + - Returns: + None + """ + pattern_len = len(pattern) + data_file = open(pathname, "w") + for i in range(0, data_size): + data_file.write(pattern[i % pattern_len]) + data_file.flush() + os.fsync(data_file.fileno()) + data_file.close() + + def nvme_write(self): + """ Wrapper for nvme write operation + - Args: + - None + - Returns: + - return code for nvme write command. + """ + write_cmd = "nvme write " + self.ns1 + " --start-block=" + \ + str(self.start_block) + " --block-count=" + \ + str(self.block_count) + " --data-size=" + \ + str(self.data_size) + " --data=" + self.write_file + return self.exec_cmd(write_cmd) + + def nvme_read(self): + """ Wrapper for nvme read operation + - Args: + - None + - Returns: + - return code for nvme read command. + """ + read_cmd = "nvme read " + self.ns1 + " --start-block=" + \ + str(self.start_block) + " --block-count=" + \ + str(self.block_count) + " --data-size=" + \ + str(self.data_size) + " --data=" + self.read_file + print(read_cmd) + return self.exec_cmd(read_cmd) diff --git a/tests/nvme_test_logger.py b/tests/nvme_test_logger.py new file mode 100644 index 0000000..d0182fd --- /dev/null +++ b/tests/nvme_test_logger.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +Logger for NVMe Test Framwwork:- + +""" +import sys + + +class TestNVMeLogger(): + """ Represents Logger for NVMe Testframework. """ + + def __init__(self, log_file_path): + """ Logger setup + - Args: + log_file_path : path to store the log. + """ + self.terminal = sys.stdout + self.log = open(log_file_path, "w") + + def write(self, log_message): + """ Logger setup + - Args: + log_message: string to write in the log file. + - Returns: + None + """ + self.terminal.write(log_message) + self.log.write(log_message) + + def flush(self): + """ This flush method is needed for python 3 compatibility. + this handles the flush command by doing nothing. + you might want to specify some extra behavior here. + """ + pass diff --git a/tests/nvme_verify_test.py b/tests/nvme_verify_test.py new file mode 100644 index 0000000..7c30828 --- /dev/null +++ b/tests/nvme_verify_test.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This file is part of nvme-cli +# +# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. +# +# Author: Arunpandian J <apj.arun@samsung.com> + +""" +NVMe Verify Testcase:- + + 1. Issue verify command on set of block; shall pass. + +""" + +from nvme_test import TestNVMe + + +class TestNVMeVerify(TestNVMe): + + """ + Represents NVMe Verify testcase. + - Attributes: + - start_block : starting block of to verify operation. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeVerify """ + super().setUp() + self.start_block = 0 + self.block_count = 0 + self.namespace = 1 + self.setup_log_dir(self.__class__.__name__) + + def tearDown(self): + """ Post Section for TestNVMeVerify """ + super().tearDown() + + def verify(self): + """ Wrapper for nvme verify + - Args: + - None + - Returns: + - return code for nvme verify command. + """ + verify_cmd = "nvme verify " + self.ctrl + \ + " --namespace-id=" + str(self.namespace) + \ + " --start-block=" + str(self.start_block) + \ + " --block-count=" + str(self.block_count) + return self.exec_cmd(verify_cmd) + + def test_verify(self): + """ Testcase main """ + self.assertEqual(self.verify(), 0) diff --git a/tests/nvme_writeuncor_test.py b/tests/nvme_writeuncor_test.py new file mode 100644 index 0000000..1083d46 --- /dev/null +++ b/tests/nvme_writeuncor_test.py @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Write Compare Testcae:- + + 1. Read block of data successfully. + 2. Issue write uncorrectable to block of data. + 3. Attempt to read from same block; shall fail. + 4. Issue a write command to first block of data. + 5. Read from the same block; shall pass. + +""" + +from nvme_test_io import TestNVMeIO + + +class TestNVMeUncor(TestNVMeIO): + + """ + Represents NVMe Write Uncorrecatble testcase. + - Attributes: + - start_block : starting block of to perform IO. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Constructor TestNVMeUncor """ + super().setUp() + self.start_block = 1023 + self.setup_log_dir(self.__class__.__name__) + self.write_file = self.test_log_dir + "/" + self.write_file + self.read_file = self.test_log_dir + "/" + self.read_file + self.create_data_file(self.write_file, self.data_size, "15") + open(self.read_file, 'a').close() + + def tearDown(self): + """ Post Section for TestNVMeUncor """ + super().tearDown() + + def write_uncor(self): + """ Wrapper for nvme write uncorrectable + - Args: + - None + - Returns: + - return code of nvme write uncorrectable command. + """ + write_uncor_cmd = "nvme write-uncor " + self.ns1 + \ + " --start-block=" + str(self.start_block) + \ + " --block-count=" + str(self.block_count) + return self.exec_cmd(write_uncor_cmd) + + def test_write_uncor(self): + """ Testcase main """ + self.assertEqual(self.nvme_read(), 0) + self.assertEqual(self.write_uncor(), 0) + self.assertNotEqual(self.nvme_read(), 0) + self.assertEqual(self.nvme_write(), 0) + self.assertEqual(self.nvme_read(), 0) diff --git a/tests/nvme_writezeros_test.py b/tests/nvme_writezeros_test.py new file mode 100644 index 0000000..3231e3d --- /dev/null +++ b/tests/nvme_writezeros_test.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2015-2016 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com> +# +""" +NVMe Write Zeros:- + + 1. Issue a write command to block of data. + 2. Read from same block to verify data pattern. + 3. Issue write zeros to the block of data. + 4. Read from same block, should be all zeroes. + +""" + +import filecmp + +from nvme_test_io import TestNVMeIO + + +class TestNVMeWriteZeros(TestNVMeIO): + + """ + Represents NVMe Write Zero Testcase. + + - Attributes: + - zero_file : file with all '\0' to compare the zero data. + - data_size : data size to perform IO. + - start_block : starting block of to perform IO. + - block_count: Number of blocks to use in IO. + - test_log_dir : directory for logs, temp files. + """ + + def setUp(self): + """ Pre Section for TestNVMeWriteZeros """ + super().setUp() + self.start_block = 1023 + self.block_count = 0 + self.setup_log_dir(self.__class__.__name__) + self.write_file = self.test_log_dir + "/" + self.write_file + self.read_file = self.test_log_dir + "/" + self.read_file + self.zero_file = self.test_log_dir + "/" + "zero_file.txt" + self.create_data_file(self.write_file, self.data_size, "15") + self.create_data_file(self.zero_file, self.data_size, '\0') + open(self.read_file, 'a').close() + + def tearDown(self): + """ Post Section for TestNVMeWriteZeros """ + super().tearDown() + + def write_zeroes(self): + """ Wrapper for nvme write-zeroe + - Args: + - None + - Returns: + - return code for nvme write command. + """ + write_zeroes_cmd = "nvme write-zeroes " + self.ns1 + \ + " --start-block=" + str(self.start_block) + \ + " --block-count=" + str(self.block_count) + return self.exec_cmd(write_zeroes_cmd) + + def validate_write_read(self): + """ Validate the file which had been read from the device + - Args: + - None + - Returns: + - 0 on success, 1 on failure + """ + return 0 if filecmp.cmp(self.write_file, self.read_file) is True else 1 + + def validate_zeroes(self): + """ + Validate the data which is zeroed out via write-zeroes + - Args: + - None + - Returns: + - 0 on success, 1 on failure + """ + return 0 if filecmp.cmp(self.zero_file, self.read_file) is True else 1 + + def test_write_zeros(self): + """ Testcae main """ + self.assertEqual(self.nvme_write(), 0) + self.assertEqual(self.nvme_read(), 0) + self.assertEqual(self.validate_write_read(), 0) + self.assertEqual(self.write_zeroes(), 0) + self.assertEqual(self.nvme_read(), 0) + self.assertEqual(self.validate_zeroes(), 0) diff --git a/tests/run_py_linters.py b/tests/run_py_linters.py new file mode 100644 index 0000000..869b3e4 --- /dev/null +++ b/tests/run_py_linters.py @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Copied from https://github.com/python-sdbus/python-sdbus +# Copyright (C) 2020, 2021 igo95862 + +# This file is part of nvme-cli + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. + +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +from __future__ import annotations + +from argparse import ArgumentParser +from os import environ +from pathlib import Path +from subprocess import run +from typing import List + +source_root = Path(environ['MESON_SOURCE_ROOT']) +build_dir = Path(environ['MESON_BUILD_ROOT']) + +tests_dir = source_root / 'tests' + +all_python_modules = [ + tests_dir, +] + +mypy_cache_dir = build_dir / '.mypy_cache' + + +def run_mypy(path: Path) -> None: + print(f"Running mypy on {path}") + run( + args=( + 'mypy', '--strict', + '--cache-dir', mypy_cache_dir, + '--python-version', '3.8', + '--namespace-packages', + '--ignore-missing-imports', + path, + ), + check=False, + env={'MYPYPATH': str(tests_dir.absolute()), **environ}, + ) + + +def linter_main() -> None: + run( + args=( + 'flake8', + *all_python_modules, + ), + check=False, + ) + + for x in all_python_modules: + run_mypy(x) + + +def get_all_python_files() -> List[Path]: + python_files: List[Path] = [] + + for python_module in all_python_modules: + if python_module.is_dir(): + for a_file in python_module.iterdir(): + if a_file.suffix == '.py': + python_files.append(a_file) + else: + python_files.append(python_module) + + return python_files + + +def formater_main() -> None: + all_python_files = get_all_python_files() + + run( + args=('autopep8', '--in-place', *all_python_files), + check=False, + ) + + run( + args=( + 'isort', + '-m', 'VERTICAL_HANGING_INDENT', + '--trailing-comma', + *all_python_files, + ), + check=False, + ) + + +def main() -> None: + parser = ArgumentParser() + parser.add_argument( + 'mode', + choices=('lint', 'format'), + ) + + args = parser.parse_args() + + mode = args.mode + + if mode == 'lint': + linter_main() + elif mode == 'format': + formater_main() + else: + raise ValueError('Unknown mode', mode) + + +if __name__ == '__main__': + main() |