diff --git a/test-scripts/deploy-and-run-test-on-robot.sh b/test-scripts/deploy-and-run-test-on-robot.sh index a4232e6816..c2d896e93c 100755 --- a/test-scripts/deploy-and-run-test-on-robot.sh +++ b/test-scripts/deploy-and-run-test-on-robot.sh @@ -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" diff --git a/test-scripts/jenkins-run-tests-get-results.sh b/test-scripts/jenkins-run-tests-get-results.sh index 5bbac686f8..faf61477f8 100755 --- a/test-scripts/jenkins-run-tests-get-results.sh +++ b/test-scripts/jenkins-run-tests-get-results.sh @@ -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 diff --git a/test-scripts/run-tests-on-robot.sh b/test-scripts/run-tests-on-robot.sh index 4785dd596f..e0785ad9e4 100755 --- a/test-scripts/run-tests-on-robot.sh +++ b/test-scripts/run-tests-on-robot.sh @@ -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` diff --git a/test-scripts/spawn_driver_station.sh b/test-scripts/spawn_driver_station.sh deleted file mode 100755 index d41b41dec1..0000000000 --- a/test-scripts/spawn_driver_station.sh +++ /dev/null @@ -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 diff --git a/wpilibc/src/main/native/cpp/DriverStation.cpp b/wpilibc/src/main/native/cpp/DriverStation.cpp index 1b2c71d8ac..f463261b93 100644 --- a/wpilibc/src/main/native/cpp/DriverStation.cpp +++ b/wpilibc/src/main/native/cpp/DriverStation.cpp @@ -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(); } diff --git a/wpilibcIntegrationTests/build.gradle b/wpilibcIntegrationTests/build.gradle index 250d396ff3..8bcb54f28b 100644 --- a/wpilibcIntegrationTests/build.gradle +++ b/wpilibcIntegrationTests/build.gradle @@ -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'] diff --git a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/TestEnvironment.cpp b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/TestEnvironment.cpp index f82d10f0a5..cdd97880d9 100644 --- a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/TestEnvironment.cpp +++ b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/TestEnvironment.cpp @@ -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 = diff --git a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.cpp b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.cpp new file mode 100644 index 0000000000..d1506a6f19 --- /dev/null +++ b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.cpp @@ -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 + +#include +#include +#include +#include +#include + +#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& 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 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(); +} diff --git a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.h b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.h new file mode 100644 index 0000000000..fc5b7b1738 --- /dev/null +++ b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/mockds/MockDS.h @@ -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 +#include + +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 diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/MockDS.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/MockDS.java new file mode 100644 index 0000000000..b710998109 --- /dev/null +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/MockDS.java @@ -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(); + } + } +} diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/test/AbstractComsSetup.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/test/AbstractComsSetup.java index 95ce0e61d2..fd3b04b13b 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/test/AbstractComsSetup.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/test/AbstractComsSetup.java @@ -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();