mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-29 02:21:44 +00:00
Runs DS enabled loop in process (#785)
Solves mutex issues, and other issues with the existing teststand software. And simplifies the unit test structure.
This commit is contained in:
committed by
Peter Johnson
parent
26a36779a6
commit
fa0b4428e9
@@ -17,12 +17,11 @@ DEFAULT_DESTINATION_CPP_TEST_FILE=${DEFAULT_TEST_SCP_DIR}/${DEFAULT_CPP_TEST_NAM
|
||||
|
||||
DEFAULT_DESTINATION_RUN_TEST_SCRIPT=${DEFAULT_DESTINATION_DIR}/${DEFAULT_LOCAL_RUN_TEST_SCRIPT}
|
||||
|
||||
usage="$(basename "$0") [-h] (java|cpp) [-m] [-A] [arg] [arg]...
|
||||
usage="$(basename "$0") [-h] (java|cpp) [-A] [arg] [arg]...
|
||||
A script designed to run the integration tests.
|
||||
This script should only be run on the roborio.
|
||||
Where:
|
||||
-h Show this help text.
|
||||
-m The driver station mutex will be handled manually.
|
||||
-A Disable language recomended arguments.
|
||||
arg Additional arguments to be passed to test."
|
||||
|
||||
@@ -62,20 +61,14 @@ if [[ ! -e ${LOCAL_TEST_FILE} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MUTEX_OVERRIDE_PARAM_TEXT=""
|
||||
if [[ "$2" = "-m" ]]; then
|
||||
MUTEX_OVERRIDE_PARAM_TEXT="-m "
|
||||
TEST_RUN_ARGS="${@:3}"
|
||||
else
|
||||
TEST_RUN_ARGS="${@:2}"
|
||||
fi
|
||||
TEST_RUN_ARGS="${@:2}"
|
||||
|
||||
shopt -s huponexit
|
||||
|
||||
SCP_TEST_SCRIPT="scp config.sh ${DEFAULT_LOCAL_RUN_TEST_SCRIPT} ${ROBOT_ADDRESS}:/${DEFAULT_DESTINATION_DIR}"
|
||||
SSH_CHMOD_AND_MAKE_TEMP_TEST_DIR="ssh -t ${ROBOT_ADDRESS} \"chmod a+x ${DEFAULT_DESTINATION_RUN_TEST_SCRIPT}; mkdir ${DEFAULT_TEST_SCP_DIR}; touch ${DESTINATION_TEST_FILE}\""
|
||||
SCP_TEST_PROGRAM="scp ${LOCAL_TEST_FILE} ${ROBOT_ADDRESS}:${DESTINATION_TEST_FILE}"
|
||||
SSH_RUN_TESTS="ssh -t ${ROBOT_ADDRESS} ${DEFAULT_DESTINATION_RUN_TEST_SCRIPT} ${LANGUAGE} $(whoami) ${MUTEX_OVERRIDE_PARAM_TEXT}-d ${DEFAULT_TEST_SCP_DIR} ${TEST_RUN_ARGS}"
|
||||
SSH_RUN_TESTS="ssh -t ${ROBOT_ADDRESS} ${DEFAULT_DESTINATION_RUN_TEST_SCRIPT} ${LANGUAGE} $(whoami) -d ${DEFAULT_TEST_SCP_DIR} ${TEST_RUN_ARGS}"
|
||||
SCP_NATIVE_LIBRARIES="scp ${DEFAULT_LIBRARY_NATIVE_FILES}/* ${ROBOT_ADDRESS}:${DEFAULT_LIBRARY_NATIVE_DESTINATION}"
|
||||
CONFIG_NATIVE_LIBRARIES="ssh -t ${ADMIN_ROBOT_ADDRESS} ldconfig"
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ source config.sh
|
||||
printf "Getting exclusive lock for RIO execution...\n"
|
||||
flock -x 200 || exit 1
|
||||
|
||||
# To work around memory leak, kill NetComm and restart the teststand
|
||||
# (the teststand dies when NetComm is killed)
|
||||
SSH_RESTART_NETCOMM="ssh -t ${ROBOT_ADDRESS} sh -c 'killall FRC_NetCommDaemon; sleep 1; /etc/init.d/teststand stop; /etc/init.d/teststand start; sleep 1'"
|
||||
# To work around memory leak, kill NetComm and ensure the teststand is dead too
|
||||
SSH_RESTART_NETCOMM="ssh -t ${ROBOT_ADDRESS} sh -c '/etc/init.d/teststand stop; sleep 1; killall FRC_NetCommDaemon; sleep 1'"
|
||||
if [ $(which sshpass) ]; then
|
||||
sshpass -p "" ${SSH_RESTART_NETCOMM}
|
||||
else
|
||||
|
||||
@@ -17,45 +17,20 @@ source config.sh
|
||||
DEFAULT_TEST_DIR=${DEFAULT_DESTINATION_DIR}
|
||||
DEFAULT_PATH_TO_JRE=/usr/local/frc/JRE/bin/java
|
||||
|
||||
usage="$(basename "$0") [-h] (java|cpp) name [-d test_dir] [-m] [-A] [arg] [arg]...
|
||||
usage="$(basename "$0") [-h] (java|cpp) name [-d test_dir] [-A] [arg] [arg]...
|
||||
A script designed to run the integration tests.
|
||||
This script should only be run on the roborio.
|
||||
Where:
|
||||
-h Show this help text
|
||||
name The name of the user trying to run the tests (used for driver station)
|
||||
-d The directory where the tests have been placed.
|
||||
This is done to prevent overwriting an already running program
|
||||
in the case where another user already has the driver station mutex.
|
||||
This is done to prevent overwriting an already running program.
|
||||
This scrip will automatically move the test into the ${DEFAULT_TEST_DIR}
|
||||
directory when the driver station mutex is released.
|
||||
directory.
|
||||
Default: Assumes the test is in the same directory as this scrip.
|
||||
-m The driver station mutex will be handled manually.
|
||||
-A Do not use the default arguments for the given language.
|
||||
arg The arguments to be passed to test."
|
||||
|
||||
mutexTaken=false
|
||||
driverStationEnabled=false
|
||||
# This function should run even if the script exits abnormally
|
||||
function finish {
|
||||
if [ "$driverStationEnabled" == true ]; then
|
||||
/usr/local/frc/bin/teststand ds --name="${NAME}" disable
|
||||
driverStationEnabled=false
|
||||
fi
|
||||
if [ "$mutexTaken" == true ]; then
|
||||
/usr/local/frc/bin/teststand give --name="${NAME}"
|
||||
mutexTaken=false
|
||||
fi
|
||||
}
|
||||
trap finish EXIT SIGINT
|
||||
|
||||
# This function should be run asynchronysly to enable the tests 10
|
||||
# seconds after they have been run.
|
||||
function enableIn10Seconds {
|
||||
/usr/local/frc/bin/teststand ds --name="${NAME}" disable
|
||||
driverStationEnabled=true
|
||||
sleep 10
|
||||
/usr/local/frc/bin/teststand ds --name="${NAME}" enable
|
||||
}
|
||||
|
||||
# Are you trying to run this program on a platform other than the roboRIO?
|
||||
if [[ ! -e "${DEFAULT_TEST_DIR}" ]]; then
|
||||
@@ -75,7 +50,6 @@ PARAM_ARGS=${@:3}
|
||||
TEST_RUN_ARGS=${@:3}
|
||||
RUN_WITH_DEFAULT_ARGS=true
|
||||
DEFAULT_ARGS=""
|
||||
MUTEX_OVERRIDE=false
|
||||
|
||||
# Determine the language that we are attempting to run
|
||||
if [[ "$1" = java ]]; then
|
||||
@@ -113,10 +87,6 @@ while getopts ':hmd:A' option $PARAM_ARGS ; do
|
||||
RUN_WITH_DEFAULT_ARGS=false
|
||||
PARAM_COUNTER=$[$PARAM_COUNTER +1]
|
||||
;;
|
||||
m)
|
||||
MUTEX_OVERRIDE=true
|
||||
PARAM_COUNTER=$[$PARAM_COUNTER +1]
|
||||
;;
|
||||
d)
|
||||
TEST_DIR=$OPTARG
|
||||
# Since we are selecting the directory the run args start from the 5th argument
|
||||
@@ -142,19 +112,9 @@ if [[ $# -lt $PARAM_COUNTER ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If the mutex has been retrived a higher level in the tree
|
||||
if [ "$MUTEX_OVERRIDE" == false ]; then
|
||||
# Attempt to take the mutex for the driver station
|
||||
mutexTaken=true
|
||||
/usr/local/frc/bin/teststand take --name="${NAME}"
|
||||
else
|
||||
printf "Override driver station control enabled.\n"
|
||||
fi
|
||||
|
||||
# Kill all running robot programs
|
||||
killall java FRCUserProgram
|
||||
|
||||
# Once we have the mutex no other tests are running
|
||||
# If we are running with the -d argument move the test to the DEFAULT_TEST_DIR
|
||||
if [[ ! -e "${TEST_DIR}/${TEST_FILE}" ]]; then
|
||||
printf "Failed to find %s.\nDid you copy the test file correctly?\n" "${TEST_DIR}/${TEST_FILE}"
|
||||
@@ -166,9 +126,6 @@ fi
|
||||
|
||||
# Make sure the excecutable file has permission to run
|
||||
|
||||
# Setup the driver station to enable automatically in 10 seconds without waiting for the function to excecute.
|
||||
enableIn10Seconds&
|
||||
|
||||
# Get the serial number and FPGADeviceCode for this rio
|
||||
export serialnum=`/sbin/fw_printenv -n serial#`
|
||||
export eval `/sbin/fw_printenv DeviceCode FPGADeviceCode DeviceDesc TargetClass`
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#*----------------------------------------------------------------------------*#
|
||||
#* Copyright (c) FIRST 2014. All Rights Reserved. *#
|
||||
#* Open Source Software - may be modified and shared by FRC teams. The code *#
|
||||
#* must be accompanied by the FIRST BSD license file in the root directory of *#
|
||||
#* the project. *#
|
||||
#*----------------------------------------------------------------------------*#
|
||||
|
||||
source config.sh
|
||||
|
||||
TESTSTAND_SERVE_COMMAND="/usr/local/frc/bin/teststand serve"
|
||||
|
||||
#Checks to see if the mutex driver station is running. If it isn't then start it.
|
||||
|
||||
SSH_CHECK_FOR_TESTSTAND="ssh -qn ${ADMIN_ROBOT_ADDRESS} 'pidof ${TESTSTAND_SERVE_COMMAND}' &>/dev/null"
|
||||
|
||||
TESTSTAND_SERVE_COMMAND="nohup ${TESTSTAND_SERVE_COMMAND} > /dev/null 2>&1 < /dev/null &"
|
||||
|
||||
if [ $(which sshpass) ]; then
|
||||
SSH_CHECK_FOR_TESTSTAND="sshpass -p '' ${SSH_CHECK_FOR_TESTSTAND}"
|
||||
fi
|
||||
|
||||
echo $SSH_CHECK_FOR_TESTSTAND
|
||||
|
||||
if $(eval $SSH_CHECK_FOR_TESTSTAND); then
|
||||
echo "Driver Station is already running";
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
echo "Starting Driver Station";
|
||||
|
||||
function startDS {
|
||||
SSH_START_TESTSTAND_DRIVER_STATION="ssh ${ADMIN_ROBOT_ADDRESS} ${TESTSTAND_SERVE_COMMAND}"
|
||||
if [ $(which sshpass) ]; then
|
||||
sshpass -p "" ${SSH_START_TESTSTAND_DRIVER_STATION}
|
||||
else
|
||||
eval ${SSH_START_TESTSTAND_DRIVER_STATION}
|
||||
fi
|
||||
}
|
||||
|
||||
startDS&
|
||||
|
||||
sleep 1
|
||||
@@ -36,6 +36,8 @@ static constexpr double kJoystickUnpluggedMessageInterval = 1.0;
|
||||
|
||||
DriverStation::~DriverStation() {
|
||||
m_isRunning = false;
|
||||
// Trigger a DS mutex release in case there is no driver station running.
|
||||
HAL_ReleaseDSMutex();
|
||||
m_dsThread.join();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ if (!project.hasProperty('skipAthena')) {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ["${rootDir}/gmock/gtest/src", 'src/FRCUserProgram/cpp']
|
||||
includes = ['*-all.cc', '*_main.cc', '*.cpp']
|
||||
includes = ['*-all.cc', '*_main.cc', '**/*.cpp']
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs = ["${rootDir}/gmock/gtest/include", "${rootDir}/gmock/gtest", 'src/FRCUserProgram/headers']
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
#include "LiveWindow/LiveWindow.h"
|
||||
#include "Timer.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "mockds/MockDS.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
class TestEnvironment : public testing::Environment {
|
||||
bool m_alreadySetUp = false;
|
||||
MockDS m_mockDS;
|
||||
|
||||
public:
|
||||
void SetUp() override {
|
||||
@@ -32,6 +34,8 @@ class TestEnvironment : public testing::Environment {
|
||||
std::exit(-1);
|
||||
}
|
||||
|
||||
m_mockDS.start();
|
||||
|
||||
/* This sets up the network communications library to enable the driver
|
||||
station. After starting network coms, it will loop until the driver
|
||||
station returns that the robot is enabled, to ensure that tests
|
||||
@@ -46,7 +50,7 @@ class TestEnvironment : public testing::Environment {
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override {}
|
||||
void TearDown() override { m_mockDS.stop(); }
|
||||
};
|
||||
|
||||
testing::Environment* const environment =
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "MockDS.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <HAL/cpp/fpga_clock.h>
|
||||
#include <llvm/SmallString.h>
|
||||
#include <llvm/SmallVector.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
#include <support/Logger.h>
|
||||
|
||||
#include "udpsockets/UDPClient.h"
|
||||
|
||||
static void LoggerFunc(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
llvm::SmallString<128> buf;
|
||||
llvm::raw_svector_ostream oss(buf);
|
||||
if (level == 20) {
|
||||
oss << "DS: " << msg << '\n';
|
||||
llvm::errs() << oss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
llvm::StringRef levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL: ";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR: ";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING: ";
|
||||
else
|
||||
return;
|
||||
oss << "DS: " << levelmsg << msg << " (" << file << ':' << line << ")\n";
|
||||
llvm::errs() << oss.str();
|
||||
}
|
||||
|
||||
static void generateEnabledDsPacket(llvm::SmallVectorImpl<uint8_t>& data,
|
||||
uint16_t sendCount) {
|
||||
data.clear();
|
||||
data.push_back(sendCount >> 8);
|
||||
data.push_back(sendCount);
|
||||
data.push_back(0x01); // general data tag
|
||||
data.push_back(0x04); // teleop enabled
|
||||
data.push_back(0x10); // normal data request
|
||||
data.push_back(0x00); // red 1 station
|
||||
}
|
||||
|
||||
using namespace frc;
|
||||
|
||||
void MockDS::start() {
|
||||
if (m_active) return;
|
||||
m_active = true;
|
||||
m_thread = std::thread([&]() {
|
||||
wpi::Logger logger(LoggerFunc);
|
||||
wpi::UDPClient client(logger);
|
||||
client.start();
|
||||
auto timeout_time = hal::fpga_clock::now();
|
||||
int initCount = 0;
|
||||
uint16_t sendCount = 0;
|
||||
llvm::SmallVector<uint8_t, 8> data;
|
||||
while (m_active) {
|
||||
// Keep 20ms intervals, and increase time to next interval
|
||||
auto current = hal::fpga_clock::now();
|
||||
while (timeout_time <= current) {
|
||||
timeout_time += std::chrono::milliseconds(20);
|
||||
}
|
||||
std::this_thread::sleep_until(timeout_time);
|
||||
generateEnabledDsPacket(data, sendCount++);
|
||||
// ~10 disabled packets are required to make the robot actually enable
|
||||
// 1 is definitely not enough.
|
||||
if (initCount < 10) {
|
||||
initCount++;
|
||||
data[3] = 0;
|
||||
}
|
||||
client.send(data, "127.0.0.1", 1110);
|
||||
}
|
||||
client.shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
void MockDS::stop() {
|
||||
m_active = false;
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
namespace frc {
|
||||
class MockDS {
|
||||
public:
|
||||
MockDS() = default;
|
||||
~MockDS() { stop(); }
|
||||
MockDS(const MockDS& other) = delete;
|
||||
MockDS& operator=(const MockDS& other) = delete;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::thread m_thread;
|
||||
std::atomic_bool m_active{false};
|
||||
};
|
||||
} // namespace frc
|
||||
@@ -0,0 +1,85 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
public class MockDS {
|
||||
private Thread m_thread;
|
||||
|
||||
private void generateEnabledDsPacket(byte[] data, short sendCount) {
|
||||
data[0] = (byte) (sendCount >> 8);
|
||||
data[1] = (byte) sendCount;
|
||||
data[2] = 0x01; // general data tag
|
||||
data[3] = 0x04; // teleop enabled
|
||||
data[4] = 0x10; // normal data request
|
||||
data[5] = 0x00; // red 1 station
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
public void start() {
|
||||
|
||||
m_thread = new Thread(() -> {
|
||||
DatagramSocket socket;
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
} catch (SocketException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 1110);
|
||||
byte[] sendData = new byte[6];
|
||||
DatagramPacket packet = new DatagramPacket(sendData, 0, 6, addr);
|
||||
short sendCount = 0;
|
||||
int initCount = 0;
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
generateEnabledDsPacket(sendData, sendCount++);
|
||||
// ~50 disabled packets are required to make the robot actually enable
|
||||
// 1 is definitely not enough.
|
||||
if (initCount < 50) {
|
||||
initCount++;
|
||||
sendData[3] = 0;
|
||||
}
|
||||
packet.setData(sendData);
|
||||
socket.send(packet);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException ex) {
|
||||
// TODO Auto-generated catch block
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
socket.close();
|
||||
});
|
||||
// Because of the test setup in Java, this thread will not be stopped
|
||||
// So it must be a daemon thread
|
||||
m_thread.setDaemon(true);
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
public void stop() {
|
||||
if (m_thread == null) {
|
||||
return;
|
||||
}
|
||||
m_thread.interrupt();
|
||||
try {
|
||||
m_thread.join(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
// TODO Auto-generated catch block
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import edu.wpi.first.wpilibj.DriverStation;
|
||||
import edu.wpi.first.wpilibj.MockDS;
|
||||
import edu.wpi.first.wpilibj.RobotBase;
|
||||
import edu.wpi.first.wpilibj.Timer;
|
||||
import edu.wpi.first.wpilibj.hal.HAL;
|
||||
@@ -34,6 +35,9 @@ public abstract class AbstractComsSetup {
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
// We have no way to stop the MockDS, so its thread is daemon.
|
||||
private static MockDS ds;
|
||||
|
||||
/**
|
||||
* This sets up the network communications library to enable the driver
|
||||
* station. After starting network coms, it will loop until the driver station
|
||||
@@ -45,6 +49,10 @@ public abstract class AbstractComsSetup {
|
||||
// Set some implementations so that the static methods work properly
|
||||
RobotBase.initializeHardwareConfiguration();
|
||||
HAL.observeUserProgramStarting();
|
||||
DriverStation.getInstance().getAlliance();
|
||||
|
||||
ds = new MockDS();
|
||||
ds.start();
|
||||
|
||||
LiveWindow.setEnabled(false);
|
||||
TestBench.out().println("Started coms");
|
||||
@@ -57,8 +65,7 @@ public abstract class AbstractComsSetup {
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
// Prints the message on one line overwriting itself each time
|
||||
TestBench.out().print("\rWaiting for enable: " + enableCounter++);
|
||||
TestBench.out().println("Waiting for enable: " + enableCounter++);
|
||||
}
|
||||
TestBench.out().println();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user