Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 144 additions & 5 deletions .github/workflows/scripts/adj-tester/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
"""Entrypoint script for validating project JSON files against schemas.
"""

ADJ Validator

Version: 1.1.0

JavierRibaldelRio

Entrypoint script for validating project JSON files against schemas.

This script coordinates several checks over files in the test/
directory using JSON Schema validation and project-specific rules
Expand All @@ -15,6 +23,7 @@
is_valid_ipv4,
print_results,
logError,
info_message,
validate_with_schema,
)

Expand Down Expand Up @@ -268,7 +277,7 @@ def check_board_json(path: str):
except RuntimeError as e:
error_list.append(logError(path, "<load>", str(e)))
is_valid = False
print_results(path, is_valid, error_list)
print_results(path, is_valid, error_list, type="(Board)")

if is_valid:
return board
Expand Down Expand Up @@ -368,11 +377,11 @@ def check_measurement_json(path: str, previous_ids=None):
except RuntimeError as e:
error_list.append(logError(path, "<load>", str(e)))
is_valid = False
print_results("\t" + path, is_valid, error_list)
print_results(path, is_valid, error_list, type="(Measurements)", prefix="\t")
return is_valid


def check_packet_json(path: str, measurement_ids=None):
def check_packet_json(path: str, sockets: set, measurement_ids=None):
"""Validate a packet JSON file.

Ensures schema conformance, global uniqueness of packet IDs
Expand Down Expand Up @@ -421,10 +430,127 @@ def check_packet_json(path: str, measurement_ids=None):
)
is_valid = False

# Ensure that socket is defined in the sockets
socket_name = pkt.get("socket", "")
if socket_name != "" and socket_name not in sockets:
error_list.append(
logError(
path,
f"id {pkt_id}",
f"socket '{socket_name}' in id {pkt_id} is not defined in sockets",
)
)
is_valid = False

except RuntimeError as e:
error_list.append(logError(path, "<load>", str(e)))
is_valid = False
print_results(path, is_valid, error_list, type="(Packets/Orders)", prefix="\t")
return is_valid


def check_socket_json(path: str, sockets_name: set):

error_list = []
is_valid = True
try:
# Open json and Schema
sockets = open_json(path)
schema = open_schema("socket.schema.json")

# Schema validation
is_valid, schema_errors = validate_with_schema(sockets, schema, path)
if not is_valid:
error_list.extend(schema_errors)

for socket in sockets:
# ensure socket name is unique across all sockets
socket_name = socket["name"]
if socket_name in sockets_name:
error_list.append(
logError(
path,
"name",
f"Duplicate socket name '{socket_name}' across sockets",
)
)
is_valid = False
else:
sockets_name.add(socket_name)

# check that remote_ip is valid (remote_ip is optional) and it is defined only for type=="DatagramSocket" || type=="Socket"
if (
socket["type"] not in ["DatagramSocket", "Socket"]
and "remote_ip" in socket
):
error_list.append(
logError(
path,
f"name {socket_name}",
f"remote_ip is only allowed for type 'DatagramSocket' or 'Socket', but socket '{socket_name}' has type '{socket['type']}'",
)
)
is_valid = False

else:
socket_ip = socket.get("remote_ip", "")
if socket_ip != "" and not is_valid_ipv4(socket_ip):
error_list.append(
logError(
path,
f"name {socket_name}",
f"Invalid IPv4 address: {socket_ip}",
)
)
is_valid = False

# check that port is only definied if type=="ServerSocket" || type=="DatagramSocket"

if socket.get("port", None) is not None and socket["type"] not in [
"ServerSocket",
"DatagramSocket",
]:
error_list.append(
logError(
path,
f"name {socket_name}",
f"Port is only allowed for type 'ServerSocket' or 'DatagramSocket', but socket '{socket_name}' has type '{socket['type']}'",
)
)
is_valid = False

# check that local_port is only used for type=="Socket"
if (
socket.get("local_port", None) is not None
and socket["type"] != "Socket"
):
error_list.append(
logError(
path,
f"name {socket_name}",
f"local_port is only allowed for type 'Socket', but socket '{socket_name}' has type '{socket['type']}'",
)
)
is_valid = False

# checck thath remote_port is only used for type=="Socket"
if (
socket.get("remote_port", None) is not None
and socket["type"] != "Socket"
):
error_list.append(
logError(
path,
f"name {socket_name}",
f"remote_port is only allowed for type 'Socket', but socket '{socket_name}' has type '{socket['type']}'",
)
)
is_valid = False

except RuntimeError as e:
error_list.append(logError(path, "<load>", str(e)))
is_valid = False
print_results("\t" + path, is_valid, error_list)
print_results(path, is_valid, error_list, type="(Socket)", prefix="\t")
return is_valid


Expand Down Expand Up @@ -466,6 +592,18 @@ def main():
# measurements are unique within a board
measurement_ids = set()

# sockets are unique
sockets_name = set()

# Validate socket JSON files
for socket_path in board.get("sockets", []):
valid = (
check_socket_json(
f"boards/{board_name}/{socket_path}", sockets_name
)
and valid
)

# Validate measurements JSON files
for measurement_path in board.get("measurements", []):
valid = (
Expand All @@ -481,6 +619,7 @@ def main():
valid = (
check_packet_json(
f"boards/{board_name}/{packets_path}",
sockets_name,
measurement_ids,
)
and valid
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/scripts/adj-tester/schema/packet.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@
"minLength": 1
},
"description": "Measurement IDs included in this packet"
},
"period_ms": {
"type": [
"integer",
"number"
],
"description": "Packet transmission period in milliseconds"
},
"socket": {
"type": "string",
"description": "Name of the socket used for this packet"
}
}
}
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/scripts/adj-tester/schema/socket.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Socket configuration schema",
"description": "Array of socket definitions for network communication",
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"name"
],
"properties": {
"type": {
"type": "string",
"enum": [
"ServerSocket",
"DatagramSocket",
"Socket"
],
"description": "Socket type"
},
"name": {
"type": "string",
"minLength": 1,
"description": "Socket name"
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Port number for ServerSocket or DatagramSocket"
},
"local_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Local port number for Socket"
},
"remote_ip": {
"type": "string",
"format": "ipv4",
"description": "Remote IPv4 address"
},
"remote_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Remote port number for Socket"
}
}
}
}
20 changes: 16 additions & 4 deletions .github/workflows/scripts/adj-tester/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def print_header(header: str):
print("=" * header_len)


def print_status(message: str, validated: bool = False):
def format_status(message: str, validated: bool = False):
"""Print a single-line status message.

The `validated` flag controls whether the status is shown as
Expand All @@ -45,7 +45,13 @@ def print_status(message: str, validated: bool = False):
elif validated:
result = "\033[32mPassed\033[0m" # green

print(f"{message} {result}")
return f"{message} {result}"


def info_message(message: str):
"""format to blue color and return the message string."""

return f"\033[34m{message}\033[0m"


def log_message(message: str, status: int = 0):
Expand Down Expand Up @@ -106,7 +112,7 @@ def is_valid_ipv4(address: str) -> bool:
return False


def print_results(file, is_valid, error_list):
def print_results(file, is_valid, error_list, type=None, prefix=""):
"""Print validation results for a file.

The function prints an overall status line and then any detailed
Expand All @@ -118,7 +124,13 @@ def print_results(file, is_valid, error_list):
error_list: Iterable of error message strings.
"""

print_status(file, is_valid)
out = format_status(file, is_valid)

if type != None:
out = f"{out} {info_message(type)}"

print(prefix + out)

for error in error_list:
# Indent errors for readability in CLI output
print("\t|\t" + error)
Expand Down
Loading