3 Commits

Author SHA1 Message Date
8791a2e3f3 Adding some schema for validation and AI generation 2026-05-22 22:51:15 +02:00
6f832cd67b validation: cover nil/None return from lua_func/py_func
Two new steps per language: function returning nothing and function
returning explicit nil/None. Both tagged $(test)_PASS — they would
have failed before the lua nil fix (Lua side reported nil result as
error). Python side already worked but is covered for parity.
2026-05-17 18:13:03 +02:00
ff46886865 lua_func: nil return is not an error
_handle_request was using the 1st pcall return as the error
discriminator, so any Lua function returning nothing (e.g. long_wait
in the example) was reported as failed. Discriminate on the 2nd
return (err) instead, and encode nil result as cjson.null so the
returned_value field stays present in the JSON-RPC response.
2026-05-17 18:04:51 +02:00
7 changed files with 541 additions and 10 deletions

73
schema/test.tum Normal file
View File

@@ -0,0 +1,73 @@
config_file:
- premier
- saluT
main:
name: Main file
steps:
- group:
name: Test
doc: Une peitite documentation
no_fail: true
steps:
- let:
values:
- my_var: <| ${salut} |>
- check:
values:
- <| ${salut} |>
- dialog_message:
question: c'est quoi?
- lua_func:
file: c'est quoi?
func_name: c'est quoi?
- console:
console_name: cons_1
steps:
- open:
protocol: telnet
terminal_path: ijfeifj
- read_until : {expected: "tutu", timeout: -4.5, mute: true}
- write: something
- writeln: tutu
- close :
- json_rpc:
name: JSONRPC console Query
doc: JSONRPC console Query not waiting (only send)
console:
name : jsonrpc_server
prompt: "@@>"
timeout: 1
version: "2.0"
steps:
- query:
method: echo
params:
- Hello world
- [0, 1, 2, 3]
id: 3095372
no_wait: true
- json_rpc:
name: JSONRPC console Reception
doc: JSONRPC console reception of the previous request
console: {name : jsonrpc_server}
timeout: 1
steps:
- receive:
id: 3095372
timeout: 0.5
report:
enabled: true
log_stored: true
export:
junit:
path: $(validation_report_path)
file_name: $(validation_report_file).junit
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)

417
schema/tum.json Normal file
View File

