mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpinet] Add mDNS discovery tests and fix mDNS JNI bugs (#8682)
In https://github.com/wpilibsuite/allwpilib/issues/8681 we discovered that multicast service types need to be valid (end with _tcp or _udp), or else errors are silently swallowed. Let's make our C++ unit test use a valid name and also check that it works. I think if we should/shouldn't do this is up for debate still. I also discovered two bugs in the JNI code that lead to incorrect results being returned - Return array index was always 0 - Use of JLocal for the return value seems to mean that the array will always be NULL in java
This commit is contained in:
2
.github/workflows/bazel.yml
vendored
2
.github/workflows/bazel.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install apt dependencies
|
- name: Install apt dependencies
|
||||||
if: matrix.os == 'ubuntu-24.04'
|
if: matrix.os == 'ubuntu-24.04'
|
||||||
run: sudo apt-get update && sudo apt-get install -y libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev
|
run: sudo apt-get update && sudo apt-get install -y libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev avahi-daemon
|
||||||
|
|
||||||
- if: matrix.os == 'ubuntu-24.04'
|
- if: matrix.os == 'ubuntu-24.04'
|
||||||
uses: bazel-contrib/setup-bazel@0.15.0
|
uses: bazel-contrib/setup-bazel@0.15.0
|
||||||
|
|||||||
6
.github/workflows/cmake.yml
vendored
6
.github/workflows/cmake.yml
vendored
@@ -37,6 +37,12 @@ jobs:
|
|||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java ninja-build
|
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java ninja-build
|
||||||
|
|
||||||
|
- name: Setup avahi-daemon
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
sudo service dbus start
|
||||||
|
sudo avahi-daemon -D
|
||||||
|
|
||||||
- name: Install dependencies (macOS)
|
- name: Install dependencies (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: brew install opencv ninja
|
run: brew install opencv ninja
|
||||||
|
|||||||
6
.github/workflows/gradle.yml
vendored
6
.github/workflows/gradle.yml
vendored
@@ -60,7 +60,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image: ${{ matrix.container }}
|
image: ${{ matrix.container }}
|
||||||
options: -v ${{ github.workspace }}:/work -w /work -e ARTIFACTORY_PUBLISH_USERNAME -e ARTIFACTORY_PUBLISH_PASSWORD -e GITHUB_REF -e CI
|
options: -v ${{ github.workspace }}:/work -w /work -e ARTIFACTORY_PUBLISH_USERNAME -e ARTIFACTORY_PUBLISH_PASSWORD -e GITHUB_REF -e CI
|
||||||
run: ./gradlew build --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
# Start avahi-daemon and build
|
||||||
|
run: |
|
||||||
|
service dbus start
|
||||||
|
avahi-daemon -D
|
||||||
|
./gradlew build --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||||
env:
|
env:
|
||||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||||
|
|||||||
9
.github/workflows/sanitizers.yml
vendored
9
.github/workflows/sanitizers.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
ctest-flags: "-E 'wpilibc'"
|
ctest-flags: "-E 'wpilibc'"
|
||||||
- name: tsan
|
- name: tsan
|
||||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
|
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
|
||||||
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1"
|
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1:suppressions=$GITHUB_WORKSPACE/tsan_suppressions.txt"
|
||||||
ctest-flags: "-E 'cscore|cameraserver'"
|
ctest-flags: "-E 'cscore|cameraserver'"
|
||||||
- name: ubsan
|
- name: ubsan
|
||||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
|
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
container: wpilib/roborio-cross-ubuntu:2025-24.04
|
container: wpilib/roborio-cross-ubuntu:2025-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-18 ninja-build
|
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-18 ninja-build avahi-daemon
|
||||||
|
|
||||||
- name: Install sccache
|
- name: Install sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
@@ -46,6 +46,11 @@ jobs:
|
|||||||
SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||||
SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Setup avahi-daemon
|
||||||
|
run: |
|
||||||
|
sudo service dbus start
|
||||||
|
sudo avahi-daemon -D
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake --build . --parallel $(nproc)
|
run: cmake --build . --parallel $(nproc)
|
||||||
|
|||||||
1
tsan_suppressions.txt
Normal file
1
tsan_suppressions.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
deadlock:libdbus-1.so.3
|
||||||
@@ -330,9 +330,10 @@ Java_org_wpilib_net_WPINetJNI_getMulticastServiceResolverData
|
|||||||
return serviceDataEmptyArray;
|
return serviceDataEmptyArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
JLocal<jobjectArray> returnData{
|
jobjectArray returnData =
|
||||||
env, env->NewObjectArray(allData.size(), serviceDataCls, nullptr)};
|
env->NewObjectArray(allData.size(), serviceDataCls, nullptr);
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
for (auto&& data : allData) {
|
for (auto&& data : allData) {
|
||||||
JLocal<jstring> serviceName{env, MakeJString(env, data.serviceName)};
|
JLocal<jstring> serviceName{env, MakeJString(env, data.serviceName)};
|
||||||
JLocal<jstring> hostName{env, MakeJString(env, data.hostName)};
|
JLocal<jstring> hostName{env, MakeJString(env, data.hostName)};
|
||||||
@@ -340,7 +341,6 @@ Java_org_wpilib_net_WPINetJNI_getMulticastServiceResolverData
|
|||||||
wpi::util::SmallVector<std::string_view, 8> keysRef;
|
wpi::util::SmallVector<std::string_view, 8> keysRef;
|
||||||
wpi::util::SmallVector<std::string_view, 8> valuesRef;
|
wpi::util::SmallVector<std::string_view, 8> valuesRef;
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
for (auto&& txt : data.txt) {
|
for (auto&& txt : data.txt) {
|
||||||
keysRef.emplace_back(txt.first);
|
keysRef.emplace_back(txt.first);
|
||||||
valuesRef.emplace_back(txt.second);
|
valuesRef.emplace_back(txt.second);
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) FIRST and other WPILib contributors.
|
||||||
|
// Open Source Software; you can modify and/or share it under the terms of
|
||||||
|
// the WPILib BSD license file in the root directory of this project.
|
||||||
|
|
||||||
|
package org.wpilib.net;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MulticastServiceAnnouncerTest {
|
||||||
|
@Test
|
||||||
|
void emptyText() throws InterruptedException, UnknownHostException {
|
||||||
|
final String serviceName = "Foaksdfjasklfj";
|
||||||
|
final String serviceType = "_wpinotxt._tcp";
|
||||||
|
final int port = 12345;
|
||||||
|
|
||||||
|
try (MulticastServiceAnnouncer announcer =
|
||||||
|
new MulticastServiceAnnouncer(serviceName, serviceType, port);
|
||||||
|
MulticastServiceResolver resolver = new MulticastServiceResolver(serviceType)) {
|
||||||
|
assertTrue(announcer.hasImplementation() && resolver.hasImplementation());
|
||||||
|
|
||||||
|
announcer.start();
|
||||||
|
resolver.start();
|
||||||
|
|
||||||
|
List<ServiceData> allData = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
ServiceData[] data = resolver.getData();
|
||||||
|
if (data == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
allData.addAll(List.of(data));
|
||||||
|
|
||||||
|
if (!allData.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(allData.isEmpty(), "Expected at least one service entry, but got none");
|
||||||
|
|
||||||
|
resolver.stop();
|
||||||
|
announcer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,10 @@
|
|||||||
// the WPILib BSD license file in the root directory of this project.
|
// the WPILib BSD license file in the root directory of this project.
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
#include <vector>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
|
|
||||||
TEST(MulticastServiceAnnouncerTest, EmptyText) {
|
TEST(MulticastServiceAnnouncerTest, EmptyText) {
|
||||||
const std::string_view serviceName = "TestServiceNoText";
|
const std::string_view serviceName = "TestServiceNoText";
|
||||||
const std::string_view serviceType = "_wpinotxt";
|
const std::string_view serviceType = "_wpinotxt._tcp";
|
||||||
const int port = std::rand();
|
const int port = std::rand();
|
||||||
wpi::net::MulticastServiceAnnouncer announcer(serviceName, serviceType, port);
|
wpi::net::MulticastServiceAnnouncer announcer(serviceName, serviceType, port);
|
||||||
wpi::net::MulticastServiceResolver resolver(serviceType);
|
wpi::net::MulticastServiceResolver resolver(serviceType);
|
||||||
@@ -26,7 +25,20 @@ TEST(MulticastServiceAnnouncerTest, EmptyText) {
|
|||||||
announcer.Start();
|
announcer.Start();
|
||||||
resolver.Start();
|
resolver.Start();
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::vector<wpi::net::MulticastServiceResolver::ServiceData> allData;
|
||||||
|
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
// GetData gives me new data since last time. This smoketest is just
|
||||||
|
// looking for -any- response
|
||||||
|
allData = resolver.GetData();
|
||||||
|
if (!allData.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_GT(allData.size(), 0ul);
|
||||||
|
|
||||||
resolver.Stop();
|
resolver.Stop();
|
||||||
announcer.Stop();
|
announcer.Stop();
|
||||||
|
|||||||
Reference in New Issue
Block a user