java - 对嵌套 JNA 结构对象进行单元测试时出现意外错误

标签 java windows junit jna

以下单元测试旨在验证嵌套 JNA 数据结构是否已根据传递到构造函数的数据进行正确解析。单元测试正确检查 JNA 何时初始化“父”结构。但是,在测试嵌套结构的成员变量时,测试失败。我怀疑问题在于单元测试设置了一个指向包含嵌套结构数据的内存位置的指针。也许 Java 指针没有一对一地转换为 C 的方式。

我尝试将 WlanBssEntry 放置在 WlanBssEntry[] 的开头,但这并没有提供与当前实现方式不同的结果。当我尝试将其连续放置时,我没有调用 pointerToMem.setPointer(8, ...)

这是单元测试:

package com.sevensignal.EyeQAgent.Util.win32.struct;

import com.sevensignal.EyeQAgent.Models.Platform;
import com.sevensignal.EyeQAgent.Util.Utils;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeThat;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Utils.class})
public class WlanBssListTest {

    Pointer pWlanBssList;

    private final static long OFFSET_TO_FIRST_WLAN_BSS_ENTRY = 1024;

    @Before
    public void setUp() {
        assumeThat(Utils.getPlatform(), equalTo(Platform.WINDOWS));
        pWlanBssList = allocateMemory(65536);
        initWlanBssEntryMemory(pWlanBssList, OFFSET_TO_FIRST_WLAN_BSS_ENTRY, 50);
        initWlanBssListMemory(pWlanBssList, 12, 1, OFFSET_TO_FIRST_WLAN_BSS_ENTRY);
    }

    @Test
    public void shouldSetTotalSize() {
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should set total size", new WinDef.DWORD(12), subject.dwTotalSize);
    }

    @Test
    public void shouldSetNumberOfItems() {
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should set number of items", new WinDef.DWORD(1), subject.dwNumberOfItems);
    }

    @Test
    public void shouldInitWlanBssEntries() {
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should init WLAN BSS Entries", 1, subject.wlanBssEntries.length);
        assertEquals("should init WLAN BSS Entry data struct", new WinDef.LONG(50), subject.wlanBssEntries[0].uPhyId);
    }

    @Test
    public void shouldInitWlanBssEntriesWhenNoEntriesExist() {
        pWlanBssList.setInt(4, 0);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should init WLAN BSS Entries when no entries exist", 0, subject.wlanBssEntries.length);
    }

    private Pointer allocateMemory(long size) {
        return new Memory(size).share(0);
    }

    private void initWlanBssListMemory(Pointer pointerToMem, int dwTotalSize, int dwNumberOfItems, long offsetToWlanBssEntry) {
        pointerToMem.setInt(0, dwTotalSize);
        pointerToMem.setInt(4, dwNumberOfItems);
        pointerToMem.setPointer(8, pointerToMem.share(offsetToWlanBssEntry));
    }

    private void initWlanBssEntryMemory(Pointer pointerToMem, long offsetToWlanBssEntry, long uPhyId) {
        final int PHY_ID_OFFSET = 40;
        pointerToMem.setLong(offsetToWlanBssEntry + 0, 3);
        pointerToMem.setByte(offsetToWlanBssEntry + 8, (byte)'T');
        pointerToMem.setByte(offsetToWlanBssEntry + 9, (byte)'S');
        pointerToMem.setByte(offsetToWlanBssEntry + 10, (byte)'T');
        pointerToMem.setLong(offsetToWlanBssEntry + PHY_ID_OFFSET, uPhyId);
    }
}

这是 WlanBssList 类,它是最高级别的结构:

package com.sevensignal.EyeQAgent.Util.win32.struct;

import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef.DWORD;

import java.util.Arrays;
import java.util.List;

public class WlanBssList extends Structure {

    public DWORD dwTotalSize;
    public DWORD dwNumberOfItems;
    public WlanBssEntry[] wlanBssEntries;

    public static class ByReference extends WlanBssList implements Structure.ByReference
    {
        public ByReference()
        {

        }

        public ByReference(Pointer p)
        {
            super(p);
        }
    }

    public WlanBssList() {
        wlanBssEntries = new WlanBssEntry[1];
    }

    public WlanBssList(Pointer p) {
        super(p);
        dwTotalSize = new DWORD(p.getInt(0));
        dwNumberOfItems = new DWORD(p.getInt(4));
        if(dwNumberOfItems.intValue() > 0) {
            wlanBssEntries = new WlanBssEntry[dwNumberOfItems.intValue()];
            readField("wlanBssEntries");
        } else {
            wlanBssEntries = new WlanBssEntry[0];
        }
    }

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("dwTotalSize", "dwNumberOfItems", "wlanBssEntries");
    }
}

这些是嵌套结构:

package com.sevensignal.EyeQAgent.Util.win32.struct;

import com.sevensignal.EyeQAgent.Models.InformationElementGetter;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef.LONG;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGLONG;
import com.sun.jna.platform.win32.WinDef.USHORT;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.List;

@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WlanBssEntry extends Structure implements InformationElementGetter {

    public DOT11_SSID dot11Ssid;
    public ULONG uPhyId;
    public DOT11_MAC_ADDRESS dot11Bssid;
    public int dot11BssType;
    public int dot11BssPhyType;
    public LONG lRssi;
    public ULONG uLinkQuality;
    public boolean bInRegDomain;
    public USHORT usBeaconPeriod;
    public ULONGLONG ullTimestamp;
    public ULONGLONG ullHostTimestamp;
    public USHORT usCapabilityInformation;
    public ULONG ulChCenterFrequency;
    public WlanRateSet wlanRateSet;
    public ULONG ulIeOffset;
    public ULONG ulIeSize;

    @Override
    protected List<String> getFieldOrder()
    {
        return Arrays.asList("dot11Ssid",
                "uPhyId",
                "dot11Bssid",
                "dot11BssType",
                "dot11BssPhyType",
                "lRssi",
                "uLinkQuality",
                "bInRegDomain",
                "usBeaconPeriod",
                "ullTimestamp",
                "ullHostTimestamp",
                "usCapabilityInformation",
                "ulChCenterFrequency",
                "wlanRateSet",
                "ulIeOffset",
                "ulIeSize");
    }

    public byte[] getInformationElement() {
        return this.getPointer()
                .getByteArray(this.ulIeOffset.intValue(), this.ulIeSize.intValue());
    }
}
package com.sevensignal.EyeQAgent.Util.win32.struct;

import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef.*;

import java.util.Arrays;
import java.util.List;

public class DOT11_SSID extends Structure
{
    public static class ByReference extends DOT11_SSID implements Structure.ByReference
    {

    }

    public static int DOT11_SSID_MAX_LENGTH = 32;

    /**
     * The length, in bytes, of the ucSSID array.
     */
    public ULONG uSSIDLength;

    /**
     * The SSID. DOT11_SSID_MAX_LENGTH is set to 32.
     */
    public byte[] ucSSID;

    public DOT11_SSID()
    {
        ucSSID = new byte[DOT11_SSID_MAX_LENGTH];
    }

    @Override
    protected List<String> getFieldOrder()
    {
        return Arrays.asList("uSSIDLength", "ucSSID");
    }

    @Override
    public String toString() {
        if(uSSIDLength != null) {
            int ssidArrayLength = uSSIDLength.intValue();
            if (ssidArrayLength > DOT11_SSID_MAX_LENGTH) {
                ssidArrayLength = DOT11_SSID_MAX_LENGTH;
            }

            return new String(Arrays.copyOfRange(ucSSID, 0, ssidArrayLength));
        } else {
            return "";
        }
    }
}

单元测试的结果是:

java.lang.AssertionError: should init WLAN BSS Entry data struct 
Expected :50
Actual   :0

失败的断言是:

assertEquals("应该初始化 WLAN BSS 条目数据结构", new WinDef.LONG(50), subject.wlanBssEntries[0].uPhyId);

最佳答案

我修复了失败的单元测试。在分配的内存中初始化数据时存在两个问题。

首先,uPhyId 的偏移量偏离了 4,因为我认为 dot11Ssid 的长度字段是 8 而不是 4。

其次,第一个 WlanBssEntry 的内存位于 WlanBssList 结构中偏移量 8 处。

最后,我更改了 wlanBssEntries 的初始化方式,以保证 WlanBssEntry 实例数组在内存中连续出现。即使没有进行此更改,我的单元测试也通过了。但根据@Daniel Widdis 的建议以及一些在线文档,我认为最好遵循推荐的程序。它的初始化如下:

wlanBssEntries = (WlanBssEntry[])(new WlanBssEntry()).toArray(dwNumberOfItems.intValue());

这是通过单元测试的代码:

package com.sevensignal.EyeQAgent.Util.win32.struct;

import com.sevensignal.EyeQAgent.Models.Platform;
import com.sevensignal.EyeQAgent.Util.Utils;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeThat;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Utils.class})
public class WlanBssListTest {

    Pointer pWlanBssList;

    private final static long OFFSET_TO_FIRST_WLAN_BSS_ENTRY = 8;
    private final static int PHY_ID_OFFSET = 36;
    private final static int WLAN_BSS_ENTRY_LENGTH = 360;

    @Before
    public void setUp() {
        assumeThat(Utils.getPlatform(), equalTo(Platform.WINDOWS));
        pWlanBssList = allocateMemory(65536);
    }

    @Test
    public void shouldSetTotalSize() {
        initWlanBssListMemory(pWlanBssList, 12, 1);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should set total size", new WinDef.DWORD(12), subject.dwTotalSize);
    }

    @Test
    public void shouldSetNumberOfItems() {
        initWlanBssListMemory(pWlanBssList, 12, 1);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should set number of items", new WinDef.DWORD(1), subject.dwNumberOfItems);
    }

    @Test
    public void shouldInitWlanBssEntries() {
        initWlanBssListMemory(pWlanBssList, 12, 1);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertArrayEquals("should init SSID entry in WLAN BSS Entry data struct",
                new byte[]{
                        (byte)'I', (byte)'D', (byte)'1', (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
                },
                subject.wlanBssEntries[0].dot11Ssid.ucSSID);
        assertEquals("should init WLAN BSS Entry data struct", new WinDef.LONG(51), subject.wlanBssEntries[0].uPhyId);
    }

    @Test
    public void shouldInitTwoWlanBssEntries() {
        initWlanBssListMemory(pWlanBssList, 12, 2);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertArrayEquals("should init SSID entry in WLAN BSS Entry data struct",
                new byte[]{
                        (byte)'I', (byte)'D', (byte)'1', (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
                },
                subject.wlanBssEntries[0].dot11Ssid.ucSSID);
        assertEquals("should init WLAN BSS Entry data struct", new WinDef.LONG(51), subject.wlanBssEntries[0].uPhyId);

        assertArrayEquals("should init SSID entry in WLAN BSS Entry data struct",
                new byte[]{
                        (byte)'I', (byte)'D', (byte)'2', (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
                },
                subject.wlanBssEntries[1].dot11Ssid.ucSSID);
        assertEquals("should init WLAN BSS Entry data struct", new WinDef.LONG(0xC2), subject.wlanBssEntries[1].uPhyId);
    }

    @Test
    public void shouldInitWlanBssEntriesWhenNoEntriesExist() {
        initWlanBssListMemory(pWlanBssList, 12, 0);
        WlanBssList subject = new WlanBssList(pWlanBssList);
        assertEquals("should init WLAN BSS Entries when no entries exist", 0, subject.wlanBssEntries.length);
    }

    private Pointer allocateMemory(long size) {
        return new Memory(size).share(0);
    }

    private void initWlanBssListMemory(Pointer pointerToMem, int dwTotalSize, int dwNumberOfItems) {
        pointerToMem.setInt(0, dwTotalSize);
        pointerToMem.setInt(4, dwNumberOfItems);
        initWlanBssEntryMemory(pointerToMem);
    }

    private void initWlanBssEntryMemory(Pointer pointerToWlanBssEntryMem) {
        pointerToWlanBssEntryMem.setLong(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + 0, 3);
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + 4, (byte)'I');
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + 5, (byte)'D');
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + 6, (byte)'1');
        pointerToWlanBssEntryMem.setMemory(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + 7, 29, (byte)0);
        pointerToWlanBssEntryMem.setLong(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + PHY_ID_OFFSET, 51);

        pointerToWlanBssEntryMem.setLong(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + 0, 3);
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + 4, (byte)'I');
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + 5, (byte)'D');
        pointerToWlanBssEntryMem.setByte(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + 6, (byte)'2');
        pointerToWlanBssEntryMem.setMemory(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + 7, 29, (byte)0);
        pointerToWlanBssEntryMem.setLong(OFFSET_TO_FIRST_WLAN_BSS_ENTRY + WLAN_BSS_ENTRY_LENGTH + PHY_ID_OFFSET, 0xC2);
    }
}

关于java - 对嵌套 JNA 结构对象进行单元测试时出现意外错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55385907/

相关文章:

java - 如何使用 BufferedWriter 读取特定行

windows - 添加到 HKCU/Software/Classes 后无法通过 URI 启动 Windows 程序

hibernate - 一起运行时 Maven Spring 测试失败,但单独成功(ehcache 关闭,IllegalTransactionStateException)

ant - 控制 Ant 运行哪些 JUnit 测试

java - 我在 @FindBy WebDriver 处得到 java.lang.NullPointerException

java - android中从 "yyyy-MM-dd hh:mm:ss.SSS"----------> "hh:mm aa"解析日期和时间

java - jprofiler 9 osx 无法启动

java - 具有重写方法的可重入锁?

c++ - Windows 事件日志中没有类别

windows - 使用 SC.exe 设置服务凭据密码失败