Skip to content

Commit 64ddbbd

Browse files
committed
Implemented console commands in Scenario files.
1 parent 7067d25 commit 64ddbbd

5 files changed

Lines changed: 248 additions & 21 deletions

File tree

APIImpl/Misc.lua

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ return
1818
a_Simulator.logger:info("LOG: %s", a_Text)
1919
end,
2020

21-
["LOGWARNING(string)"] = function (a_Simulator, a_Text)
22-
a_Simulator.logger:warning("LOGWARNING: %s", a_Text)
23-
end,
24-
2521
["LOGERROR(string)"] = function (a_Simulator, a_Text)
2622
a_Simulator.logger:warning("LOGERROR: %s", a_Text)
2723
end,
2824

25+
["LOGINFO(string)"] = function (a_Simulator, a_Text)
26+
a_Simulator.logger:info("LOGINFO: %s", a_Text)
27+
end,
28+
29+
["LOGWARNING(string)"] = function (a_Simulator, a_Text)
30+
a_Simulator.logger:warning("LOGWARNING: %s", a_Text)
31+
end,
32+
2933
["<static, global> sqlite3:open(string)"] = function (a_Simulator, a_FileName)
3034
return sqlite3.open(a_Simulator:redirectPath(a_FileName))
3135
end,

Scenario.lua

Lines changed: 138 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,29 @@ end
136136

137137

138138

139+
--- Sandbox handler of the "consoleCommand" keyword.
140+
-- Simulates an admin executing a console command
141+
local function sandboxConsoleCommand(a_Table)
142+
-- Check the attributes:
143+
local command = a_Table.command
144+
if not(command) then
145+
error("Error in scenario file, consoleCommand doesn't have the required \"command\" attribute", 2)
146+
end
147+
148+
-- Return the action implementation:
149+
return function(a_Simulator)
150+
a_Simulator.logger:trace(
151+
"Scenario: processing action \"consoleCommand\", command \"%s\".",
152+
command
153+
)
154+
a_Simulator:executeConsoleCommand(command)
155+
end
156+
end
157+
158+
159+
160+
161+
139162
--- Fuzzes a single command
140163
-- a_Simulator is the simulator instance on which to fuzz the commands
141164
-- a_Command is the registered command (string) being fuzzed
@@ -176,6 +199,45 @@ end
176199

177200

178201