@@ -0,0 +1,417 @@
{
"$defs": {
"config_file":{
"desciption": "The list of the configuration files",
"type": "array",
"items": {
"type": "string"
}
},
"cons_open" : {
"type":"object",
"properties": {
"protocol" : {
"enum": ["telnet", "ssh", "serial", "rawtcp", "terminal"]
},
"telnet_host" : {"type": "string"},
"telnet_port" : {"type": "string"},
"ssh_host": {"type": "string"},
"ssh_user": {"type": "string"},
"ssh_pwd": {"type": "string"},
"serial_port": {"type": "string"},
"serial_baudrate": {"type": "integer"},
"buffered": {"type": "boolean"},
"tcp_host" : {"type": "string"},
"tcp_port" : {"type": "string"},
"terminal_path": {"type":"string"},
"shell": {"type":"string"}
},
"required":["protocol"],
"additionalProperties": false
},
"cons_read": {
"type":"object",
"properties": {
"protocol" : {
"enum": ["telnet", "ssh", "serial", "rawtcp", "terminal"]
},
"expected" : {"type": "string"},
"timeout": {"type": "number"},
"mute": {"type": "boolean"}
},
"required":["expected"],
"additionalProperties": false
},
"console" : {
"description": "The let items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"console_name": {"type":"string" },
"steps": {
"type": "array",
"items": {
"type": "object",
"properties":{
"open": {
"$ref": "#/$defs/cons_open"
},
"write": { "type": "string" },
"writeln": { "type": "string" },
"read_until": {
"$ref": "#/$defs/cons_read"
},
"close": { "type": "null"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": ["steps", "console_name"]
},
"json_rpc_console": {
"type":"object",
"properties": {
"name" : {"type": "string"},
"prompt" : {"type": "string"}
},
"required":["name"],
"additionalProperties": false
},
"json_rpc_udp": {
"type":"object",
"properties": {
"server" : {"type": "string"},
"udp_snd_port" : {"type": "integer"},
"udp_rcv_port" : {"type": "integer"},
"bufsize" : {"type": "integer"}
},
"required":["name"],
"additionalProperties": false
},
"json_rpc_query": {
"type":"object",
"properties": {
"method" : { "type": "string"},
"params" : { "type": "array"},
"id" : {"type": "integer"},
"no_wait" : {"type": "boolean"}
},
"required":["method"],
"additionalProperties": false
},
"json_rpc_receive": {
"type":"object",
"properties": {
"id" : {"type": "integer"},
"timeout" : {"type": "number"}
},
"required":["id"],
"additionalProperties": false
},
"json_rpc" : {
"description": "The json_rpc items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"expected" : {"type": "string"},
"udp" : { "$ref": "#/defs/json_rpc_udp"},
"console" : { "$ref": "#/$defs/json_rpc_console"},
"timeout": {"type": "number"},
"version": {"enum": ["1.0", "2.0"]},
"steps": {
"type": "array",
"items": {
"type": "object",
"properties":{
"open": { "type": "null"},
"query": { "$ref": "#/$defs/json_rpc_query" },
"receive": { "$ref": "#/$defs/json_rpc_receive" },
"writeln": { "type": "string" },
"read_until": { "$ref": "#/$defs/cons_read"
},
"close": { "type": "null"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": ["steps"]
},
"group" : {
"description": "The group items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"steps": {
"type": "array",
"items": {
"type": "object",
"properties":{
"let": { "$ref": "#/$defs/let" },
"check": { "$ref": "#/$defs/check" },
"dialog_message": { "$ref": "#/$defs/dialog_txt" },
"dialog_note": { "$ref": "#/$defs/dialog_txt" },
"dialog_value": { "$ref": "#/$defs/dialog_txt" },
"lua_func": { "$ref": "#/$defs/func" },
"py_func": { "$ref": "#/$defs/func" },
"console": { "$ref": "#/$defs/console" },
"json_rpc": { "$ref": "#/$defs/json_rpc" },
"group": { "$ref": "#/$defs/group" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": ["steps"]
},
"main" : {
"description": "The Main items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"version": { "type": "string"},
"steps": {
"type": "array",
"items": {
"type": "object",
"properties":{
"let": { "$ref": "#/$defs/let" },
"check": { "$ref": "#/$defs/check" },
"dialog_message": { "$ref": "#/$defs/dialog_txt" },
"dialog_note": { "$ref": "#/$defs/dialog_txt" },
"dialog_value": { "$ref": "#/$defs/dialog_txt" },
"lua_func": { "$ref": "#/$defs/func" },
"py_func": { "$ref": "#/$defs/func" },
"console": { "$ref": "#/$defs/console" },
"json_rpc": { "$ref": "#/$defs/json_rpc" },
"group": { "$ref": "#/$defs/group" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": ["steps"]
},
"sleep" : {
"description": "Sleep for X time [secondes",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"dialog": { "type": "boolean"},
"timeout": { "type": "number"}
},
"additionalProperties": false,
"required": ["timeout"]
},
"let" : {
"description": "The let items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"values": {
"type": "array",
"items": {"type": "object" }
}
},
"additionalProperties": false,
"required": ["values"]
},
"check" : {
"description": "The let items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"values": {
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false,
"required": ["values"]
},
"dialog_txt" : {
"description": "The let items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"question": { "type": "string" }
},
"additionalProperties": false,
"required": ["question"]
},
"func" : {
"description": "The py_fun and lua_func items",
"type": "object",
"properties":{
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean"},
"execute_on_stop":{ "type": "boolean"},
"skipped":{ "type": "boolean"},
"no_fail":{ "type": "boolean"},
"doc":{ "type": "string"},
"key":{ "type": "string"},
"report":{ "type": "string"},
"condition":{ "type": "string"},
"process_result":{ "type": "string"},
"expeted_result":{ "type": "string"},
"store_result":{ "type": "string"},
"file": { "type": "string"},
"func_name": { "type": "string"},
"context_id": { "type": "string"},
"param" : {
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false,
"required": ["file", "func_name"]
},
"report_export": {
"type": "object",
"properties": {
"path": {"type":"string"},
"file_name": {"type":"string"},
"pattern": {"type":"string"},
"key": {"type":"string"}
}
},
"report":{
"desciption": "The list of the configuration files",
"type": "object",
"properties": {
"enabled": {"type":"boolean"},
"log_stored": {"type":"boolean"},
"export": {
"type": "object",
"properties":{
"html": { "$ref": "#/$defs/report_export" },
"sqlite": { "$ref": "#/$defs/report_export" },
"junit": { "$ref": "#/$defs/report_export" }
}
}
}
}
},
"type": "object",
"properties": {
"config_file" : { "$ref": "#/$defs/config_file" },
"main": { "$ref": "#/$defs/group" },
"report" : { "$ref": "#/$defs/report" }
},
"required" : ["main"],
"additionalProperties": false
}

View File

@@ -41,8 +41,7 @@ end
--- INTERNAL: Handle requests from the client
function JSONRPC:_handle_request(req)
local method = self.methods[req.method]
local ok, ret
local res, err
local ok, ret, err
if not method then
if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end
return
@@ -52,15 +51,18 @@ function JSONRPC:_handle_request(req)
-- Only send response if it's not a Notification (notifications have no ID)
if req.id then
if ok then
res = ret
if res == nil then
self:_send_error(req.id, tostring(err))
else
self:_send({ jsonrpc = "2.0", result = { returned_value = res }, id = req.id })
end
else
if not ok then
-- pcall trapped a runtime error in the method itself.
self:_send_error(req.id, tostring(ret))
elseif err ~= nil then
-- Method ran but signaled a logical error via its 2nd return.
self:_send_error(req.id, tostring(err))
else
-- Success. A user function returning nothing yields ret==nil;
-- encode it as JSON null so "returned_value" stays present.
local val = ret
if val == nil then val = json.null end
self:_send({ jsonrpc = "2.0", result = { returned_value = val }, id = req.id })
end
end
end

View File

@@ -49,4 +49,12 @@ function module.test_delgd()
return 0
end
function module.return_nothing()
-- Returns no value: ret is nil but no error.
end
function module.return_explicit_nil()
return nil
end
return module

View File

@@ -186,6 +186,18 @@
file: $(test_path)$(psep)lua_func.lua
func_name: test_delgd
- lua_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_nothing
- lua_func:
name: function returning explicit nil should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_explicit_nil
- group:
name: context_id tests
steps:

View File

@@ -54,3 +54,10 @@ def test_delgd():
tm.delgd("_py_delgd_test")
assert tm.gd("_py_delgd_test", None) is None
return 0
def return_nothing():
# Falls off the end: implicit None return, no error.
pass
def return_explicit_none():
return None

View File

@@ -196,6 +196,18 @@
file: $(test_path)$(psep)py_func.py
func_name: test_delgd
- py_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_nothing
- py_func:
name: function returning explicit None should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_explicit_none
- group:
name: context_id tests
steps: