-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

---drive_start_slot_helper 单元测试，覆盖 find_matching_configuration_index、check_configuration_continuity、
---calculate_start_slot_from_config、update_backplane_start_slot_if_pending

local lu = require 'luaunit'
local drive_start_slot_helper = require 'unit_manager.drive_start_slot_helper'

TestDriveStartSlotHelper = {}

-- DriveStartSlotConfigItem 下标：1=UID, 2=Slot, 3=ChildSlotType, 4=ChildSlotIndex, 5=ChildStartSlot
local function make_config_item(uid, slot, child_slot_type, child_slot_index, child_start_slot)
    return {
        [1] = uid,
        [2] = slot,
        [3] = child_slot_type,
        [4] = child_slot_index,
        [5] = child_start_slot
    }
end

local function make_mock_backplane(props)
    props = props or {}
    return {
        position = props.position or '0101',
        get_prop = function(_, name)
            return props[name]
        end,
        set_prop = function(_, name, val)
            props[name] = val
        end
    }
end

local function make_mock_parent_unit(uid, slot)
    return {
        get_prop = function(_, name)
            if name == 'UID' then return uid end
            if name == 'Slot' then return slot end
            return nil
        end
    }
end

---find_matching_configuration_index
function TestDriveStartSlotHelper:test_find_matching_configuration_index_match_ok()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'SEU', 1, config)
    lu.assertEquals(idx, 1)
    idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'SEU', 2, config)
    lu.assertEquals(idx, 2)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_uid_mismatch()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID002', 1, 'SEU', 1, config)
    lu.assertNil(idx)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_slot_mismatch()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 2, 'SEU', 1, config)
    lu.assertNil(idx)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_type_mismatch()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'M2', 1, config)
    lu.assertNil(idx)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_child_slot_index_nil()
    local config = make_config_item('UID001', 1, 'SEU', nil, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'SEU', 1, config)
    lu.assertNil(idx)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_length_mismatch()
    local config = make_config_item('UID001', 1, 'SEU', {1}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'SEU', 1, config)
    lu.assertNil(idx)
end

function TestDriveStartSlotHelper:test_find_matching_configuration_index_backplane_slot_not_found()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local idx = drive_start_slot_helper.find_matching_configuration_index('UID001', 1, 'SEU', 3, config)
    lu.assertNil(idx)
end

---check_configuration_continuity
function TestDriveStartSlotHelper:test_check_configuration_continuity_last_element_pass()
    local config = make_config_item('UID001', 1, 'SEU', {1}, {8})
    local ok = drive_start_slot_helper.check_configuration_continuity(config, 1, 8, {position = '0101'})
    lu.assertTrue(ok)
end

function TestDriveStartSlotHelper:test_check_configuration_continuity_continuity_ok()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local ok = drive_start_slot_helper.check_configuration_continuity(config, 1, 8, {position = '0101'})
    lu.assertTrue(ok)
end

function TestDriveStartSlotHelper:test_check_configuration_continuity_continuity_fail()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 15})
    local ok = drive_start_slot_helper.check_configuration_continuity(config, 1, 8, {position = '0101'})
    lu.assertFalse(ok)
end

function TestDriveStartSlotHelper:test_check_configuration_continuity_child_start_slot_nil()
    local config = make_config_item('UID001', 1, 'SEU', {1, 2}, nil)
    local ok = drive_start_slot_helper.check_configuration_continuity(config, 1, 8, {position = '0101'})
    lu.assertTrue(ok)
end

