-
Notifications
You must be signed in to change notification settings - Fork 122
Add common command CIs #3449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add common command CIs #3449
Changes from all commits
81065a8
06df1b7
05c6cd9
7eded6d
798e460
d5da91c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -177,3 +177,33 @@ jobs: | |
| name: yellow-ai-vs-ai-proto-logs | ||
| path: | | ||
| /tmp/tbots/yellow/proto_* | ||
|
|
||
| common-commands: | ||
| strategy: | ||
| # TODO: set fail-fast to true in production. | ||
| fail-fast: false | ||
| matrix: | ||
| commands: | ||
| - ../scripts/safe_run.sh bazel run --run_under="xvfb-run" //software/thunderscope:thunderscope_main -- --run_blue --run_diagnostics --interface lo --keyboard_estop --ci_mode | ||
| - ../scripts/safe_run.sh bazel run --run_under="xvfb-run" //software/ai/hl/stp/tactic/goalie:goalie_tactic_test -- --enable_thunderscope | ||
| - ../scripts/safe_run.sh bazel run --run_under="xvfb-run" //software/thunderscope:thunderscope_main -- --enable_autoref --ci_mode && ../scripts/safe_run.sh bazel run --run_under="xvfb-run" //software/thunderscope:thunderscope_main -- blue_log="$(find /tmp/tbots/blue -maxdepth 1 -type d -name 'proto_*' -printf '/tmp/tbots/blue/%f\n' 2>/dev/null | head -n1)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does the second half of this command do?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the delay, I am finally done work.
This part tries to find the previous replay log and display the logs: This is not ideal, I am trying to think of a way to handle this. Maybe I should allow a new argument for our thunderscope, so we can specify the location for the replay log to save, so that we don't need to lookup for it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah being able to specify the runtime dir + folder name would be ideal |
||
|
|
||
| name: Sanity Check on Common Commands | ||
| runs-on: ubuntu-20.04 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use the ubuntu-22.04 runner |
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Environment Setup | ||
| run: | | ||
| "${GITHUB_WORKSPACE}"/environment_setup/setup_software.sh | ||
|
|
||
| - name: Build Thunderscope | ||
| run: | | ||
| cd src | ||
| bazel build //software/thunderscope:thunderscope_main | ||
|
|
||
| - name: Check Common Commands | ||
| run: | | ||
| cd src | ||
| ${{ matrix.commands }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| #!/opt/tbotspython/bin/python3 | ||
|
|
||
| """ | ||
| ci_runner.py runs a given command for a fix amount of time and | ||
| return 0 if there is no Python runtime exception found in the output | ||
| otherwise return 1 | ||
| Because bazel outputs in both stderr and stdout regardless the content, | ||
| (https://github.com/bazelbuild/bazel/issues/10496) | ||
| only checking stderr is insufficient. Therefore, we are doing a pattern | ||
| matching in both stderr and stdout, which tells us if there is any runtime exception. | ||
| """ | ||
|
|
||
| from typing import List | ||
| import subprocess | ||
| import sys | ||
| import time | ||
| import select | ||
|
|
||
| # Default timeout is 120 seconds | ||
| TIME_LIMIT_S = 30 | ||
| # Key pattern of python exception | ||
| PYTHON_ERROR_PATTERN="Traceback (most recent call last):" | ||
| # Delimiter which splits the target command output and this program logs | ||
| SECTION_DELIM = "=" * 50 | ||
|
|
||
|
|
||
| def print_command(command: List[str]) -> None: | ||
| """ | ||
| Format and print commands | ||
| :param command: command to be printed in a list of string | ||
| """ | ||
| print(" ".join(command)) | ||
|
|
||
| def read_available_output(proc: subprocess.Popen) -> str: | ||
| """ | ||
| Safely read available output from process without blocking | ||
| :param proc: Running process | ||
| :return: string output of stdout | ||
| """ | ||
| output = "" | ||
| if proc.stdout: | ||
| rlist, _, _ = select.select([proc.stdout], [], [], 0) | ||
| if rlist: | ||
| output = proc.stdout.readline() | ||
| return output | ||
|
|
||
| def test_command(command: List[str]) -> int: | ||
| """ | ||
| Run a given command and return status code | ||
| :param command: command to run and test | ||
| :return: 0 if the given command was run successfully and did not | ||
| throw any error in the given time limit. | ||
| 1 if the give command failed to run or threw errors to console. | ||
| """ | ||
| start_time = time.time() | ||
|
|
||
| # Run the command as a subprocess | ||
| proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) | ||
|
|
||
| # Keep polling and checking ONLY if | ||
| # - did not reach time limit yet AND | ||
| # - the process is still running | ||
| while time.time() - start_time <= TIME_LIMIT_S and proc.poll() is None: | ||
| stdout_data = read_available_output(proc) | ||
| if PYTHON_ERROR_PATTERN in stdout_data: | ||
| print(SECTION_DELIM) | ||
| print("Oops! Error found while running the following command :(") | ||
| print_command(command) | ||
|
|
||
| proc.kill() | ||
| return 1 | ||
| if stdout_data: | ||
| print(stdout_data, end="") | ||
|
|
||
| # If the process is still running, send SIGKILL signal | ||
| if proc.poll() is None: | ||
| # TODO: remove the following print statement | ||
| print("killing the proc") | ||
| proc.kill() | ||
|
|
||
| remaining_output = proc.communicate()[0] | ||
| print(remaining_output) | ||
| print(SECTION_DELIM) | ||
|
|
||
| print("Nice! Test passed! :)") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| if len(sys.argv) < 2: | ||
| print("Usage: command_runner.py <command> [args...]", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| command = sys.argv[1:] | ||
|
Comment on lines
+94
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better to use the argparse module here, it's more standard for python. it can also let you take in an arbitrary number of inputs. |
||
| print(f"Testing the following command:") | ||
| print_command(command) | ||
|
|
||
| print(SECTION_DELIM) | ||
| sys.exit(test_command(sys.argv[1:])) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #!/bin/bash | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But tbh I don't know a better name... maybe
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a high-level description of this script at the top of this file.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you re-write this script with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, sounds good! |
||
|
|
||
| # Timeout in seconds | ||
| # When the time is up and no error was shown, this test will pass | ||
| TIME_LIMIT=120 # 2 minutes | ||
|
|
||
| # Match Python traceback | ||
| ERROR_PATTERN="Traceback (most recent call last):" | ||
|
|
||
| # Temporary log file | ||
| LOG_FILE=$(mktemp) | ||
|
|
||
| # Run the command and record the log | ||
| "$@" &> "$LOG_FILE" & | ||
| CMD_PID=$! | ||
|
|
||
| echo "Process Running in Wrapper with Timeout $TIME_LIMIT ..." | ||
|
|
||
| # Time the process | ||
| SECONDS=0 | ||
| while kill -0 $CMD_PID 2>/dev/null; do | ||
| # Check if time is up | ||
| if [ $SECONDS -ge $TIME_LIMIT ]; then | ||
| echo "Time limit reached, stopping process: $CMD_PID" | ||
| kill -SIGINT $CMD_PID | ||
| wait $CMD_PID | ||
| exit 0 # Upon time out and no error, returns 0 status code | ||
| fi | ||
|
|
||
| # Check if the log contains Traceback | ||
| if grep -q "$ERROR_PATTERN" "$LOG_FILE"; then | ||
| echo "[Error detected] Potential error found in command output!" | ||
| kill -SIGINT $CMD_PID | ||
| wait $CMD_PID | ||
| exit 1 | ||
| fi | ||
|
|
||
| sleep 1 # Run this loop once per second | ||
| done | ||
|
|
||
| cat $LOG_FILE | ||
| # Get the exit code of the process | ||
| wait $CMD_PID | ||
| EXIT_CODE=$? | ||
|
|
||
| # Clean up log file | ||
| rm -f "$LOG_FILE" | ||
|
|
||
| # Exit with the command status code | ||
| exit $EXIT_CODE | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,14 @@ | |
| default="/tmp/tbots/yellow", | ||
| ) | ||
|
|
||
| # Proto log folder name | ||
| parser.add_argument( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice work! |
||
| "--log_name", | ||
| type=str, | ||
| help="proto log folder name to save to", | ||
| default=None, | ||
| ) | ||
|
|
||
| # Debugging | ||
| parser.add_argument( | ||
| "--debug_blue_full_system", | ||
|
|
@@ -374,6 +382,7 @@ | |
| friendly_colour_yellow=friendly_colour_yellow, | ||
| should_restart_on_crash=True, | ||
| run_sudo=args.sudo, | ||
| log_name=args.log_name | ||
| ) as full_system: | ||
| full_system.setup_proto_unix_io(current_proto_unix_io) | ||
|
|
||
|
|
@@ -447,13 +456,15 @@ def __ticker(tick_rate_ms: int) -> None: | |
| should_restart_on_crash=False, | ||
| run_sudo=args.sudo, | ||
| running_in_realtime=(not args.ci_mode), | ||
| log_name=args.log_name, | ||
| ) as blue_fs, FullSystem( | ||
| full_system_runtime_dir=args.yellow_full_system_runtime_dir, | ||
| debug_full_system=args.debug_yellow_full_system, | ||
| friendly_colour_yellow=True, | ||
| should_restart_on_crash=False, | ||
| run_sudo=args.sudo, | ||
| running_in_realtime=(not args.ci_mode), | ||
| log_name=args.log_name, | ||
| ) as yellow_fs, Gamecontroller( | ||
| suppress_logs=(not args.verbose) | ||
| ) as gamecontroller, ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remember to remove TODO before merging