Skip to content
Draft
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
23 changes: 23 additions & 0 deletions .lune/test.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- lune run build dev yes && rojo build ./tests.project.json --output tests.rbxlx && run-in-roblox.exe --script ./tests/runner.luau --place ./tests.rbxlx

local process = require("@lune/process")

local function ensure(result: process.SpawnResult)
if not result.ok then
error(`got {result.code}, stderr: {result.stderr}, stdout: {result.stdout}`, 2)
end

return result
end

ensure(process.spawn("lune", {
"run",
"build",
"dev",
"yes",
}))

ensure(process.spawn("rojo", { "build", "./tests.project.json", "--output", "tests.rbxlx" }))
print(ensure(process.spawn("run-in-roblox", { "--script", "./tests/pluginRun.luau", "--place", "tests.rbxlx" }, {
stdio = "forward",
})).stdout)
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,10 @@ A list of "branches" is visible when running the build script without providing
lune run build
```

Tests are available (if you want to contribute, edit [tests/scriptTest.txt](./tests/scriptTest.txt)):

```bash
lune run test
```

For more help, check out the [Rojo](https://rojo.space/docs) and [darklua](https://darklua.com/docs) documentation.
1 change: 1 addition & 0 deletions aftman.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ darklua = "seaofvoices/darklua@0.17.1"
selene = "Kampfkarren/selene@0.29.0"
StyLua = "JohnnyMorganz/StyLua@2.1.0"
lune = "lune-org/lune@0.8.9"
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
30 changes: 14 additions & 16 deletions modules/client/wm/sandbox/environment.luau
Original file line number Diff line number Diff line change
Expand Up @@ -311,17 +311,20 @@ function Module:Init()
return error(message, safeLevel or 0)
end

-- https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#gcinfo
senv.gcinfo = renv.gcinfo

-- https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#getfenv
senv.getfenv = function(...)
local stack = ...

local stackType = type(stack)
if stackType == "number" then
assertArg("getfenv", 1, stack >= 0, "level must be non-negative", 1)
stack = seekSafeLevel(stack)
stack = if stack ~= 0 then seekSafeLevel(stack) else stack -- Level 0 is the current thread's environment
elseif stackType == "function" then
if not isStackSafe(stack) then
stack = seekSafeLevel()
stack = 0
end
elseif stackType == "nil" then
stack = seekSafeLevel()
Expand Down Expand Up @@ -400,18 +403,16 @@ function Module:Init()

local stackType = type(stack)
if stackType == "number" then
assertArg("setfenv", 1, stack >= 0, "level must be non-negative")
if stack == 0 then
-- setfenv does nothing when stack is set to 0
return
end
assertArg("setfenv", 1, stack > -1, "level must be non-negative")

if math.floor(stack) < 1 then
if stack == 0 then
-- Level 0 is the current thread's environment
elseif stack < 1 then
-- When stack is under 1 (e.g 0.7) setfenv throws this error
return error("'setfenv' cannot change environment of given object", seekSafeLevel())
else
stack = seekSafeLevel(stack)
end

stack = seekSafeLevel(stack)
elseif stackType == "function" then
if not isStackSafe(stack) then
return error("'setfenv' cannot change environment of given object", seekSafeLevel())
Expand Down Expand Up @@ -501,9 +502,6 @@ function Module:Init()
return senv.delay(...)
end

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#gcinfo
senv.gcinfo = renv.gcinfo

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#printidentity
senv.printidentity = function(...)
local length = select("#", ...)
Expand Down Expand Up @@ -566,12 +564,12 @@ function Module:Init()
-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#time
senv.time = renv.time

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#version
senv.version = renv.version

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#typeof
senv.typeof = renv.typeof

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#version
senv.version = renv.version

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#wait
senv.wait = renv.wait

Expand Down
1 change: 0 additions & 1 deletion modules/client/wm/sandbox/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ end

function Module.claimEnvironment(environment: table, sandbox: sandbox?)
sandbox = sandbox or Module.getSandbox()
Module.assertTerminated(sandbox)

sandbox.Environments[environment] = true
envLookup[environment] = sandbox
Expand Down
30 changes: 14 additions & 16 deletions modules/server/wm/sandbox/environment.luau
Original file line number Diff line number Diff line change
Expand Up @@ -326,17 +326,20 @@ function Module:Init()
return error(message, safeLevel or 0)
end

-- https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#gcinfo
senv.gcinfo = renv.gcinfo

-- https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#getfenv
senv.getfenv = function(...)
local stack = ...

local stackType = type(stack)
if stackType == "number" then
assertArg("getfenv", 1, stack >= 0, "level must be non-negative", 1)
stack = seekSafeLevel(stack)
stack = if stack ~= 0 then seekSafeLevel(stack) else stack -- Level 0 is the current thread's environment
elseif stackType == "function" then
if not isStackSafe(stack) then
stack = seekSafeLevel()
stack = 0
end
elseif stackType == "nil" then
stack = seekSafeLevel()
Expand Down Expand Up @@ -414,18 +417,16 @@ function Module:Init()

local stackType = type(stack)
if stackType == "number" then
assertArg("setfenv", 1, stack >= 0, "level must be non-negative")
if stack == 0 then
-- setfenv does nothing when stack is set to 0
return
end
assertArg("setfenv", 1, stack > -1, "level must be non-negative")

if math.floor(stack) < 1 then
if stack == 0 then
-- Level 0 is the current thread's environment
elseif stack < 1 then
-- When stack is under 1 (e.g 0.7) setfenv throws this error
return error("'setfenv' cannot change environment of given object", seekSafeLevel())
else
stack = seekSafeLevel(stack)
end

stack = seekSafeLevel(stack)
elseif stackType == "function" then
if not isStackSafe(stack) then
return error("'setfenv' cannot change environment of given object", seekSafeLevel())
Expand Down Expand Up @@ -529,9 +530,6 @@ function Module:Init()
return senv.delay(...)
end

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#gcinfo
senv.gcinfo = renv.gcinfo

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#printidentity
senv.printidentity = function(...)
local sandbox = getSandbox()
Expand Down Expand Up @@ -600,9 +598,6 @@ function Module:Init()
-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#time
senv.time = renv.time

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#version
senv.version = renv.version

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#typeof
senv.typeof = function(...)
if select("#", ...) < 1 then
Expand All @@ -612,6 +607,9 @@ function Module:Init()
return typeofWrapped((...))
end

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#version
senv.version = renv.version

-- https://create.roblox.com/docs/reference/engine/globals/RobloxGlobals#wait
senv.wait = function(...)
local sandbox = getSandbox()
Expand Down
116 changes: 116 additions & 0 deletions src/server/testRunner.server.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
local __start = os.clock()

local Log = require("@shared/log")
Log:SetPrefix("[SB]")
Log.print("Loading...")

do
local wm = require("@shared/wm")
wm.isWorkerManager = false
table.freeze(wm)
end

-- Fetch assets and destroy script
local sbActor = script.Parent
local root = sbActor.Parent

do
local thread = coroutine.running()
task.defer(
function() -- Defer has a small yield (under a frame) allowing us to delete the script (instances can't change their parent instantly after there were parented / created)
sbActor:Destroy()
script:Destroy()
script = nil

task.spawn(thread)
end
)

coroutine.yield() -- Yield thread until script has been destroyed, so events dont get connected in the process (then disconnected by :Destroy())
end

local Assets = require("@shared/assets")
Assets:Init(root, "assets")

Log.debug("Loading modules...")

-- local Commands = require("@server/commands")
local WorkerManagers = require("@shared/workerManagers")
local Protection = require("@shared/protection")

WorkerManagers:Init(root, "workerManager")
-- Network:Init()
-- Commands:Init({ require("@server/commands/default"), require("@server/commands/get") })

task.spawn(function()
Log.debug("Protecting TextChatService...")

local TextChatService = game:GetService("TextChatService")

local function protectWithExpectedStructure(instance: Instance, structure: { [string]: any })
for name, subStructure in structure do
local child = instance:WaitForChild(name)
Protection.add(child, "write")

protectWithExpectedStructure(child, subStructure)
end
end

Protection.add(TextChatService, "write")
protectWithExpectedStructure(TextChatService, {
ChatWindowConfiguration = {},
TextChannels = {
RBXGeneral = {},
RBXSystem = {},
},
TextChatCommands = {
RBXHelpCommand = {},
RBXUnmuteCommand = {},
RBXTeamCommand = {},
RBXClearCommand = {},
RBXEmoteCommand = {},
RBXWhisperCommand = {},
RBXMuteCommand = {},
RBXVersionCommand = {},
RBXConsoleCommand = {},
},
ChatInputBarConfiguration = {},
ChannelTabsConfiguration = {},
BubbleChatConfiguration = {},
})

Log.debug("TextChatService should be fully protected")
end)

-- Finalize
Log.debug("Finalizing...")

WorkerManagers:ready()

Log.print(`Loaded in {math.round((os.clock() - __start) * 1000)}ms.`)

local ScriptManager = require("@server/scriptManager")

local player = nil

do
local Players = game:GetService("Players")

repeat
player = Players:FindFirstChildOfClass("Player")
task.wait()
until player

local testScript = ScriptManager:CreateScript(
player,
require("@shared/scriptManager/scriptTypes").Script,
"TestRunner",
(game:WaitForChild("testRunnerSource") :: StringValue).Value
)
testScript.Enabled = true

Log.print("Running test script...")
testScript.Parent = workspace
end

return nil
Loading