---calculate_start_slot_from_config
function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_no_configs()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    local parent = make_mock_parent_unit('UID001', 1)
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, nil, parent)
    lu.assertEquals(ret, 0)
    ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, {}, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_no_parent_unit()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, {{mds_obj = {Configurations = {}}}}, nil)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_parent_missing_uid()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    local parent = make_mock_parent_unit(nil, 1)
    local configs = {{mds_obj = {Configurations = {make_config_item('UID001', 1, 'SEU', {1}, {8})}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_parent_missing_slot()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    local parent = make_mock_parent_unit('UID001', nil)
    local configs = {{mds_obj = {Configurations = {make_config_item('UID001', 1, 'SEU', {1}, {8})}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_empty_configurations()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    local configs = {{mds_obj = {Configurations = {}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_invalid_slot_count()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 0})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 0 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    local configs = {{mds_obj = {Configurations = {make_config_item('UID001', 1, 'SEU', {1}, {8})}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_match_ok()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    local config_item = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local configs = {{mds_obj = {Configurations = {config_item}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 8)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_no_match()
    local bp = make_mock_backplane({Slot = 3, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 3 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    local config_item = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local configs = {{mds_obj = {Configurations = {config_item}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_continuity_fail()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    -- ChildStartSlot[2]=15 not 8+8=16, continuity fail
    local config_item = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 15})
    local configs = {{mds_obj = {Configurations = {config_item}}}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

function TestDriveStartSlotHelper:test_calculate_start_slot_from_config_mds_obj_nil_skip()
    local bp = make_mock_backplane({Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    local parent = make_mock_parent_unit('UID001', 1)
    local configs = {{mds_obj = nil, position = '0101'}}
    local ret = drive_start_slot_helper.calculate_start_slot_from_config(bp, configs, parent)
    lu.assertEquals(ret, 0)
end

---update_backplane_start_slot_if_pending
function TestDriveStartSlotHelper:test_update_backplane_start_slot_if_pending_skip_not_0xfe()
    local set_called = false
    local bp = make_mock_backplane({StartSlot = 8, Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'StartSlot' then return 8 end
        return nil
    end
    bp.set_prop = function() set_called = true end
    local configs = {{mds_obj = {Configurations = {}}}}
    local parent = make_mock_parent_unit('UID001', 1)
    local ret = drive_start_slot_helper.update_backplane_start_slot_if_pending(bp, configs, true, parent)
    lu.assertFalse(ret)
    lu.assertFalse(set_called)
end

function TestDriveStartSlotHelper:test_update_backplane_start_slot_if_pending_has_config_match_ok()
    local set_val
    local bp = make_mock_backplane({StartSlot = 0xFE, Slot = 1, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'StartSlot' then return 0xFE end
        if name == 'Slot' then return 1 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    bp.set_prop = function(_, name, val)
        set_val = val
    end
    local config_item = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local configs = {{mds_obj = {Configurations = {config_item}}}}
    local parent = make_mock_parent_unit('UID001', 1)
    local ret = drive_start_slot_helper.update_backplane_start_slot_if_pending(bp, configs, true, parent)
    lu.assertTrue(ret)
    lu.assertEquals(set_val, 8)
end

function TestDriveStartSlotHelper:test_update_backplane_start_slot_if_pending_has_config_match_fail()
    local set_val
    local bp = make_mock_backplane({StartSlot = 0xFE, Slot = 3, Type = 'SEU', SlotCount = 8})
    bp.get_prop = function(_, name)
        if name == 'StartSlot' then return 0xFE end
        if name == 'Slot' then return 3 end
        if name == 'Type' then return 'SEU' end
        if name == 'SlotCount' then return 8 end
        return nil
    end
    bp.set_prop = function(_, name, val)
        set_val = val
    end
    local config_item = make_config_item('UID001', 1, 'SEU', {1, 2}, {8, 16})
    local configs = {{mds_obj = {Configurations = {config_item}}}}
    local parent = make_mock_parent_unit('UID001', 1)
    local ret = drive_start_slot_helper.update_backplane_start_slot_if_pending(bp, configs, true, parent)
    lu.assertTrue(ret)
    lu.assertEquals(set_val, 0)
end

function TestDriveStartSlotHelper:test_update_backplane_start_slot_if_pending_no_config_complete_fired()
    local set_val
    local bp = make_mock_backplane({StartSlot = 0xFE})
    bp.get_prop = function(_, name)
        if name == 'StartSlot' then return 0xFE end
        return nil
    end
    bp.set_prop = function(_, name, val)
        set_val = val
    end
    local ret = drive_start_slot_helper.update_backplane_start_slot_if_pending(bp, nil, true, nil)
    lu.assertTrue(ret)
    lu.assertEquals(set_val, 0)
end

function TestDriveStartSlotHelper:test_update_backplane_start_slot_if_pending_no_config_complete_not_fired()
    local set_called = false
    local bp = make_mock_backplane({StartSlot = 0xFE})
    bp.get_prop = function(_, name)
        if name == 'StartSlot' then return 0xFE end
        return nil
    end
    bp.set_prop = function() set_called = true end
    local ret = drive_start_slot_helper.update_backplane_start_slot_if_pending(bp, nil, false, nil)
    lu.assertFalse(ret)
    lu.assertFalse(set_called)
end

function TestDriveStartSlotHelper:test_start_slot_wait_psr_constant()
    lu.assertEquals(drive_start_slot_helper.START_SLOT_WAIT_PSR, 0xFE)
end