202+
--- Fuzzes a single console command
203+
-- a_Simulator is the simulator instance on which to fuzz the commands
204+
-- a_Command is the registered command (string) being fuzzed
205+
-- a_Choices is the array-table of choices for the command parameters
206+
-- a_MinLen is the minimum length of the fuzzed command parameter array
207+
-- a_MaxLen is the maximum length of the fuzzed command parameter array
208+
local function fuzzConsoleCommand(a_Simulator, a_Command, a_Choices, a_MinLen, a_MaxLen)
209+
-- Recursively fuzzes a single console command
210+
-- a_CurrentIndex is the index into the a_Split array specifying the index that this recursion level should modify
211+
-- a_Split is the command params split array
212+
-- The recursion is called "backwards", the last param is chosen first and then the previous param is recursed
213+
-- When a_CurrentIndex is zero, the actual command handlers are invoked
214+
local function fuzzSingleCommand(a_Simulator, a_Command, a_Choices, a_NumChoices, a_CurrentIndex, a_Split)
215+
if (a_CurrentIndex == 0) then
216+
-- We've built the whole command, serialize the params into a string and execute it:
217+
a_Simulator.logger:info("Scenario: fuzzing console command \"%s\".", a_Command .. " " .. table.concat(a_Split, " "))
218+
a_Simulator:executeConsoleCommand(a_Command .. " " .. table.concat(a_Split, " "))
219+
-- Process all queued callbacks:
220+
a_Simulator:processAllQueuedCallbackRequests()
221+
return
222+
end
223+
224+
-- Try all choices on position <a_CurrentIndex> and recurse:
225+
for ch = 1, a_NumChoices do
226+
a_Split[a_CurrentIndex] = a_Choices[ch]
227+
fuzzSingleCommand(a_Simulator, a_Command, a_Choices, a_NumChoices, a_CurrentIndex - 1, a_Split)
228+
end
229+
end
230+
231+
-- Start the fuzzing:
232+
for len = a_MinLen, a_MaxLen do
233+
fuzzSingleCommand(a_Simulator, a_Command, a_Choices, #a_Choices, len, {})
234+
end -- for len - number of chosen params
235+
end
236+
237+
238+
239+
240+
179241
--- Sandbox handler of the "fuzzAllCommands" keyword.
180242
-- Simulates a player executing each registered command with all kinds of parameters
181243
local function sandboxFuzzAllCommands(a_Table)
@@ -213,6 +275,64 @@ end
213275

214276

215277

278+
--- Sandbox handler of the "fuzzConsoleCommand" keyword.
279+
-- Simulates an admin executing a registered console command with all kinds of parameters
280+
local function sandboxFuzzConsoleCommand(a_Table)
281+
-- Check the attributes:
282+
local choices = a_Table.choices
283+
if (not(choices) or (type(choices) ~= "table") or not(choices[1])) then
284+
error("Error in scenario file, fuzzConsoleCommand doesn't have the required \"choices\" array-table attribute", 2)
285+
end
286+
local cmd = a_Table.command
287+
if not(cmd) then
288+
error("Error in scenario file, fuzzConsoleCommand doesn't have the required \"command\" string attribute", 2)
289+
end
290+
local maxLen = tonumber(a_Table.maxLen)
291+
if not(maxLen) then
292+
error("Error in scenario file, fuzzConsoleCommand doesn't have the required \"maxLen\" number attribute", 2)
293+
end
294+
a_Table.maxLen = maxLen
295+
a_Table.minLen = a_Table.minLen or 0
296+
297+
-- Return the action implementation:
298+
return function(a_Simulator)
299+
a_Simulator.logger:trace("Scenario: processing action \"fuzzConsoleCommand(\"%s\")\".", cmd)
300+
fuzzConsoleCommand(a_Simulator, cmd, a_Table.choices, a_Table.minLen, a_Table.maxLen)
301+
end
302+
end
303+
304+
305+
306+
307+
308+
--- Sandbox handler of the "fuzzAllConsoleCommands" keyword.
309+
-- Simulates an admin executing each registered console command with all kinds of parameters
310+
local function sandboxFuzzAllConsoleCommands(a_Table)
311+
-- Check the attributes:
312+
local choices = a_Table.choices
313+
if (not(choices) or (type(choices) ~= "table") or not(choices[1])) then
314+
error("Error in scenario file, fuzzAllConsoleCommands doesn't have the required \"choices\" array-table attribute", 2)
315+
end
316+
local maxLen = tonumber(a_Table.maxLen)
317+
if not(maxLen) then
318+
error("Error in scenario file, fuzzAllConsoleCommands doesn't have the required \"maxLen\" number attribute", 2)
319+
end
320+
a_Table.maxLen = maxLen
321+
a_Table.minLen = a_Table.minLen or 0
322+
323+
-- Return the action implementation:
324+
return function(a_Simulator)
325+
a_Simulator.logger:trace("Scenario: processing action \"fuzzAllConsoleCommands\".")
326+
for cmd, cmdReg in pairs(a_Simulator.registeredConsoleCommandHandlers) do
327+
fuzzConsoleCommand(a_Simulator, cmd, a_Table.choices, a_Table.minLen, a_Table.maxLen)
328+
end
329+
end
330+
end
331+
332+
333+
334+
335+
216336
--- Sandbox handler of the "initializePlugin" keyword.
217337
-- Initializes the plugin (and loads it before that if not loaded yet)
218338
local function sandboxInitializePlugin(a_Table)
@@ -397,21 +517,24 @@ end
397517
-- Provides only the scenario functions
398518
local scenarioSandbox =
399519
{
400-
scenario = nil, -- Will be explicitly modified for each file being loaded
401-
redirect = sandboxRedirect,
402-
world = sandboxWorld,
403-
connectPlayer = sandboxConnectPlayer,
404-
playerCommand = sandboxPlayerCommand,
405-
fuzzAllCommands = sandboxFuzzAllCommands,
406-
initializePlugin = sandboxInitializePlugin,
407-
loadPluginFiles = sandboxLoadPluginFiles,
408-
fsCreateFile = sandboxFsCreateFile,
409-
fsCopyFile = sandboxFsCopyFile,
410-
fsRenameFile = sandboxFsRenameFile,
411-
fsDeleteFile = sandboxFsDeleteFile,
412-
fsCreateFolder = sandboxFsCreateFolder,
413-
fsRenameFolder = sandboxFsRenameFolder,
414-
fsDeleteFolder = sandboxFsDeleteFolder,
520+
scenario = nil, -- Will be explicitly modified for each file being loaded
521+
redirect = sandboxRedirect,
522+
world = sandboxWorld,
523+
connectPlayer = sandboxConnectPlayer,
524+
playerCommand = sandboxPlayerCommand,
525+
fuzzAllCommands = sandboxFuzzAllCommands,
526+
consoleCommand = sandboxConsoleCommand,
527+
fuzzConsoleCommand = sandboxFuzzConsoleCommand,
528+
fuzzAllConsoleCommands = sandboxFuzzAllConsoleCommands,
529+
initializePlugin = sandboxInitializePlugin,
530+
loadPluginFiles = sandboxLoadPluginFiles,
531+
fsCreateFile = sandboxFsCreateFile,
532+
fsCopyFile = sandboxFsCopyFile,
533+
fsRenameFile = sandboxFsRenameFile,
534+
fsDeleteFile = sandboxFsDeleteFile,
535+
fsCreateFolder = sandboxFsCreateFolder,
536+
fsRenameFolder = sandboxFsRenameFolder,
537+
fsDeleteFolder = sandboxFsDeleteFolder,
415538
}
416539

417540

ScenarioFiles.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Takes a dictionary table as its parameter. The `playerName` value specifies the
7979
If, at the time the action is executed, the specified player is not connected, the Checker aborts with an error message.
8080

8181
## fuzzAllCommands
82-
Simulates a player executing each and every command with combinations of command parameters. It takes an array of strings and builds commands with parameters picked from this array, and a minimum / maximum number of parameters; then it executes each command with all combinations of parameters from the array between the max and min. For example, given the array `{"a", "b"}`, maximum length of 3 and minimum length of 0, it tests each command with the following parameters:
82+
Simulates a player executing each registered command with combinations of command parameters. It takes an array of strings and builds commands with parameters picked from this array, and a minimum / maximum number of parameters; then it executes each command with all combinations of parameters from the array between the max and min. For example, given the array `{"a", "b"}`, maximum length of 3 and minimum length of 0, it tests each command with the following parameters:
8383
```
8484
<none>
8585
a
@@ -102,6 +102,55 @@ Takes a dictionary table as its parameter. The `playerName` value specifies the
102102

103103
If, at the time the action is executed, the specified player is not connected, the Checker aborts with an error message.
104104

105+
## consoleCommand
106+
Simulates an admin executing a console command. The `HOOK_EXECUTE_COMMAND` hook is fired and its return value is respected. Then the command handler is invoked.
107+
108+
Takes a dictionary table as its parameter. The `command` value specifies the entire command to execute.
109+
110+
## fuzzConsoleCommand
111+
Simulates an admin executing a console command with combinations of command parameters. It takes an array of strings and builds console commands with parameters picked from this array, and a minimum / maximum number of parameters; then it executes the console command with all combinations of parameters from the array between the max and min. For example, given the array `{"a", "b"}`, maximum length of 3 and minimum length of 0, it tests the console command with the following parameters:
112+
```
113+
<none>
114+
a
115+
b
116+
a a
117+
a b
118+
b a
119+
b b
120+
a a a
121+
a a b
122+
a b a
123+
a b b
124+
b a a
125+
b a b
126+
b b a
127+
b b b
128+
```
129+
130+
Takes a dictionary table as its parameter. The `command` specifies the base console command to test. The `choices` specifies an array of strings that are used for the parameters. The 'maxLen' specifies the maximum number of parameters, the optional `minLen` (default: 0) specifies the minimum number of parameters.
131+
132+
## fuzzAllConsoleCommands
133+
Simulates an admin executing each registered console command with combinations of command parameters. It takes an array of strings and builds console commands with parameters picked from this array, and a minimum / maximum number of parameters; then it executes each console command with all combinations of parameters from the array between the max and min. For example, given the array `{"a", "b"}`, maximum length of 3 and minimum length of 0, it tests each console command with the following parameters:
134+
```
135+
<none>
136+
a
137+
b
138+
a a
139+
a b
140+
b a
141+
b b
142+
a a a
143+
a a b
144+
a b a
145+
a b b
146+
b a a
147+
b a b
148+
b b a
149+
b b b
150+
```
151+
152+
Takes a dictionary table as its parameter. The `choices` specifies an array of strings that are used for the parameters. The 'maxLen' specifies the maximum number of parameters, the optional `minLen` (default: 0) specifies the minimum number of parameters.
153+
105154
## fsCreateFile
106155
Creates a file and, optionally, fills it with data. Takes a dictionary table as its parameter. The `fileName` specifies the file to create. Note that redirection is NOT performed on this filename. The optional `contents` value specifies the contents to write into the new file.
107156

Simulator.lua

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,45 @@ end
857857

858858

859859

860+
--- Executes a callback request simulating the admin executing the specified console command
861+
-- Calls the command execution hooks and if they allow, the command handler itself
862+
-- Returns true if the command was executed, false if not.
863+
function Simulator:executeConsoleCommand(a_CommandString)
864+
-- Check params:
865+
assert(self)
866+
assert(type(a_CommandString) == "string")
867+
868+
-- Call the command execution hook:
869+
local split = self:splitCommandString(a_CommandString)
870+
if (self:executeHookCallback("HOOK_EXECUTE_COMMAND", nil, split, a_CommandString)) then
871+
self.logger:trace("Plugin hook refused to execute console command \"%s\".", a_CommandString)
872+
return false
873+
end
874+
875+
-- Call the command handler:
876+
split = self:splitCommandString(a_CommandString) -- Re-split, in case the hooks changed it
877+
local cmdReg = self.registeredConsoleCommandHandlers[split[1]]
878+
if not(cmdReg) then
879+
self.logger:warning("Trying to execute console command \"%s\" for which there's no registered handler.", split[1])
880+
return false
881+
end
882+
local res = self:processCallbackRequest(
883+
{
884+
Function = cmdReg.callback,
885+
ParamValues = { split, a_CommandString },
886+
Notes = "Console command " .. a_CommandString,
887+
}
888+
)
889+
if ((type(res) == "table") and (type(res[2]) == "string")) then
890+
self.logger:info("Console command \"%s\" returned string \"%s\".", a_CommandString, res[2])
891+
end
892+
return true
893+
end
894+
895+
896+
897+
898+
860899
--- Executes a callback request simulating the specified hook type
861900
-- a_HookTypeStr is the string name of the hook ("HOOK_PLAYER_DISCONNECTING" etc.)
862901
-- All the rest of the params are given to the hook as-is
@@ -922,7 +961,7 @@ function Simulator:executePlayerCommand(a_PlayerName, a_CommandString)
922961
local cmdReg = self.registeredCommandHandlers[split[1]]
923962
if not(cmdReg) then
924963
self.logger:warning("Trying to execute command \"%s\" for which there's no registered handler.", split[1])
925-
return
964+
return false
926965
end
927966
self:processCallbackRequest(
928967
{
@@ -931,6 +970,7 @@ function Simulator:executePlayerCommand(a_PlayerName, a_CommandString)
931970
Notes = "Command " .. a_CommandString,
932971
}
933972
)
973+
return true
934974
end
935975

936976

tests/Gallery/FuzzCommands.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ scenario
2020
name = "world",
2121
},
2222
initializePlugin(),
23+
fuzzAllConsoleCommands
24+
{
25+
-- command = "gallery",
26+
choices =
27+
{
28+
"checkindices",
29+
"fix",
30+
"-force",
31+
},
32+
maxLen = 2,
33+
},
2334
connectPlayer -- Simulate a player connection
2435
{
2536
name = "player1",

0 commit comments

Comments
 (0)