Skip to content

Commit

Permalink
tested and finished #51
Browse files Browse the repository at this point in the history
  • Loading branch information
FIREdog5 committed Jun 13, 2021
1 parent c185744 commit 4c18f0c
Show file tree
Hide file tree
Showing 15 changed files with 80 additions and 80 deletions.
12 changes: 5 additions & 7 deletions shepherd/sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from ydl import ydl_send
from utils import YDL_TARGETS, SHEPHERD_HEADER
from utils import YDL_TARGETS, SHEPHERD_HEADER, CONSTANTS

# If modifying these scopes, delete your previously saved credentials
# at USER_TOKEN_FILE
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
CLIENT_SECRET_FILE = 'sheets/client_secret.json'
CSV_FILE_NAME = "sheets/Shepherd Evergreen Database - Match Database.csv"
USER_TOKEN_FILE = "sheets/user_token.json" # user token; do not upload to github (.gitignore it)
SPREADSHEET_ID = "1JCtt_Iqyx15EOAZN6agqeeUKCFsrL6oOy3brKyAWjBM"


class Sheet:
Expand All @@ -32,12 +30,12 @@ def bg_thread_work():
game_data = [[]]
try:
spreadsheet = Sheet.__get_authorized_sheet()
game_data = spreadsheet.values().get(spreadsheetId=SPREADSHEET_ID,
game_data = spreadsheet.values().get(spreadsheetId=CONSTANTS.SPREADSHEET_ID,
range="Match Database!A2:M").execute()['values']
except: # pylint: disable=bare-except
print('[error!] Google API has changed yet again, please fix Sheet.py')
print("Fetching data from offline csv file")
with open(CSV_FILE_NAME) as csv_file:
with open(CONSTANTS.CSV_FILE_NAME) as csv_file:
game_data = list(csv.reader(csv_file, delimiter=','))[1:]

return_len = 12
Expand Down Expand Up @@ -98,7 +96,7 @@ def __write_online_scores(match_number, blue_score, gold_score):
A method that writes the scores to the sheet
"""
spreadsheet = Sheet.__get_authorized_sheet()
game_data = spreadsheet.values().get(spreadsheetId=SPREADSHEET_ID,
game_data = spreadsheet.values().get(spreadsheetId=CONSTANTS.SPREADSHEET_ID,
range="Match Database!A2:A").execute()['values']

row_num = -1 # if this fails, it'll overwrite the header which is fine
Expand All @@ -111,5 +109,5 @@ def __write_online_scores(match_number, blue_score, gold_score):
body = {
'values': [[str(blue_score), str(gold_score)]]
}
spreadsheet.values().update(spreadsheetId=SPREADSHEET_ID,
spreadsheet.values().update(spreadsheetId=CONSTANTS.SPREADSHEET_ID,
range=range_name, body=body, valueInputOption="RAW").execute()
38 changes: 19 additions & 19 deletions shepherd/tests/TESTING_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@

## Intro

The testing utility was designed as an asynchronous dummy that would be able to mock up the communication between a piece of shepherd and the rest. It is designed to make simple responses to LCM messages sent by the piece of shepherd that is being tested, and it can be used to test any number of shepherd pieces together. Unfortunately, the shepherd testing utility requires LCM to communicate with other parts of shepherd, so a computer without LCM will not be able to run it.
The testing utility was designed as an asynchronous dummy that would be able to mock up the communication between a piece of shepherd and the rest. It is designed to make simple responses to YDL messages sent by the piece of shepherd that is being tested, and it can be used to test any number of shepherd pieces together. Unfortunately, the shepherd testing utility requires YDL to communicate with other parts of shepherd, so a computer without YDL will not be able to run it.

The testing utility communicates via LCM targets, and there are a few important things to be aware of while using the utility. All LCM targets and headers must be included in Utils.py, otherwise the utility will not recognize them. In addition, Utils.py is not imported into the internal environment for python execution that the tester provides. LCM headers and targets will be looked up in Utils.py when they appear in a non-python context, but otherwise they would need to be imported via a RUN statement. Due to underlying limitations, we can only read from one target at a time, so each READ statement will override any previous READ statements, and change our LCM target. Furthermore, we cannot wait on a header from a target before we read from that target, so READ should probably be the first line of your script.
The testing utility communicates via YDL targets, and there are a few important things to be aware of while using the utility. All YDL targets and headers must be included in Utils.py, otherwise the utility will not recognize them. In addition, Utils.py is not imported into the internal environment for python execution that the tester provides. YDL headers and targets will be looked up in Utils.py when they appear in a non-python context, but otherwise they would need to be imported via a RUN statement. Due to underlying limitations, we can only read from one target at a time, so each READ statement will override any previous READ statements, and change our YDL target. Furthermore, we cannot wait on a header from a target before we read from that target, so READ should probably be the first line of your script.

While the error recognition and reporting in this utility is helpful, it is not exhaustive. Some disallowed behavior in the following command may not result in a runtime exception, and instead have unexpected and undefined consequences. Adhering to the syntax provided below is the best way to make sure your script works.

It is possible to run multiple .shepherd scripts at once, and have them communicate, however having multiple scripts read from the same LCM target is currently untested and may result in undefined behavior. Also keep in mind that there is no synchronization or timing command in this utility yet, however the RUN command may be used in combination with python timing and synchronization to create the same effect.
It is possible to run multiple .shepherd scripts at once, and have them communicate, however having multiple scripts read from the same YDL target is currently untested and may result in undefined behavior. Also keep in mind that there is no synchronization or timing command in this utility yet, however the RUN command may be used in combination with python timing and synchronization to create the same effect.

## Commands

The testing utility reads in .shepherd testing scripts and emulates the LCM communication per those script's specifications. These scripts use special syntax, which is covered below.
The testing utility reads in .shepherd testing scripts and emulates the YDL communication per those script's specifications. These scripts use special syntax, which is covered below.

### Comments

Expand All @@ -32,11 +32,11 @@ Usage: `## <comment>`

### READ

The READ statement will mount a listener to the LCM channel that is indicated.
The READ statement will mount a listener to the YDL channel that is indicated.

Typically, this is put at the top of a test file but it is possible to have multiple READ statements in a test. Subsequent READ statements will cause the LCM queue to clear.
Typically, this is put at the top of a test file but it is possible to have multiple READ statements in a test. Subsequent READ statements will cause the YDL queue to clear.

Usage: `READ <LCM target>`
Usage: `READ <YDL target>`

### RUN

Expand All @@ -60,13 +60,13 @@ Usage: `PRINTP <python expression>`

### SLEEP

The SLEEP statement is used in order to pause the execution of the .shepherd interpreter for a specified amount of time. Any LCM messages received while the interpreter is paused will still be recorded and may be processed by the next WAIT statement that the interpreter encounters. The sleep time may be a decimal, and is in terms of seconds. SLEEP may take a python expression as an argument, so long as it evaluates to a float.
The SLEEP statement is used in order to pause the execution of the .shepherd interpreter for a specified amount of time. Any YDL messages received while the interpreter is paused will still be recorded and may be processed by the next WAIT statement that the interpreter encounters. The sleep time may be a decimal, and is in terms of seconds. SLEEP may take a python expression as an argument, so long as it evaluates to a float.

Usage: `SLEEP <time / python expression>`

### DISCARD

The DISCARD statement clears the LCM / YDL of any messages that have been received so far, but have not been processed, ensuring that they will not be processed. This is helpful after a SLEEP statement, to ensure that any messages that were received during the sleep would be ignored, if that is the desired functionality.
The DISCARD statement clears the YDL / YDL of any messages that have been received so far, but have not been processed, ensuring that they will not be processed. This is helpful after a SLEEP statement, to ensure that any messages that were received during the sleep would be ignored, if that is the desired functionality.

Usage: `DISCARD`

Expand Down Expand Up @@ -120,17 +120,17 @@ Usage: `ASSERT <python conditional expression>`

### WAIT

The WAIT statement is used to pause code execution until a specific LCM message is received by the testing script. A WAIT statement consists of the following pieces:
The WAIT statement is used to pause code execution until a specific YDL message is received by the testing script. A WAIT statement consists of the following pieces:

- A LCM header must follow the WAIT statement, this is the header that will be waited on.
- A YDL header must follow the WAIT statement, this is the header that will be waited on.

- FROM

- A LCM target must follow the header, separated by the keyword FROM. This is the LCM channel that the WAIT statement will listen to for the specified header.
- A YDL target must follow the header, separated by the keyword FROM. This is the YDL channel that the WAIT statement will listen to for the specified header.

- WITH can then be specified, to store arguments from the header into the namespace. This must follow the target specified by FROM. The WITH statement looks something like this, `WITH argument = 'argument in header'`. The name of the argument in the header must be surrounded by single quotes. If the argument is not present in the header, an error will be thrown.

- WITH INFER is a special kind of WITH statement which instructs the testing script to place all of the data found in the LCM message into the namespace with the variable name inferred as the name of the key in the data dictionary from the header. Any key in the data dictionary that is used in another WITH statement will not be inferred, and therefore will not be copied into the namespace a second time. The WITH INFER statement does not need to go in any particular place and will look both ahead and behind when calculating what statements to infer. This means it can go anywhere a normal WITH statement can go. It is important to note that any header keys that are inferred that are not valid python variable names, or that begin with an underscore will cause an error. Therefore you must explicitly name and store any such key values in their own WITH statements in order to give them valid names and cause WITH INFER to skip over them.
- WITH INFER is a special kind of WITH statement which instructs the testing script to place all of the data found in the YDL message into the namespace with the variable name inferred as the name of the key in the data dictionary from the header. Any key in the data dictionary that is used in another WITH statement will not be inferred, and therefore will not be copied into the namespace a second time. The WITH INFER statement does not need to go in any particular place and will look both ahead and behind when calculating what statements to infer. This means it can go anywhere a normal WITH statement can go. It is important to note that any header keys that are inferred that are not valid python variable names, or that begin with an underscore will cause an error. Therefore you must explicitly name and store any such key values in their own WITH statements in order to give them valid names and cause WITH INFER to skip over them.

- SET can then be specified, which will execute a line of python code when the header is received. The SET statement looks something like this, `SET test = True`.

Expand All @@ -148,19 +148,19 @@ Usage: `WAIT <header> FROM <target> WITH <assignment>... SET <python expression>

### TIMEOUT

The TIMEOUT statement is used in order to set the timeout for subsequent WAIT statements. WAIT statements will cause the test to fail if they see no inactivity for the amount of time specified. By default this value is 30 seconds. The TIMEOUT statement modifies this value, and the value stays at the new value until another TIMEOUT statement, or the end of the test. Inactivity is defined as no new LCM messages being received. The timeout time may be a decimal, and is in terms of seconds. TIMEOUT may take a python expression as an argument, so long as it evaluates to a float.
The TIMEOUT statement is used in order to set the timeout for subsequent WAIT statements. WAIT statements will cause the test to fail if they see no inactivity for the amount of time specified. By default this value is 30 seconds. The TIMEOUT statement modifies this value, and the value stays at the new value until another TIMEOUT statement, or the end of the test. Inactivity is defined as no new YDL messages being received. The timeout time may be a decimal, and is in terms of seconds. TIMEOUT may take a python expression as an argument, so long as it evaluates to a float.

Usage: `TIMEOUT <time / python expression>`

### EMIT

The EMIT statement will send an LCM message from the script to a target. The EMIT statement is significantly simpler than the WAIT statement, and it's structure is as follows:
The EMIT statement will send an YDL message from the script to a target. The EMIT statement is significantly simpler than the WAIT statement, and it's structure is as follows:

- A LCM header must follow the EMIT statement, this is the header that will be sent.
- A YDL header must follow the EMIT statement, this is the header that will be sent.

- TO

- A LCM target must follow the header, separated by the keyword TO. This is the LCM channel that the EMIT statement send specified header to.
- A YDL target must follow the header, separated by the keyword TO. This is the YDL channel that the EMIT statement send specified header to.

- WITH can then be specified to set the arguments into the header from the namespace. This must follow the header specified by TO. The WITH statement looks something like this, `WITH 'argument in header' = argument`. The name of the argument in the header must be surrounded by single quotes. It is worth noting that this is the reverse of the WITH statement used in WAIT.

Expand Down Expand Up @@ -202,15 +202,15 @@ where example/example_client.shepherd is the path to a test inside the tests fol

## Future modifications:

The testing utility right now is functional, but flawed. The most precarious systems are the syntax error recognition, the execution of IF and WHILE statements, and the LCM interactions. There are also quite a few features that the framework might benefit from.
The testing utility right now is functional, but flawed. The most precarious systems are the syntax error recognition, the execution of IF and WHILE statements, and the YDL interactions. There are also quite a few features that the framework might benefit from.

### Modifications:

- Syntax for the testing utility is currently hard coded into each of the processing functions. A more robust solution would be to introduce a tokenizer and parser using regular expressions and a grammar such as CUP or BISON to ensure that the syntax makes sense. Unfortunately some of the more specific error messages in the current implementation would become generic, however all syntax errors would be caught.

- The IF and WHILE statements would also see more reliable functionality from the previous strategy. The grammar would allow IF and WHILE to be packaged with their corresponding END statements, and this would make execution faster and more robust. Furthermore, unbalanced END statements would be detected when the file was read in, and not at runtime (how they currently are) making debugging a script much simpler.

- LCM is ultimately too niche (hard to install) and overly complicated for Shepherd's needs, however until a better alternative is found (or coded), the best solution to the LCM issues is to modify the interaction with queues used in the start loop of the tester. Having a dynamic way to reassign LCM targets, or potentially to even read from multiple would make the scripting much more intuitive.
- YDL is ultimately too niche (hard to install) and overly complicated for Shepherd's needs, however until a better alternative is found (or coded), the best solution to the YDL issues is to modify the interaction with queues used in the start loop of the tester. Having a dynamic way to reassign YDL targets, or potentially to even read from multiple would make the scripting much more intuitive.

### Additions:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
READ LCM_TARGETS.SHEPHERD
READ YDL_TARGETS.SHEPHERD
RUN started = False
RUN names = []
RUN ids = []
WHILE not started
WAIT SHEPHERD_HEADERS.PLAYER_JOINED FROM LCM_TARGETS.SHEPHERD WITH name = 'name' WITH id = 'id' OR SHEPHERD_HEADERS.NEXT_STAGE FROM LCM_TARGETS.SHEPHERD SET started = True
WAIT SHEPHERD_HEADERS.PLAYER_JOINED FROM YDL_TARGETS.SHEPHERD WITH name = 'name' WITH id = 'id' OR SHEPHERD_HEADERS.NEXT_STAGE FROM YDL_TARGETS.SHEPHERD SET started = True
RUN names.append(name)
RUN ids.append(id)
EMIT SERVER_HEADERS.PLAYERS TO LCM_TARGETS.SERVER WITH 'usernames' = names WITH 'recipients' = ids
EMIT SERVER_HEADERS.PLAYERS TO YDL_TARGETS.SERVER WITH 'usernames' = names WITH 'recipients' = ids
IF len(names) < 5 and started
RUN started = False
RUN num_players = len(names)
EMIT SERVER_HEADERS.NOT_ENOUGH_PLAYERS TO LCM_TARGETS.SERVER WITH 'players' = num_players
EMIT SERVER_HEADERS.NOT_ENOUGH_PLAYERS TO YDL_TARGETS.SERVER WITH 'players' = num_players
END
END
EMIT SERVER_HEADERS.CHANCELLOR_REQUEST TO LCM_TARGETS.SERVER WITH 'president' = names[0] WITH 'ineligibles' = []
EMIT SERVER_HEADERS.CHANCELLOR_REQUEST TO YDL_TARGETS.SERVER WITH 'president' = names[0] WITH 'ineligibles' = []
ASSERT len(names) >= 5

WAIT SHEPHERD_HEADER.START_NEXT_STAGE FROM LCM_TARGETS.SHEPHERD OR SHEPHERD_HEADER.RESET_CURRENT_STAGE FROM LCM_TARGETS.SHEPHERD AND SHEPHERD_HEADER.RESET_MATCH FROM LCM_TARGETS.SHEPHERD
WAIT SHEPHERD_HEADER.START_NEXT_STAGE FROM YDL_TARGETS.SHEPHERD OR SHEPHERD_HEADER.RESET_CURRENT_STAGE FROM YDL_TARGETS.SHEPHERD AND SHEPHERD_HEADER.RESET_MATCH FROM YDL_TARGETS.SHEPHERD
14 changes: 7 additions & 7 deletions shepherd/tests/examples/example_test/example.shepherd
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
READ LCM_TARGETS.SHEPHERD
READ YDL_TARGETS.SHEPHERD
RUN SETUP = 'setup'
RUN AUTO = 'auto'
RUN stage = SETUP
WHILE stage == SETUP
WAIT SHEPHERD_HEADER.START_NEXT_STAGE FROM LCM_TARGETS.SHEPHERD SET stage = AUTO OR SHEPHERD_HEADER.SETUP_MATCH FROM LCM_TARGETS.SHEPHERD WITH b1_name = 'b1name' WITH b1_num = 'b1num'
WAIT SHEPHERD_HEADER.START_NEXT_STAGE FROM YDL_TARGETS.SHEPHERD SET stage = AUTO OR SHEPHERD_HEADER.SETUP_MATCH FROM YDL_TARGETS.SHEPHERD WITH b1_name = 'b1name' WITH b1_num = 'b1num'
PRINTP stage
END
PRINT entering autonomous
RUN from Utils import *
EMIT SCOREBOARD_HEADER.STAGE_TIMER_START TO LCM_TARGETS.SCOREBOARD WITH 'time' = CONSTANTS.AUTO_TIME
EMIT SCOREBOARD_HEADER.STAGE_TIMER_START TO LCM_TARGETS.SCOREBOARD WITH 'time' = CONSTANTS.AUTO_TIME
EMIT SCOREBOARD_HEADER.STAGE TO LCM_TARGETS.SCOREBOARD WITH 'stage' = AUTO
EMIT SCOREBOARD_HEADER.STAGE TO LCM_TARGETS.SCOREBOARD WITH 'stage' = AUTO
RUN from utils import *
EMIT UI_HEADER.SCORES TO YDL_TARGETS.UI WITH 'blue_score' = 0 WITH 'gold_score' = 0
EMIT UI_HEADER.SCORES TO YDL_TARGETS.UI WITH 'blue_score' = 0 WITH 'gold_score' = 0
EMIT UI_HEADER.STATE TO YDL_TARGETS.UI WITH 'stage' = AUTO
EMIT UI_HEADER.STATE TO YDL_TARGETS.UI WITH 'stage' = AUTO
PASS
Loading

0 comments on commit 4c18f0c

Please sign in to comment.