// SPDX-License-Identifier: GPL-3.0-or-later package nvme import ( "encoding/json" "errors" "fmt" "os" "testing" "github.com/netdata/netdata/go/go.d.plugin/agent/module" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( dataConfigJSON, _ = os.ReadFile("testdata/config.json") dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") dataNVMeListJSON, _ = os.ReadFile("testdata/nvme-list.json") dataNVMeListEmptyJSON, _ = os.ReadFile("testdata/nvme-list-empty.json") dataNVMeSmartLogJSON, _ = os.ReadFile("testdata/nvme-smart-log.json") dataNVMeSmartLogStringJSON, _ = os.ReadFile("testdata/nvme-smart-log-string.json") dataNVMeSmartLogFloatJSON, _ = os.ReadFile("testdata/nvme-smart-log-float.json") ) func Test_testDataIsValid(t *testing.T) { for name, data := range map[string][]byte{ "dataConfigJSON": dataConfigJSON, "dataConfigYAML": dataConfigYAML, "dataNVMeListJSON": dataNVMeListJSON, "dataNVMeListEmptyJSON": dataNVMeListEmptyJSON, "dataNVMeSmartLogStringJSON": dataNVMeSmartLogStringJSON, "dataNVMeSmartLogFloatJSON": dataNVMeSmartLogFloatJSON, } { require.NotNil(t, data, name) } } func TestNVMe_ConfigurationSerialize(t *testing.T) { module.TestConfigurationSerialize(t, &NVMe{}, dataConfigJSON, dataConfigYAML) } func TestNVMe_Init(t *testing.T) { tests := map[string]struct { prepare func(n *NVMe) wantFail bool }{ "fails if 'binary_path' not set": { wantFail: true, prepare: func(n *NVMe) { n.BinaryPath = "" }, }, "fails if can't locate nvme-cli": { wantFail: true, prepare: func(n *NVMe) { n.BinaryPath += "!!!" }, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { nv := New() test.prepare(nv) if test.wantFail { assert.Error(t, nv.Init()) } else { assert.NoError(t, nv.Init()) } }) } } func TestNVMe_Charts(t *testing.T) { assert.NotNil(t, New().Charts()) } func TestNVMe_Cleanup(t *testing.T) { assert.NotPanics(t, New().Cleanup) } func TestNVMe_Check(t *testing.T) { tests := map[string]struct { wantFail bool prepare func(n *NVMe) }{ "success if all calls successful": { wantFail: false, prepare: prepareCaseOK, }, "fails if 'nvme list' returns an empty list": { wantFail: true, prepare: prepareCaseEmptyList, }, "fails if 'nvme list' returns an error": { wantFail: true, prepare: prepareCaseErrOnList, }, "fails if 'nvme smart-log' returns an error": { wantFail: true, prepare: prepareCaseErrOnSmartLog, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { n := New() test.prepare(n) if test.wantFail { assert.Error(t, n.Check()) } else { assert.NoError(t, n.Check()) } }) } } func TestNVMe_Collect(t *testing.T) { type testCaseStep struct { prepare func(n *NVMe) check func(t *testing.T, n *NVMe) } tests := map[string][]testCaseStep{ "success if all calls successful": { { prepare: prepareCaseOK, check: func(t *testing.T, n *NVMe) { mx := n.Collect() expected := map[string]int64{ "device_nvme0n1_available_spare": 100, "device_nvme0n1_controller_busy_time": 497040, "device_nvme0n1_critical_comp_time": 0, "device_nvme0n1_critical_warning_available_spare": 0, "device_nvme0n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme0n1_critical_warning_persistent_memory_read_only": 0, "device_nvme0n1_critical_warning_read_only": 0, "device_nvme0n1_critical_warning_temp_threshold": 0, "device_nvme0n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme0n1_data_units_read": 5068041216000, "device_nvme0n1_data_units_written": 69712734208000, "device_nvme0n1_host_read_commands": 313528805, "device_nvme0n1_host_write_commands": 1928062610, "device_nvme0n1_media_errors": 0, "device_nvme0n1_num_err_log_entries": 110, "device_nvme0n1_percentage_used": 2, "device_nvme0n1_power_cycles": 64, "device_nvme0n1_power_on_time": 17906400, "device_nvme0n1_temperature": 36, "device_nvme0n1_thm_temp1_total_time": 0, "device_nvme0n1_thm_temp1_trans_count": 0, "device_nvme0n1_thm_temp2_total_time": 0, "device_nvme0n1_thm_temp2_trans_count": 0, "device_nvme0n1_unsafe_shutdowns": 39, "device_nvme0n1_warning_temp_time": 0, "device_nvme1n1_available_spare": 100, "device_nvme1n1_controller_busy_time": 497040, "device_nvme1n1_critical_comp_time": 0, "device_nvme1n1_critical_warning_available_spare": 0, "device_nvme1n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme1n1_critical_warning_persistent_memory_read_only": 0, "device_nvme1n1_critical_warning_read_only": 0, "device_nvme1n1_critical_warning_temp_threshold": 0, "device_nvme1n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme1n1_data_units_read": 5068041216000, "device_nvme1n1_data_units_written": 69712734208000, "device_nvme1n1_host_read_commands": 313528805, "device_nvme1n1_host_write_commands": 1928062610, "device_nvme1n1_media_errors": 0, "device_nvme1n1_num_err_log_entries": 110, "device_nvme1n1_percentage_used": 2, "device_nvme1n1_power_cycles": 64, "device_nvme1n1_power_on_time": 17906400, "device_nvme1n1_temperature": 36, "device_nvme1n1_thm_temp1_total_time": 0, "device_nvme1n1_thm_temp1_trans_count": 0, "device_nvme1n1_thm_temp2_total_time": 0, "device_nvme1n1_thm_temp2_trans_count": 0, "device_nvme1n1_unsafe_shutdowns": 39, "device_nvme1n1_warning_temp_time": 0, } assert.Equal(t, expected, mx) }, }, }, "success if all calls successful with string values": { { prepare: prepareCaseStringValuesOK, check: func(t *testing.T, n *NVMe) { mx := n.Collect() expected := map[string]int64{ "device_nvme0n1_available_spare": 100, "device_nvme0n1_controller_busy_time": 497040, "device_nvme0n1_critical_comp_time": 0, "device_nvme0n1_critical_warning_available_spare": 0, "device_nvme0n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme0n1_critical_warning_persistent_memory_read_only": 0, "device_nvme0n1_critical_warning_read_only": 0, "device_nvme0n1_critical_warning_temp_threshold": 0, "device_nvme0n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme0n1_data_units_read": 5068041216000, "device_nvme0n1_data_units_written": 69712734208000, "device_nvme0n1_host_read_commands": 313528805, "device_nvme0n1_host_write_commands": 1928062610, "device_nvme0n1_media_errors": 0, "device_nvme0n1_num_err_log_entries": 110, "device_nvme0n1_percentage_used": 2, "device_nvme0n1_power_cycles": 64, "device_nvme0n1_power_on_time": 17906400, "device_nvme0n1_temperature": 36, "device_nvme0n1_thm_temp1_total_time": 0, "device_nvme0n1_thm_temp1_trans_count": 0, "device_nvme0n1_thm_temp2_total_time": 0, "device_nvme0n1_thm_temp2_trans_count": 0, "device_nvme0n1_unsafe_shutdowns": 39, "device_nvme0n1_warning_temp_time": 0, "device_nvme1n1_available_spare": 100, "device_nvme1n1_controller_busy_time": 497040, "device_nvme1n1_critical_comp_time": 0, "device_nvme1n1_critical_warning_available_spare": 0, "device_nvme1n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme1n1_critical_warning_persistent_memory_read_only": 0, "device_nvme1n1_critical_warning_read_only": 0, "device_nvme1n1_critical_warning_temp_threshold": 0, "device_nvme1n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme1n1_data_units_read": 5068041216000, "device_nvme1n1_data_units_written": 69712734208000, "device_nvme1n1_host_read_commands": 313528805, "device_nvme1n1_host_write_commands": 1928062610, "device_nvme1n1_media_errors": 0, "device_nvme1n1_num_err_log_entries": 110, "device_nvme1n1_percentage_used": 2, "device_nvme1n1_power_cycles": 64, "device_nvme1n1_power_on_time": 17906400, "device_nvme1n1_temperature": 36, "device_nvme1n1_thm_temp1_total_time": 0, "device_nvme1n1_thm_temp1_trans_count": 0, "device_nvme1n1_thm_temp2_total_time": 0, "device_nvme1n1_thm_temp2_trans_count": 0, "device_nvme1n1_unsafe_shutdowns": 39, "device_nvme1n1_warning_temp_time": 0, } assert.Equal(t, expected, mx) }, }, }, "success if all calls successful with float values": { { prepare: prepareCaseFloatValuesOK, check: func(t *testing.T, n *NVMe) { mx := n.Collect() expected := map[string]int64{ "device_nvme0n1_available_spare": 100, "device_nvme0n1_controller_busy_time": 497040, "device_nvme0n1_critical_comp_time": 0, "device_nvme0n1_critical_warning_available_spare": 0, "device_nvme0n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme0n1_critical_warning_persistent_memory_read_only": 0, "device_nvme0n1_critical_warning_read_only": 0, "device_nvme0n1_critical_warning_temp_threshold": 0, "device_nvme0n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme0n1_data_units_read": 5068041216000, "device_nvme0n1_data_units_written": 69712734208000, "device_nvme0n1_host_read_commands": 313528805, "device_nvme0n1_host_write_commands": 1928062610, "device_nvme0n1_media_errors": 0, "device_nvme0n1_num_err_log_entries": 110, "device_nvme0n1_percentage_used": 2, "device_nvme0n1_power_cycles": 64, "device_nvme0n1_power_on_time": 17906400, "device_nvme0n1_temperature": 36, "device_nvme0n1_thm_temp1_total_time": 0, "device_nvme0n1_thm_temp1_trans_count": 0, "device_nvme0n1_thm_temp2_total_time": 0, "device_nvme0n1_thm_temp2_trans_count": 0, "device_nvme0n1_unsafe_shutdowns": 39, "device_nvme0n1_warning_temp_time": 0, "device_nvme1n1_available_spare": 100, "device_nvme1n1_controller_busy_time": 497040, "device_nvme1n1_critical_comp_time": 0, "device_nvme1n1_critical_warning_available_spare": 0, "device_nvme1n1_critical_warning_nvm_subsystem_reliability": 0, "device_nvme1n1_critical_warning_persistent_memory_read_only": 0, "device_nvme1n1_critical_warning_read_only": 0, "device_nvme1n1_critical_warning_temp_threshold": 0, "device_nvme1n1_critical_warning_volatile_mem_backup_failed": 0, "device_nvme1n1_data_units_read": 5068041216000, "device_nvme1n1_data_units_written": 69712734208000, "device_nvme1n1_host_read_commands": 313528805, "device_nvme1n1_host_write_commands": 1928062610, "device_nvme1n1_media_errors": 0, "device_nvme1n1_num_err_log_entries": 110, "device_nvme1n1_percentage_used": 2, "device_nvme1n1_power_cycles": 64, "device_nvme1n1_power_on_time": 17906400, "device_nvme1n1_temperature": 36, "device_nvme1n1_thm_temp1_total_time": 0, "device_nvme1n1_thm_temp1_trans_count": 0, "device_nvme1n1_thm_temp2_total_time": 0, "device_nvme1n1_thm_temp2_trans_count": 0, "device_nvme1n1_unsafe_shutdowns": 39, "device_nvme1n1_warning_temp_time": 0, } assert.Equal(t, expected, mx) }, }, }, "fail if 'nvme list' returns an empty list": { { prepare: prepareCaseEmptyList, check: func(t *testing.T, n *NVMe) { mx := n.Collect() assert.Equal(t, (map[string]int64)(nil), mx) }, }, }, "fail if 'nvme list' returns an error": { { prepare: prepareCaseErrOnList, check: func(t *testing.T, n *NVMe) { mx := n.Collect() assert.Equal(t, (map[string]int64)(nil), mx) }, }, }, "fail if 'nvme smart-log' returns an error": { { prepare: prepareCaseErrOnSmartLog, check: func(t *testing.T, n *NVMe) { mx := n.Collect() assert.Equal(t, (map[string]int64)(nil), mx) }, }, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { n := New() for i, step := range test { t.Run(fmt.Sprintf("step[%d]", i), func(t *testing.T) { step.prepare(n) step.check(t, n) }) } }) } } func prepareCaseOK(n *NVMe) { n.exec = &mockNVMeCLIExec{} } func prepareCaseStringValuesOK(n *NVMe) { n.exec = &mockNVMeCLIExec{smartLogString: true} } func prepareCaseFloatValuesOK(n *NVMe) { n.exec = &mockNVMeCLIExec{smartLogFloat: true} } func prepareCaseEmptyList(n *NVMe) { n.exec = &mockNVMeCLIExec{emptyList: true} } func prepareCaseErrOnList(n *NVMe) { n.exec = &mockNVMeCLIExec{errOnList: true} } func prepareCaseErrOnSmartLog(n *NVMe) { n.exec = &mockNVMeCLIExec{errOnSmartLog: true} } type mockNVMeCLIExec struct { errOnList bool errOnSmartLog bool emptyList bool smartLogString bool smartLogFloat bool } func (m *mockNVMeCLIExec) list() (*nvmeDeviceList, error) { if m.errOnList { return nil, errors.New("mock.list() error") } data := dataNVMeListJSON if m.emptyList { data = dataNVMeListEmptyJSON } var v nvmeDeviceList if err := json.Unmarshal(data, &v); err != nil { return nil, err } return &v, nil } func (m *mockNVMeCLIExec) smartLog(_ string) (*nvmeDeviceSmartLog, error) { if m.errOnSmartLog { return nil, errors.New("mock.smartLog() error") } if m.emptyList { return nil, errors.New("mock.smartLog() no devices error") } data := dataNVMeSmartLogJSON if m.smartLogString { data = dataNVMeSmartLogStringJSON } if m.smartLogFloat { data = dataNVMeSmartLogFloatJSON } var v nvmeDeviceSmartLog if err := json.Unmarshal(data, &v); err != nil { return nil, err } return &v, nil }