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:
Thad House
2017-11-28 19:12:05 -08:00
committed by Peter Johnson
parent 26a36779a6
commit fa0b4428e9
11 changed files with 228 additions and 106 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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`

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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']

View File

@@ -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 =

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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();